refactor(P2): standardize API responses + fix migrations + cleanup DI

Wave 3 — 3 parallel agents fixing P2 code quality issues:

Response format standardization (30 controllers across 8 services):
- Wrapped all raw DTO returns with { success: true, data: result }
- Standardized error responses with { success: false, error: { code, message } }
- Services: chat, social, membership, ads-manager, ads-serving,
  ads-billing, ads-tracking, ads-analytics
- booking-service already compliant (skipped)

Migration fixes:
- ads-billing: Fixed InvoiceId1 spurious FK (explicit HasMany navigation)
- Removed unused IRequestManager DI from: ads-analytics, ads-serving,
  booking, mkt-facebook (classes preserved for future use)

Unused dependencies:
- No Redis/Dapper DI registrations found (only NuGet refs, kept as-is)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-13 20:34:10 +07:00
parent 52c818ea83
commit efabe49157
39 changed files with 193 additions and 194 deletions

View File

@@ -74,7 +74,7 @@ public class AdminMetricsController : ControllerBase
_logger.LogInformation("Generated platform overview for period {Start} to {End}", start, end);
return Ok(overview);
return Ok(new { success = true, data = overview });
}
/// <summary>
@@ -89,7 +89,7 @@ public class AdminMetricsController : ControllerBase
{
var validMetrics = new[] { "spend", "impressions", "clicks", "revenue", "roas" };
if (!validMetrics.Contains(metric.ToLower()))
return BadRequest(new { message = $"Invalid metric. Valid options: {string.Join(", ", validMetrics)}" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = $"Invalid metric. Valid options: {string.Join(", ", validMetrics)}" } });
// EN: Get metrics from last 30 days
// VI: Lấy metrics 30 ngày gần nhất
@@ -129,7 +129,7 @@ c.TotalClicks, "Clicks")).ToList(),
_logger.LogInformation("Generated top {Limit} campaigns by {Metric}", limit, metric);
return Ok(topCampaigns);
return Ok(new { success = true, data = topCampaigns });
}
/// <summary>
@@ -142,6 +142,6 @@ c.TotalClicks, "Clicks")).ToList(),
{
_logger.LogWarning("GetAnomalies not yet implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "Anomaly detection will be implemented in future version with ML models" });
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "Anomaly detection will be implemented in future version with ML models" } });
}
}

View File

@@ -53,7 +53,7 @@ public class AdminReportsController : ControllerBase
_logger.LogInformation("Admin retrieved {Count} reports", reports.Count);
return Ok(reports);
return Ok(new { success = true, data = reports });
}
/// <summary>
@@ -68,7 +68,7 @@ public class AdminReportsController : ControllerBase
var report = await _context.Reports.FindAsync(id);
if (report == null)
return NotFound(new { message = $"Report {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Report {id} not found" } });
_context.Reports.Remove(report);
await _context.SaveChangesAsync();

View File

@@ -37,11 +37,11 @@ public class BreakdownController : ControllerBase
[FromQuery] DateTime? endDate = null)
{
if (string.IsNullOrWhiteSpace(by))
return BadRequest(new { message = "Breakdown dimension 'by' is required" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Breakdown dimension 'by' is required" } });
var validDimensions = new[] { "age", "gender", "device", "placement" };
if (!validDimensions.Contains(by.ToLower()))
return BadRequest(new { message = $"Invalid dimension. Valid options: {string.Join(", ", validDimensions)}" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = $"Invalid dimension. Valid options: {string.Join(", ", validDimensions)}" } });
// EN: Return mock data (full implementation requires event tracking data)
// VI: Trả về mock data (triển khai đầy đủ cần dữ liệu tracking)
@@ -50,7 +50,7 @@ public class BreakdownController : ControllerBase
_logger.LogInformation("Generated mock breakdown data for campaign {CampaignId} by {Dimension}",
id, by);
return Ok(mockBreakdown);
return Ok(new { success = true, data = mockBreakdown });
}
private CampaignBreakdownDto GenerateMockBreakdown(Guid campaignId, string dimension)

View File

@@ -46,7 +46,7 @@ public class InsightsController : ControllerBase
_logger.LogInformation("Generated audience insights for campaign {CampaignId}", campaignId);
return Ok(insights);
return Ok(new { success = true, data = insights });
}
/// <summary>
@@ -83,6 +83,6 @@ public class InsightsController : ControllerBase
_logger.LogInformation("Generated performance insights for advertiser {AdvertiserId}", advertiserId);
return Ok(insights);
return Ok(new { success = true, data = insights });
}
}

View File

