feat: Introduce MktXService with API controllers for campaigns, contacts, conversations, templates, and webhooks, and add chatbot rule deletion and status toggling to MktZaloService.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MktXService.API.Application.Commands;
|
||||
using MktXService.API.Application.Queries;
|
||||
using MktXService.Domain.AggregatesModel.CampaignAggregate;
|
||||
|
||||
namespace MktXService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for Campaign management.
|
||||
/// VI: Controller quản lý chiến dịch.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class CampaignsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<CampaignsController> _logger;
|
||||
|
||||
public CampaignsController(IMediator mediator, ILogger<CampaignsController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all campaigns for a merchant.
|
||||
/// VI: Lấy tất cả chiến dịch của merchant.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<CampaignDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetCampaigns(
|
||||
[FromQuery] Guid merchantId,
|
||||
[FromQuery] string? status = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20)
|
||||
{
|
||||
var campaigns = await _mediator.Send(new GetCampaignsQuery(merchantId, status, skip, take));
|
||||
return Ok(new { success = true, data = campaigns });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get a campaign by ID.
|
||||
/// VI: Lấy chiến dịch theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(CampaignDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetCampaign(Guid id)
|
||||
{
|
||||
var campaign = await _mediator.Send(new GetCampaignQuery(id));
|
||||
if (campaign == null)
|
||||
return NotFound(new { success = false, error = new { code = "CAMPAIGN_NOT_FOUND", message = $"Campaign {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, data = campaign });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Create a new campaign.
|
||||
/// VI: Tạo chiến dịch mới.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(CreateCampaignResult), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateCampaign([FromBody] CreateCampaignRequest request)
|
||||
{
|
||||
CampaignSchedule? schedule = null;
|
||||
if (request.Schedule != null)
|
||||
schedule = new CampaignSchedule(request.Schedule.StartAt, request.Schedule.EndAt, request.Schedule.Recurrence);
|
||||
|
||||
var command = new CreateCampaignCommand(
|
||||
request.MerchantId,
|
||||
request.Name,
|
||||
request.Type,
|
||||
request.TemplateId,
|
||||
request.SegmentIds,
|
||||
schedule);
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
return BadRequest(new { success = false, error = new { code = "CREATE_FAILED", message = result.Error } });
|
||||
|
||||
return CreatedAtAction(nameof(GetCampaign), new { id = result.CampaignId },
|
||||
new { success = true, data = result });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Start a campaign.
|
||||
/// VI: Bắt đầu chiến dịch.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/start")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> StartCampaign(Guid id)
|
||||
{
|
||||
var result = await _mediator.Send(new StartCampaignCommand(id));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "START_FAILED", message = "Failed to start campaign" } });
|
||||
|
||||
return Ok(new { success = true, message = "Campaign started successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Pause a running campaign.
|
||||
/// VI: Tạm dừng chiến dịch đang chạy.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/pause")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> PauseCampaign(Guid id)
|
||||
{
|
||||
var result = await _mediator.Send(new PauseCampaignCommand(id));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "PAUSE_FAILED", message = "Failed to pause campaign" } });
|
||||
|
||||
return Ok(new { success = true, message = "Campaign paused successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Resume a paused campaign.
|
||||
/// VI: Tiếp tục chiến dịch đã tạm dừng.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/resume")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ResumeCampaign(Guid id)
|
||||
{
|
||||
var result = await _mediator.Send(new ResumeCampaignCommand(id));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "RESUME_FAILED", message = "Failed to resume campaign" } });
|
||||
|
||||
return Ok(new { success = true, message = "Campaign resumed successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Cancel a campaign.
|
||||
/// VI: Hủy chiến dịch.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/cancel")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CancelCampaign(Guid id, [FromBody] CancelCampaignRequest? request = null)
|
||||
{
|
||||
var result = await _mediator.Send(new CancelCampaignCommand(id, request?.Reason));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "CANCEL_FAILED", message = "Failed to cancel campaign" } });
|
||||
|
||||
return Ok(new { success = true, message = "Campaign cancelled successfully" });
|
||||
}
|
||||
}
|
||||
|
||||
#region Request Models
|
||||
|
||||
public record CreateCampaignRequest(
|
||||
Guid MerchantId,
|
||||
string Name,
|
||||
string Type,
|
||||
Guid? TemplateId,
|
||||
List<Guid> SegmentIds,
|
||||
CampaignScheduleRequest? Schedule = null);
|
||||
|
||||
public record CampaignScheduleRequest(DateTime? StartAt, DateTime? EndAt, string? Recurrence);
|
||||
|
||||
public record CancelCampaignRequest(string? Reason);
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,59 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MktXService.API.Application.Queries;
|
||||
|
||||
namespace MktXService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for Contact management.
|
||||
/// VI: Controller quản lý liên hệ.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class ContactsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<ContactsController> _logger;
|
||||
|
||||
public ContactsController(IMediator mediator, ILogger<ContactsController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all contacts for an account.
|
||||
/// VI: Lấy tất cả liên hệ của tài khoản.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ContactDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetContacts(
|
||||
[FromQuery] Guid accountId,
|
||||
[FromQuery] string? search = null,
|
||||
[FromQuery] List<string>? tags = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20)
|
||||
{
|
||||
var contacts = await _mediator.Send(new GetContactsQuery(accountId, search, tags, skip, take));
|
||||
return Ok(new { success = true, data = contacts });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get a contact by ID.
|
||||
/// VI: Lấy liên hệ theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(ContactDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetContact(Guid id)
|
||||
{
|
||||
var contact = await _mediator.Send(new GetContactQuery(id));
|
||||
if (contact == null)
|
||||
return NotFound(new { success = false, error = new { code = "CONTACT_NOT_FOUND", message = $"Contact {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, data = contact });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MktXService.API.Application.Commands;
|
||||
using MktXService.API.Application.Queries;
|
||||
|
||||
namespace MktXService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for Conversation management.
|
||||
/// VI: Controller quản lý hội thoại.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class ConversationsController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<ConversationsController> _logger;
|
||||
|
||||
public ConversationsController(IMediator mediator, ILogger<ConversationsController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all conversations for an account.
|
||||
/// VI: Lấy tất cả hội thoại của tài khoản.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ConversationDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetConversations(
|
||||
[FromQuery] Guid accountId,
|
||||
[FromQuery] string? status = null,
|
||||
[FromQuery] Guid? assignedToUserId = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 20)
|
||||
{
|
||||
var conversations = await _mediator.Send(
|
||||
new GetConversationsQuery(accountId, status, assignedToUserId, skip, take));
|
||||
return Ok(new { success = true, data = conversations });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get a conversation by ID with messages.
|
||||
/// VI: Lấy hội thoại theo ID với tin nhắn.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(ConversationDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetConversation(Guid id, [FromQuery] bool includeMessages = true)
|
||||
{
|
||||
var conversation = await _mediator.Send(new GetConversationQuery(id, includeMessages));
|
||||
if (conversation == null)
|
||||
return NotFound(new { success = false, error = new { code = "CONVERSATION_NOT_FOUND", message = $"Conversation {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, data = conversation });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Send a message in a conversation.
|
||||
/// VI: Gửi tin nhắn trong hội thoại.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/messages")]
|
||||
[ProducesResponseType(typeof(SendMessageResult), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> SendMessage(Guid id, [FromBody] SendMessageRequest request)
|
||||
{
|
||||
var command = new SendMessageCommand(id, request.Content, request.IsFromBot, request.MediaUrls);
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
return BadRequest(new { success = false, error = new { code = "SEND_FAILED", message = result.Error } });
|
||||
|
||||
return Created($"/api/v1/conversations/{id}/messages/{result.MessageId}",
|
||||
new { success = true, data = result });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Close a conversation.
|
||||
/// VI: Đóng hội thoại.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/close")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> CloseConversation(Guid id, [FromBody] CloseConversationRequest? request = null)
|
||||
{
|
||||
var result = await _mediator.Send(new CloseConversationCommand(id, request?.Reason));
|
||||
if (!result)
|
||||
return NotFound(new { success = false, error = new { code = "CONVERSATION_NOT_FOUND", message = $"Conversation {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, message = "Conversation closed successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Reopen a closed conversation.
|
||||
/// VI: Mở lại hội thoại đã đóng.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/reopen")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> ReopenConversation(Guid id)
|
||||
{
|
||||
var result = await _mediator.Send(new ReopenConversationCommand(id));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "REOPEN_FAILED", message = "Failed to reopen conversation" } });
|
||||
|
||||
return Ok(new { success = true, message = "Conversation reopened successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Assign conversation to an agent.
|
||||
/// VI: Gán hội thoại cho agent.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/assign")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> AssignConversation(Guid id, [FromBody] AssignConversationRequest request)
|
||||
{
|
||||
var result = await _mediator.Send(new AssignConversationCommand(id, request.UserId));
|
||||
if (!result)
|
||||
return NotFound(new { success = false, error = new { code = "CONVERSATION_NOT_FOUND", message = $"Conversation {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, message = "Conversation assigned successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Mark conversation as pending.
|
||||
/// VI: Đánh dấu hội thoại đang chờ.
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/pending")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> MarkAsPending(Guid id)
|
||||
{
|
||||
var result = await _mediator.Send(new MarkConversationPendingCommand(id));
|
||||
if (!result)
|
||||
return BadRequest(new { success = false, error = new { code = "MARK_PENDING_FAILED", message = "Failed to mark conversation as pending" } });
|
||||
|
||||
return Ok(new { success = true, message = "Conversation marked as pending" });
|
||||
}
|
||||
}
|
||||
|
||||
#region Request Models
|
||||
|
||||
public record SendMessageRequest(string Content, bool IsFromBot = false, List<string>? MediaUrls = null);
|
||||
public record CloseConversationRequest(string? Reason);
|
||||
public record AssignConversationRequest(Guid UserId);
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,56 @@
|
||||
using Asp.Versioning;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MktXService.API.Application.Queries;
|
||||
|
||||
namespace MktXService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for Template management.
|
||||
/// VI: Controller quản lý template.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class TemplatesController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger<TemplatesController> _logger;
|
||||
|
||||
public TemplatesController(IMediator mediator, ILogger<TemplatesController> logger)
|
||||
{
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all templates for a merchant.
|
||||
/// VI: Lấy tất cả template của merchant.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<TemplateDto>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetTemplates(
|
||||
[FromQuery] Guid merchantId,
|
||||
[FromQuery] string? type = null)
|
||||
{
|
||||
var templates = await _mediator.Send(new GetTemplatesQuery(merchantId, type));
|
||||
return Ok(new { success = true, data = templates });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get a template by ID.
|
||||
/// VI: Lấy template theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(TemplateDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetTemplate(Guid id)
|
||||
{
|
||||
var template = await _mediator.Send(new GetTemplateQuery(id));
|
||||
if (template == null)
|
||||
return NotFound(new { success = false, error = new { code = "TEMPLATE_NOT_FOUND", message = $"Template {id} not found" } });
|
||||
|
||||
return Ok(new { success = true, data = template });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MktXService.Infrastructure.ExternalServices.Twitter;
|
||||
|
||||
namespace MktXService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Controller for Twitter Webhook handling.
|
||||
/// VI: Controller xử lý Twitter Webhook.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("api/v{version:apiVersion}/webhooks/twitter")]
|
||||
[Produces("application/json")]
|
||||
public class WebhooksController : ControllerBase
|
||||
{
|
||||
private readonly ITwitterApiClient _twitterClient;
|
||||
private readonly TwitterApiOptions _options;
|
||||
private readonly ILogger<WebhooksController> _logger;
|
||||
|
||||
public WebhooksController(
|
||||
ITwitterApiClient twitterClient,
|
||||
IOptions<TwitterApiOptions> options,
|
||||
ILogger<WebhooksController> logger)
|
||||
{
|
||||
_twitterClient = twitterClient;
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: CRC token verification for Twitter webhook.
|
||||
/// VI: Xác thực CRC token cho Twitter webhook.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public IActionResult VerifyCrcToken([FromQuery(Name = "crc_token")] string crcToken)
|
||||
{
|
||||
_logger.LogInformation("Received CRC challenge: {Token}", crcToken);
|
||||
|
||||
var response = _twitterClient.GenerateCrcResponse(crcToken);
|
||||
|
||||
return Ok(new { response_token = response });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Receive Twitter webhook events.
|
||||
/// VI: Nhận events từ Twitter webhook.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> ReceiveWebhookEvent([FromBody] TwitterWebhookPayload payload)
|
||||
{
|
||||
_logger.LogInformation("Received webhook event for user: {UserId}", payload.ForUserId);
|
||||
|
||||
// EN: Process Direct Message events
|
||||
// VI: Xử lý events Direct Message
|
||||
if (payload.DirectMessageEvents?.Any() == true)
|
||||
{
|
||||
foreach (var dmEvent in payload.DirectMessageEvents)
|
||||
{
|
||||
_logger.LogInformation("Received DM from {SenderId}: {MessageId}",
|
||||
dmEvent.MessageCreate?.SenderId, dmEvent.Id);
|
||||
|
||||
// EN: In production, queue for async processing
|
||||
// VI: Trong production, đưa vào hàng đợi để xử lý bất đồng bộ
|
||||
}
|
||||
}
|
||||
|
||||
// EN: Process follow events
|
||||
// VI: Xử lý events follow
|
||||
if (payload.FollowEvents?.Any() == true)
|
||||
{
|
||||
foreach (var followEvent in payload.FollowEvents)
|
||||
{
|
||||
_logger.LogInformation("New follower: {FollowerId}",
|
||||
followEvent.Source?.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Register webhook for an account.
|
||||
/// VI: Đăng ký webhook cho tài khoản.
|
||||
/// </summary>
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> RegisterWebhook([FromBody] RegisterWebhookRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var webhookId = await _twitterClient.RegisterWebhookAsync(request.WebhookUrl);
|
||||
return Ok(new { success = true, webhookId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to register webhook");
|
||||
return BadRequest(new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Subscribe to webhook events.
|
||||
/// VI: Đăng ký nhận webhook events.
|
||||
/// </summary>
|
||||
[HttpPost("subscribe")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> SubscribeToWebhook()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _twitterClient.SubscribeToWebhookAsync();
|
||||
return Ok(new { success = true, message = "Subscribed to webhook events" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to subscribe to webhook");
|
||||
return BadRequest(new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Webhook DTOs
|
||||
|
||||
public class TwitterWebhookPayload
|
||||
{
|
||||
public string? ForUserId { get; set; }
|
||||
public List<DirectMessageEvent>? DirectMessageEvents { get; set; }
|
||||
public List<FollowEvent>? FollowEvents { get; set; }
|
||||
public Dictionary<string, TwitterUserData>? Users { get; set; }
|
||||
}
|
||||
|
||||
public class DirectMessageEvent
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? CreatedTimestamp { get; set; }
|
||||
public MessageCreate? MessageCreate { get; set; }
|
||||
}
|
||||
|
||||
public class MessageCreate
|
||||
{
|
||||
public string? SenderId { get; set; }
|
||||
public string? Target { get; set; }
|
||||
public MessageData? MessageData { get; set; }
|
||||
}
|
||||
|
||||
public class MessageData
|
||||
{
|
||||
public string? Text { get; set; }
|
||||
public List<object>? Entities { get; set; }
|
||||
}
|
||||
|
||||
public class FollowEvent
|
||||
{
|
||||
public string? Type { get; set; }
|
||||
public string? CreatedTimestamp { get; set; }
|
||||
public TwitterUserData? Source { get; set; }
|
||||
public TwitterUserData? Target { get; set; }
|
||||
}
|
||||
|
||||
public class TwitterUserData
|
||||
{
|
||||
public string? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? ScreenName { get; set; }
|
||||
}
|
||||
|
||||
public record RegisterWebhookRequest(string WebhookUrl);
|
||||
|
||||
#endregion
|
||||
@@ -40,13 +40,11 @@ public class UpdateCustomerCommandHandler : IRequestHandler<UpdateCustomerComman
|
||||
!string.IsNullOrEmpty(request.PhoneNumber) ||
|
||||
!string.IsNullOrEmpty(request.Email))
|
||||
{
|
||||
var newProfile = new CustomerProfile(
|
||||
customer.UpdateProfile(
|
||||
request.DisplayName ?? customer.Profile.DisplayName,
|
||||
request.AvatarUrl ?? customer.Profile.AvatarUrl,
|
||||
request.PhoneNumber ?? customer.Profile.PhoneNumber,
|
||||
request.Email ?? customer.Profile.Email);
|
||||
|
||||
customer.UpdateProfile(newProfile);
|
||||
}
|
||||
|
||||
// EN: Add tags / VI: Thêm tags
|
||||
|
||||
@@ -72,6 +72,47 @@ public class ChatbotRulesController : ControllerBase
|
||||
|
||||
return CreatedAtAction(nameof(GetRules), new { id = result.RuleId }, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Delete a chatbot rule.
|
||||
/// VI: Xóa quy tắc chatbot.
|
||||
/// </summary>
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> DeleteRule(
|
||||
Guid id,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var command = new DeleteChatbotRuleCommand(id);
|
||||
var result = await _mediator.Send(command, ct);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return NotFound(new { error = result.Error });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Toggle chatbot rule active status.
|
||||
/// VI: Bật/tắt trạng thái hoạt động quy tắc chatbot.
|
||||
/// </summary>
|
||||
[HttpPatch("{id:guid}/toggle")]
|
||||
public async Task<IActionResult> ToggleRule(
|
||||
Guid id,
|
||||
[FromQuery] bool activate,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var command = new ToggleChatbotRuleCommand(id, activate);
|
||||
var result = await _mediator.Send(command, ct);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(new { error = result.Error });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user