@@ -57,9 +57,9 @@ public class MetricsController : ControllerBase
var metrics = await _mediator.Send(query);
if (metrics == null)
return NotFound(new { message = $"No metrics found for campaign {id}" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"No metrics found for campaign {id}" } });
return Ok(metrics);
return Ok(new { success = true, data = metrics });
}
/// <summary>
@@ -71,8 +71,8 @@ public class MetricsController : ControllerBase
public IActionResult GetAdSetMetrics(Guid id)
{
_logger.LogWarning("AdSet metrics not yet implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "AdSet metrics endpoint not yet implemented" });
return StatusCode(StatusCodes.Status501NotImplemented,
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "AdSet metrics endpoint not yet implemented" } });
}
/// <summary>
@@ -84,7 +84,7 @@ public class MetricsController : ControllerBase
public IActionResult GetAdMetrics(Guid id)
{
_logger.LogWarning("Ad metrics not yet implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "Ad metrics endpoint not yet implemented" });
return StatusCode(StatusCodes.Status501NotImplemented,
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "Ad metrics endpoint not yet implemented" } });
}
}

View File

@@ -48,7 +48,7 @@ public class ReportsController : ControllerBase
};
var reports = await _mediator.Send(query);
return Ok(reports);
return Ok(new { success = true, data = reports });
}
/// <summary>
@@ -63,10 +63,10 @@ public class ReportsController : ControllerBase
[FromQuery] Guid advertiserId)
{
if (string.IsNullOrWhiteSpace(request.Name))
return BadRequest(new { message = "Report name is required" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Report name is required" } });
if (!Enum.TryParse<ReportType>(request.ReportType, out var reportType))
return BadRequest(new { message = "Invalid report type" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Invalid report type" } });
var command = new CreateReportCommand
{
@@ -79,7 +79,7 @@ public class ReportsController : ControllerBase
var reportId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetReportById), new { id = reportId }, reportId);
return CreatedAtAction(nameof(GetReportById), new { id = reportId }, new { success = true, data = reportId });
}
/// <summary>
@@ -92,7 +92,7 @@ public class ReportsController : ControllerBase
{
_logger.LogWarning("GetReportById not yet fully implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "Get report by ID endpoint not yet implemented" });
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "Get report by ID endpoint not yet implemented" } });
}
/// <summary>
@@ -105,7 +105,7 @@ public class ReportsController : ControllerBase
{
_logger.LogWarning("ScheduleReport not yet implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "Schedule report endpoint will be implemented in future version" });
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "Schedule report endpoint will be implemented in future version" } });
}
/// <summary>
@@ -118,6 +118,6 @@ public class ReportsController : ControllerBase
{
_logger.LogWarning("ExportReport not yet implemented");
return StatusCode(StatusCodes.Status501NotImplemented,
new { message = "Export report endpoint will be implemented in future version" });
new { success = false, error = new { code = "NOT_IMPLEMENTED", message = "Export report endpoint will be implemented in future version" } });
}
}

View File

@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AdsAnalyticsService.Infrastructure.Idempotency;
namespace AdsAnalyticsService.Infrastructure;
@@ -30,7 +29,9 @@ public static class DependencyInjection
}
});
services.AddScoped<IRequestManager, RequestManager>();
// EN: Idempotency services not registered — no handler uses IRequestManager
// VI: Idempotency services không được đăng ký — không có handler nào dùng IRequestManager
return services;
}
}

View File

@@ -38,7 +38,7 @@ public class BillingAccountsController : ControllerBase
var accountId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetBillingAccount), new { id = accountId }, accountId);
return CreatedAtAction(nameof(GetBillingAccount), new { id = accountId }, new { success = true, data = accountId });
}
/// <summary>
@@ -57,10 +57,10 @@ public class BillingAccountsController : ControllerBase
if (account == null)
{
return NotFound(new { message = $"Billing account {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Billing account {id} not found" } });
}
return Ok(account);
return Ok(new { success = true, data = account });
}
/// <summary>
@@ -77,7 +77,7 @@ public class BillingAccountsController : ControllerBase
if (request.Amount <= 0)
{
return BadRequest(new { message = "Amount must be positive" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Amount must be positive" } });
}
var command = new AddFundsCommand(id, request.Amount);
@@ -85,10 +85,10 @@ public class BillingAccountsController : ControllerBase
if (!success)
{
return NotFound(new { message = $"Billing account {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Billing account {id} not found" } });
}
return Ok(new { accountId = id, amountAdded = request.Amount });
return Ok(new { success = true, data = new { accountId = id, amountAdded = request.Amount } });
}
/// <summary>
@@ -107,10 +107,10 @@ public class BillingAccountsController : ControllerBase
if (balance == null)
{
return NotFound(new { message = $"Billing account {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Billing account {id} not found" } });
}
return Ok(new { accountId = id, balance = balance.Value });
return Ok(new { success = true, data = new { accountId = id, balance = balance.Value } });
}
}

View File

@@ -43,14 +43,14 @@ public class CreditLinesController : ControllerBase
if (account == null)
{
return NotFound(new { message = $"Billing account for advertiser {advertiserId} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Billing account for advertiser {advertiserId} not found" } });
}
var availableCredit = account.CreditLimit <= 0
? 0m
: Math.Max(0m, account.CreditLimit - account.Balance);
return Ok(new
return Ok(new { success = true, data = new
{
advertiserId,
accountId = account.Id,
@@ -59,7 +59,7 @@ public class CreditLinesController : ControllerBase
availableCredit,
paymentMethod = account.PaymentMethod.ToString(),
status = account.Status.ToString()
});
} });
}
/// <summary>
@@ -78,7 +78,7 @@ public class CreditLinesController : ControllerBase
if (request.RequestedAmount <= 0)
{
return BadRequest(new { message = "Requested amount must be positive" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Requested amount must be positive" } });
}
var account = await _context.BillingAccounts
@@ -86,14 +86,14 @@ public class CreditLinesController : ControllerBase
if (account == null)
{
return NotFound(new { message = $"Billing account for advertiser {request.AdvertiserId} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Billing account for advertiser {request.AdvertiserId} not found" } });
}
var oldCreditLimit = account.CreditLimit;
account.SetCreditLimit(oldCreditLimit + request.RequestedAmount);
await _context.SaveEntitiesAsync(cancellationToken);
return Accepted(new
return Accepted(new { success = true, data = new
{
advertiserId = request.AdvertiserId,
accountId = account.Id,
@@ -102,7 +102,7 @@ public class CreditLinesController : ControllerBase
newCreditLimit = account.CreditLimit,
status = account.Status.ToString(),
message = "Credit limit increased successfully"
});
} });
}
}

View File

@@ -42,13 +42,13 @@ public class InvoicesController : ControllerBase
var query = new GetInvoicesQuery(billingAccountId, status, pageNumber, pageSize);
var invoices = await _mediator.Send(query);
return Ok(new
{
return Ok(new { success = true, data = new
{
pageNumber,
pageSize,
total = invoices.Count,
data = invoices
});
items = invoices
} });
}
/// <summary>
@@ -67,10 +67,10 @@ public class InvoicesController : ControllerBase
if (invoice == null)
{
return NotFound(new { message = $"Invoice {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Invoice {id} not found" } });
}
return Ok(invoice);
return Ok(new { success = true, data = invoice });
}
/// <summary>
@@ -90,7 +90,7 @@ public class InvoicesController : ControllerBase
if (invoice == null)
{
return NotFound(new { message = $"Invoice {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Invoice {id} not found" } });
}
var lineItemsContent = invoice.LineItems.Any()

View File

@@ -67,14 +67,15 @@ public class InvoiceEntityTypeConfiguration : IEntityTypeConfiguration<Invoice>
.HasColumnType("decimal(18,2)")
.IsRequired();
// EN: Line items as collection / VI: Các dòng chi tiết
builder.HasMany<InvoiceLineItem>()
// EN: Line items as owned collection via navigation property
// VI: Các dòng chi tiết qua navigation property
// NOTE: Must use the navigation expression to avoid EF creating a shadow InvoiceId1 FK
var navigation = builder.Metadata.FindNavigation(nameof(Invoice.LineItems))!;
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
builder.HasMany(i => i.LineItems)
.WithOne()
.HasForeignKey("InvoiceId")
.OnDelete(DeleteBehavior.Cascade);
// EN: Metadata / VI: Ignore navigation property for EF Core
builder.Metadata.FindNavigation(nameof(Invoice.LineItems))!
.SetPropertyAccessMode(PropertyAccessMode.Field);
}
}

View File

@@ -38,7 +38,7 @@ public class AdSetsController : ControllerBase
var adSetId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetAdSetById), new { id = adSetId }, adSetId);
return CreatedAtAction(nameof(GetAdSetById), new { id = adSetId }, new { success = true, data = adSetId });
}
/// <summary>
@@ -53,8 +53,8 @@ public class AdSetsController : ControllerBase
var adSet = await _mediator.Send(new GetAdSetByIdQuery { AdSetId = id });
if (adSet == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Ad set not found" } });
return Ok(adSet);
return Ok(new { success = true, data = adSet });
}
}

View File

@@ -41,7 +41,7 @@ public class AdminAdsController : ControllerBase
PageSize = pageSize
});
return Ok(ads);
return Ok(new { success = true, data = ads });
}
/// <summary>
@@ -58,7 +58,7 @@ public class AdminAdsController : ControllerBase
var result = await _mediator.Send(new ApproveAdCommand { AdId = id });
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Ad not found" } });
return NoContent();
}
@@ -81,7 +81,7 @@ public class AdminAdsController : ControllerBase
});
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Ad not found" } });
return NoContent();
}

View File

@@ -43,7 +43,7 @@ public class AdminCampaignsController : ControllerBase
};
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -55,7 +55,7 @@ public class AdminCampaignsController : ControllerBase
public async Task<ActionResult<CampaignStatsDto>> GetCampaignStats()
{
var stats = await _mediator.Send(new GetCampaignStatsQuery());
return Ok(stats);
return Ok(new { success = true, data = stats });
}
}

View File

@@ -32,7 +32,7 @@ public class AdminReportsController : ControllerBase
public async Task<ActionResult<List<TopAdvertiserDto>>> GetTopAdvertisers([FromQuery] int limit = 10)
{
var advertisers = await _mediator.Send(new GetTopAdvertisersQuery { Limit = limit });
return Ok(advertisers);
return Ok(new { success = true, data = advertisers });
}
/// <summary>
@@ -51,7 +51,7 @@ public class AdminReportsController : ControllerBase
EndDate = endDate
});
return Ok(analytics);
return Ok(new { success = true, data = analytics });
}
}

View File

@@ -38,7 +38,7 @@ public class AdsController : ControllerBase
var adId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetAdById), new { id = adId }, adId);
return CreatedAtAction(nameof(GetAdById), new { id = adId }, new { success = true, data = adId });
}
/// <summary>
@@ -53,9 +53,9 @@ public class AdsController : ControllerBase
var ad = await _mediator.Send(new GetAdByIdQuery { AdId = id });
if (ad == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Ad not found" } });
return Ok(ad);
return Ok(new { success = true, data = ad });
}
/// <summary>
@@ -72,7 +72,7 @@ public class AdsController : ControllerBase
var result = await _mediator.Send(new SubmitAdForReviewCommand { AdId = id });
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Ad not found" } });
return NoContent();
}

View File

@@ -36,7 +36,7 @@ public class AudiencesController : ControllerBase
var audiences = await _mediator.Send(new ListAudiencesQuery { AdvertiserId = advertiserId });
return Ok(audiences);
return Ok(new { success = true, data = audiences });
}
/// <summary>
@@ -51,9 +51,9 @@ public class AudiencesController : ControllerBase
var audience = await _mediator.Send(new GetAudienceByIdQuery { AudienceId = id });
if (audience == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Audience not found" } });
return Ok(audience);
return Ok(new { success = true, data = audience });
}
}

View File

@@ -38,7 +38,7 @@ public class CampaignsController : ControllerBase
var campaignId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetCampaignById), new { id = campaignId }, campaignId);
return CreatedAtAction(nameof(GetCampaignById), new { id = campaignId }, new { success = true, data = campaignId });
}
/// <summary>
@@ -66,7 +66,7 @@ public class CampaignsController : ControllerBase
};
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -81,9 +81,9 @@ public class CampaignsController : ControllerBase
var campaign = await _mediator.Send(new GetCampaignByIdQuery { CampaignId = id });
if (campaign == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Campaign not found" } });
return Ok(campaign);
return Ok(new { success = true, data = campaign });
}
/// <summary>
@@ -105,7 +105,7 @@ public class CampaignsController : ControllerBase
var result = await _mediator.Send(command);
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Campaign not found" } });
return NoContent();
}
@@ -124,7 +124,7 @@ public class CampaignsController : ControllerBase
var result = await _mediator.Send(new ActivateCampaignCommand { CampaignId = id });
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Campaign not found" } });
return NoContent();
}
@@ -143,7 +143,7 @@ public class CampaignsController : ControllerBase
var result = await _mediator.Send(new PauseCampaignCommand { CampaignId = id });
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Campaign not found" } });
return NoContent();
}
@@ -162,7 +162,7 @@ public class CampaignsController : ControllerBase
var result = await _mediator.Send(new DeleteCampaignCommand { CampaignId = id });
if (!result)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Campaign not found" } });
return NoContent();
}

View File

@@ -49,7 +49,7 @@ public class AdminAuctionsController : ControllerBase
};
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -62,6 +62,6 @@ public class AdminAuctionsController : ControllerBase
{
_logger.LogInformation("Auction statistics requested");
var statistics = await _mediator.Send(new GetAuctionStatisticsQuery());
return Ok(statistics);
return Ok(new { success = true, data = statistics });
}
}

View File

@@ -50,7 +50,7 @@ public class AdminBudgetController : ControllerBase
};
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -78,9 +78,9 @@ public class AdminBudgetController : ControllerBase
.FirstOrDefaultAsync();
if (pacer == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Budget pacer not found for this campaign" } });
return Ok(pacer);
return Ok(new { success = true, data = pacer });
}
/// <summary>
@@ -97,13 +97,13 @@ public class AdminBudgetController : ControllerBase
.FirstOrDefaultAsync();
if (pacer == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Budget pacer not found for this campaign" } });
pacer.ResetDailySpend();
await _context.SaveChangesAsync();
_logger.LogInformation("Manual budget reset for Campaign {CampaignId}", campaignId);
return Ok();
return Ok(new { success = true, data = new { message = "Budget reset successfully" } });
}
/// <summary>
@@ -132,6 +132,6 @@ public class AdminBudgetController : ControllerBase
CampaignsExceeded = stats.Count(s => s.Utilization >= 100)
};
return Ok(result);
return Ok(new { success = true, data = result });
}
}

View File

@@ -48,7 +48,7 @@ public class AdminFrequencyController : ControllerBase
})
.ToListAsync();
return Ok(caps);
return Ok(new { success = true, data = caps });
}
/// <summary>
@@ -72,9 +72,9 @@ public class AdminFrequencyController : ControllerBase
.FirstOrDefaultAsync();
if (cap == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Frequency cap not found" } });
return Ok(cap);
return Ok(new { success = true, data = cap });
}
/// <summary>
@@ -87,7 +87,7 @@ public class AdminFrequencyController : ControllerBase
public async Task<ActionResult<FrequencyCapDto>> CreateFrequencyCap([FromBody] CreateFrequencyCapRequest request)
{
if (!Enum.TryParse<FrequencyWindow>(request.Window, out var window))
return BadRequest("Invalid frequency window");
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Invalid frequency window" } });
var cap = new FrequencyCap(request.AdId, request.MaxImpressionsPerUser, window);
@@ -104,7 +104,7 @@ public class AdminFrequencyController : ControllerBase
_logger.LogInformation("Created frequency cap {Id} for Ad {AdId}", cap.Id, request.AdId);
return CreatedAtAction(nameof(GetFrequencyCap), new { id = cap.Id }, dto);
return CreatedAtAction(nameof(GetFrequencyCap), new { id = cap.Id }, new { success = true, data = dto });
}
/// <summary>
@@ -119,7 +119,7 @@ public class AdminFrequencyController : ControllerBase
var cap = await _context.FrequencyCaps.FindAsync(id);
if (cap == null)
return NotFound();
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Frequency cap not found" } });
_context.FrequencyCaps.Remove(cap);
await _context.SaveChangesAsync();

View File

@@ -51,7 +51,7 @@ public class AdsController : ControllerBase
if (result == null)
return NoContent();
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -73,7 +73,7 @@ public class AdsController : ControllerBase
},
HttpContext.RequestAborted);
return Accepted();
return Accepted(new { success = true, data = new { message = "Impression tracked" } });
}
/// <summary>
@@ -95,7 +95,7 @@ public class AdsController : ControllerBase
},
HttpContext.RequestAborted);
return Accepted();
return Accepted(new { success = true, data = new { message = "Click tracked" } });
}
}

View File

@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AdsServingService.Infrastructure.Idempotency;
namespace AdsServingService.Infrastructure;
@@ -41,8 +40,8 @@ public static class DependencyInjection
// Register repositories (when needed)
// services.AddScoped<IAuctionRepository, AuctionRepository>();
// Register idempotency services
services.AddScoped<IRequestManager, RequestManager>();
// EN: Idempotency services not registered — no handler uses IRequestManager
// VI: Idempotency services không được đăng ký — không có handler nào dùng IRequestManager
return services;
}

View File

@@ -46,7 +46,7 @@ public class AdminAttributionController : ControllerBase
);
_logger.LogInformation("Admin: Retrieved attribution stats");
return Ok(stats);
return Ok(new { success = true, data = stats });
}
/// <summary>
@@ -78,7 +78,7 @@ public class AdminAttributionController : ControllerBase
);
_logger.LogInformation("Admin: Retrieved attribution report for campaign {CampaignId}", campaignId);
return Ok(report);
return Ok(new { success = true, data = report });
}
}

View File

@@ -51,7 +51,7 @@ public class AdminConversionsController : ControllerBase
var result = await _mediator.Send(query, ct);
_logger.LogInformation("Admin: Listed {Count} conversions", result.Count());
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -78,7 +78,7 @@ public class AdminConversionsController : ControllerBase
);
_logger.LogInformation("Admin: Retrieved conversion stats");
return Ok(stats);
return Ok(new { success = true, data = stats });
}
/// <summary>
@@ -107,7 +107,7 @@ public class AdminConversionsController : ControllerBase
);
_logger.LogInformation("Admin: Retrieved conversion details for {ConversionId}", id);
return Ok(detail);
return Ok(new { success = true, data = detail });
}
}

View File

@@ -44,7 +44,7 @@ public class AdminPixelsController : ControllerBase
};
_logger.LogInformation("Admin: Listed {Count} pixels", pixels.Count);
return Ok(pixels);
return Ok(new { success = true, data = pixels });
}
/// <summary>
@@ -63,7 +63,7 @@ public class AdminPixelsController : ControllerBase
{
var events = new List<PixelEventDto>();
_logger.LogInformation("Admin: Listed events for pixel {PixelId}", pixelId);
return Ok(events);
return Ok(new { success = true, data = events });
}
/// <summary>
@@ -90,7 +90,7 @@ public class AdminPixelsController : ControllerBase
);
_logger.LogInformation("Admin: Retrieved stats for pixel {PixelId}", pixelId);
return Ok(stats);
return Ok(new { success = true, data = stats });
}
/// <summary>
@@ -105,7 +105,7 @@ public class AdminPixelsController : ControllerBase
// 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" });
return Ok(new { success = true, data = new { message = "Pixel activated successfully" } });
}
/// <summary>
@@ -120,7 +120,7 @@ public class AdminPixelsController : ControllerBase
// 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" });
return Ok(new { success = true, data = new { message = "Pixel deactivated successfully" } });
}
}

View File

@@ -73,7 +73,7 @@ public class ConversionsController : ControllerBase
var query = new GetConversionsQuery(campaignId, userId, from, to, skip, take);
var result = await _mediator.Send(query, ct);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -92,10 +92,10 @@ public class ConversionsController : ControllerBase
if (result == null)
{
return NotFound(new { Message = "Attribution not found for this conversion" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Attribution not found for this conversion" } });
}
return Ok(result);
return Ok(new { success = true, data = result });
}
}

View File

@@ -50,10 +50,10 @@ public class EventsController : ControllerBase
if (!success)
{
return BadRequest(new { Message = "Invalid pixel code or pixel is not active" });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = "Invalid pixel code or pixel is not active" } });
}
return Accepted(new { Message = "Event tracked successfully" });
return Accepted(new { success = true, data = new { message = "Event tracked successfully" } });
}
/// <summary>
@@ -84,7 +84,7 @@ public class EventsController : ControllerBase
"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" });
return Accepted(new { success = true, data = new { message = "Server-side event tracked successfully" } });
}
}

View File

@@ -41,10 +41,10 @@ public class PixelsController : ControllerBase
if (result == null)
{
return NotFound(new { Message = "Pixel not found for this advertiser" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Pixel not found for this advertiser" } });
}
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -68,7 +68,7 @@ public class PixelsController : ControllerBase
return CreatedAtAction(
nameof(GetPixelCode),
new { advertiserId = request.AdvertiserId },
result);
new { success = true, data = result });
}
}

View File

@@ -3,7 +3,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using BookingService.Domain.AggregatesModel.AppointmentAggregate;
using BookingService.Domain.AggregatesModel.TherapistAggregate;
using BookingService.Infrastructure.Idempotency;
using BookingService.Infrastructure.Repositories;
namespace BookingService.Infrastructure;
@@ -53,8 +52,8 @@ public static class DependencyInjection
services.AddScoped<IStaffScheduleRepository, StaffScheduleRepository>();
services.AddScoped<ITherapistRepository, TherapistRepository>();
// EN: Register idempotency services / VI: Đăng ký idempotency services
services.AddScoped<IRequestManager, RequestManager>();
// EN: Idempotency services not registered — no handler uses IRequestManager
// VI: Idempotency services không được đăng ký — không có handler nào dùng IRequestManager
return services;
}

View File

@@ -43,11 +43,11 @@ public class ConversationsController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = ex.Message } });
}
}
@@ -64,7 +64,7 @@ public class ConversationsController : ControllerBase
{
var query = new GetConversationsQuery(userId, page, pageSize);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -82,10 +82,10 @@ public class ConversationsController : ControllerBase
var conversation = result.Conversations.FirstOrDefault(c => c.Id == conversationId);
if (conversation == null)
{
return NotFound($"Conversation {conversationId} not found");
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Conversation {conversationId} not found" } });
}
return Ok(conversation);
return Ok(new { success = true, data = conversation });
}
}

View File

@@ -38,7 +38,7 @@ public class KeysController : ControllerBase
if (string.IsNullOrEmpty(identityUserId))
{
return Unauthorized("User ID not found in token");
return Unauthorized(new { success = false, error = new { code = "UNAUTHORIZED", message = "User ID not found in token" } });
}
var command = new RegisterUserKeysCommand(
@@ -52,7 +52,7 @@ public class KeysController : ControllerBase
);
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -74,11 +74,11 @@ public class KeysController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (InvalidOperationException ex)
{
return NotFound(ex.Message);
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
}
@@ -100,11 +100,11 @@ public class KeysController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (InvalidOperationException ex)
{
return NotFound(ex.Message);
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
}
@@ -122,10 +122,10 @@ public class KeysController : ControllerBase
if (result == null)
{
return NotFound($"Key bundle not found for user {userId}");
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Key bundle not found for user {userId}" } });
}
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -142,7 +142,7 @@ public class KeysController : ControllerBase
if (string.IsNullOrEmpty(identityUserId))
{
return Unauthorized("User ID not found in token");
return Unauthorized(new { success = false, error = new { code = "UNAUTHORIZED", message = "User ID not found in token" } });
}
var query = new GetMyKeyBundleQuery(identityUserId);
@@ -150,10 +150,10 @@ public class KeysController : ControllerBase
if (result == null)
{
return NotFound("Key bundle not registered. Please register your keys first.");
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Key bundle not registered. Please register your keys first." } });
}
return Ok(result);
return Ok(new { success = true, data = result });
}
}

View File

@@ -47,11 +47,11 @@ public class MessagesController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (InvalidOperationException ex)
{
return NotFound(ex.Message);
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
}
@@ -74,7 +74,7 @@ public class MessagesController : ControllerBase
try
{
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (UnauthorizedAccessException ex)
{
@@ -100,11 +100,11 @@ public class MessagesController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (InvalidOperationException ex)
{
return NotFound(ex.Message);
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
}
}

View File

@@ -41,7 +41,7 @@ public class LevelsController : ControllerBase
{
var query = new GetLevelDefinitionsQuery { IncludeInactive = includeInactive };
var levels = await _mediator.Send(query);
return Ok(levels);
return Ok(new { success = true, data = levels });
}
/// <summary>
@@ -62,20 +62,20 @@ public class LevelsController : ControllerBase
try
{
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetAll), new { }, result);
return CreatedAtAction(nameof(GetAll), new { }, new { success = true, data = result });
}
catch (InvalidOperationException ex) when (ex.Message.Contains("already exists"))
{
return Conflict(new { message = ex.Message });
return Conflict(new { success = false, error = new { code = "CONFLICT", message = ex.Message } });
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = ex.Message } });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating level definition");
return StatusCode(500, new { message = "An error occurred while creating level definition" });
return StatusCode(500, new { success = false, error = new { code = "INTERNAL_ERROR", message = "An error occurred while creating level definition" } });
}
}
@@ -100,20 +100,20 @@ public class LevelsController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = ex.Message } });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating level definition {LevelId}", id);
return StatusCode(500, new { message = "An error occurred while updating level definition" });
return StatusCode(500, new { success = false, error = new { code = "INTERNAL_ERROR", message = "An error occurred while updating level definition" } });
}
}
@@ -137,16 +137,16 @@ public class LevelsController : ControllerBase
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = ex.Message } });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deactivating level definition {LevelId}", id);
return StatusCode(500, new { message = "An error occurred while deactivating level definition" });
return StatusCode(500, new { success = false, error = new { code = "INTERNAL_ERROR", message = "An error occurred while deactivating level definition" } });
}
}
}

View File

@@ -42,9 +42,9 @@ public class MembersController : ControllerBase
var member = await _mediator.Send(new GetMemberByIdQuery(id));
if (member == null)
{
return NotFound(new { message = $"Member {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Member {id} not found" } });
}
return Ok(member);
return Ok(new { success = true, data = member });
}
/// <summary>
@@ -67,9 +67,9 @@ public class MembersController : ControllerBase
var member = await _mediator.Send(new GetMemberByIdQuery(userId.Value));
if (member == null)
{
return NotFound(new { message = "Member profile not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Member profile not found" } });
}
return Ok(member);
return Ok(new { success = true, data = member });
}
/// <summary>
@@ -91,7 +91,7 @@ public class MembersController : ControllerBase
PageSize = pageSize,
SearchTerm = search
});
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -109,11 +109,11 @@ public class MembersController : ControllerBase
try
{
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = result.MemberId }, result);
return CreatedAtAction(nameof(GetById), new { id = result.MemberId }, new { success = true, data = result });
}
catch (InvalidOperationException ex) when (ex.Message.Contains("already exists"))
{
return Conflict(new { message = ex.Message });
return Conflict(new { success = false, error = new { code = "CONFLICT", message = ex.Message } });
}
}
@@ -136,11 +136,11 @@ public class MembersController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
}
@@ -163,15 +163,15 @@ public class MembersController : ControllerBase
try
{
var result = await _mediator.Send(command);
return Ok(result);
return Ok(new { success = true, data = result });
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = ex.Message } });
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
return BadRequest(new { success = false, error = new { code = "BAD_REQUEST", message = ex.Message } });
}
}
@@ -189,9 +189,9 @@ public class MembersController : ControllerBase
var progress = await _mediator.Send(new GetMemberProgressQuery(id));
if (progress == null)
{
return NotFound(new { message = $"Member {id} not found" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = $"Member {id} not found" } });
}
return Ok(progress);
return Ok(new { success = true, data = progress });
}
/// <summary>
@@ -208,7 +208,7 @@ public class MembersController : ControllerBase
[FromQuery] int pageSize = 20)
{
var result = await _mediator.Send(new GetExperienceHistoryQuery(id, pageIndex, pageSize));
return Ok(result);
return Ok(new { success = true, data = result });
}
private Guid? GetCurrentUserId()

View File

@@ -4,7 +4,6 @@ using Microsoft.Extensions.DependencyInjection;
using FacebookService.Domain.AggregatesModel.CustomerAggregate;
using FacebookService.Domain.AggregatesModel.ConversationAggregate;
using FacebookService.Domain.AggregatesModel.ChatbotAggregate;
using FacebookService.Infrastructure.Idempotency;
using FacebookService.Infrastructure.Repositories;
using FacebookService.Infrastructure.ExternalServices;
using Polly;
@@ -57,8 +56,8 @@ public static class DependencyInjection
services.AddScoped<IChatbotFlowRepository, ChatbotFlowRepository>();
services.AddScoped<IAIChatbotConfigRepository, AIChatbotConfigRepository>();
// EN: Register idempotency services / VI: Đăng ký idempotency services
services.AddScoped<IRequestManager, RequestManager>();
// EN: Idempotency services not registered — no handler uses IRequestManager
// VI: Idempotency services không được đăng ký — không có handler nào dùng IRequestManager
// EN: Register Facebook Messenger client / VI: Đăng ký Facebook Messenger client
services.AddHttpClient<IFacebookMessengerClient, FacebookMessengerClient>()

View File

@@ -40,7 +40,7 @@ public class AdminController : ControllerBase
{
var query = new GetAllRelationshipsQuery(skip, take, status, type);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -56,9 +56,9 @@ public class AdminController : ControllerBase
var result = await _mediator.Send(query);
if (result == null)
return NotFound(new { error = "Relationship not found" });
return Ok(result);
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Relationship not found" } });
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -74,9 +74,9 @@ public class AdminController : ControllerBase
var result = await _mediator.Send(command);
if (!result)
return NotFound(new { error = "Relationship not found" });
return Ok(new { success = true, message = "Relationship deleted successfully" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Relationship not found" } });
return Ok(new { success = true, data = new { message = "Relationship deleted successfully" } });
}
#endregion
@@ -95,7 +95,7 @@ public class AdminController : ControllerBase
{
var query = new GetAllBlocksQuery(skip, take);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -111,9 +111,9 @@ public class AdminController : ControllerBase
var result = await _mediator.Send(command);
if (!result)
return NotFound(new { error = "Block not found" });
return Ok(new { success = true, message = "Block deleted successfully" });
return NotFound(new { success = false, error = new { code = "NOT_FOUND", message = "Block not found" } });
return Ok(new { success = true, data = new { message = "Block deleted successfully" } });
}
#endregion
@@ -130,7 +130,7 @@ public class AdminController : ControllerBase
{
var query = new GetSocialStatisticsQuery();
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
#endregion

View File

@@ -34,7 +34,7 @@ public class BlocksController : ControllerBase
{
var command = new BlockUserCommand(request.BlockerId, request.BlockedId, request.Reason);
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetBlockedUsers), new { userId = request.BlockerId }, result);
return CreatedAtAction(nameof(GetBlockedUsers), new { userId = request.BlockerId }, new { success = true, data = result });
}
/// <summary>
@@ -48,7 +48,7 @@ public class BlocksController : ControllerBase
{
var command = new UnblockUserCommand(request.BlockerId, request.BlockedId);
var result = await _mediator.Send(command);
return Ok(new { success = result });
return Ok(new { success = true, data = new { unblocked = result } });
}
/// <summary>
@@ -61,7 +61,7 @@ public class BlocksController : ControllerBase
{
var query = new GetBlockedUsersQuery(userId, skip, take);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
}

View File

@@ -36,7 +36,7 @@ public class RelationshipsController : ControllerBase
{
var query = new GetFriendsQuery(userId, skip, take);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -50,7 +50,7 @@ public class RelationshipsController : ControllerBase
{
var command = new SendFriendRequestCommand(request.RequesterId, request.AddresseeId);
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetFriends), new { userId = request.RequesterId }, result);
return CreatedAtAction(nameof(GetFriends), new { userId = request.RequesterId }, new { success = true, data = result });
}
/// <summary>
@@ -65,7 +65,7 @@ public class RelationshipsController : ControllerBase
{
var command = new RespondToFriendRequestCommand(relationshipId, request.UserId, request.Accept);
var result = await _mediator.Send(command);
return Ok(new { success = result });
return Ok(new { success = true, data = new { accepted = result } });
}
/// <summary>
@@ -78,7 +78,7 @@ public class RelationshipsController : ControllerBase
{
var query = new GetMutualFriendsQuery(userId1, userId2);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
/// <summary>
@@ -91,7 +91,7 @@ public class RelationshipsController : ControllerBase
{
var query = new GetFriendSuggestionsQuery(userId, limit);
var result = await _mediator.Send(query);
return Ok(result);
return Ok(new { success = true, data = result });
}
#endregion
@@ -109,7 +109,7 @@ public class RelationshipsController : ControllerBase
{
var command = new FollowUserCommand(request.FollowerId, request.FolloweeId);
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetFriends), new { userId = request.FollowerId }, result);
return CreatedAtAction(nameof(GetFriends), new { userId = request.FollowerId }, new { success = true, data = result });
}
/// <summary>
@@ -123,7 +123,7 @@ public class RelationshipsController : ControllerBase
{
var command = new UnfollowUserCommand(request.FollowerId, request.FolloweeId);
var result = await _mediator.Send(command);
return Ok(new { success = result });
return Ok(new { success = true, data = new { unfollowed = result } });
}
#endregion