Files
pos-system/services/mkt-x-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f3779c4ebe docs: add SERVICE_DOCS.md for all 24 microservices from per-service code audit
Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries,
Domain Model, Database Schema, Integration Events, Dependencies, Configuration.
Generated by 23 parallel audit agents reading actual source code.

Key corrections from audit:
- inventory-service: 12 commands/6 queries (was listed as scaffold)
- promotion-service: 12 commands/10 queries (was listed as 0)
- mission-service: 4 commands/7 queries (was listed as 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:54:53 +07:00

20 KiB

MktXService (X/Twitter Marketing Integration)

Overview

  • Purpose: Microservice for integrating with X (Twitter) platform — managing Twitter accounts, conversations, contacts, campaigns, templates, automation flows, AI conversation sessions, and audience segments.
  • Port: 5000 (configured in Program.cs via ASPNETCORE_URLS)
  • Database: PostgreSQL (connection string: DefaultConnection or DATABASE_URL)
  • Architecture: Clean Architecture + CQRS (MediatR 12.4.1)
  • API Versioning: URL segment api/v{version:apiVersion} (v1.0)

API Endpoints

AccountsController (api/v1/accounts)

Method Route Description
GET /accounts?merchantId Get Twitter accounts for a merchant
GET /accounts/{id} Get Twitter account by ID
POST /accounts/connect Connect a new Twitter account
POST /accounts/{id}/disconnect Disconnect a Twitter account
POST /accounts/{id}/refresh-tokens Refresh OAuth tokens

CampaignsController (api/v1/campaigns)

Method Route Description
GET /campaigns?merchantId&status&skip&take List campaigns with filtering + pagination
GET /campaigns/{id} Get campaign details
POST /campaigns Create a new campaign
POST /campaigns/{id}/start Start a campaign
POST /campaigns/{id}/pause Pause a running campaign
POST /campaigns/{id}/resume Resume a paused campaign
POST /campaigns/{id}/cancel Cancel a campaign

ContactsController (api/v1/contacts)

Method Route Description
GET /contacts?accountId&search&tags&skip&take List contacts with search + filtering
GET /contacts/{id} Get contact details

ConversationsController (api/v1/conversations)

Method Route Description
GET /conversations?accountId&status&assignedToUserId&skip&take List conversations with filtering
GET /conversations/{id}?includeMessages Get conversation details (optionally with messages)
POST /conversations/{id}/messages Send a message in a conversation
POST /conversations/{id}/close Close a conversation
POST /conversations/{id}/reopen Reopen a closed conversation
POST /conversations/{id}/assign Assign conversation to a user
POST /conversations/{id}/pending Mark conversation as pending

TemplatesController (api/v1/templates)

Method Route Description
GET /templates?merchantId&type List templates by merchant and type
GET /templates/{id} Get template details

SamplesController (api/v1/samples)

Method Route Description
GET /samples List all samples
GET /samples/{id} Get sample by ID
POST /samples Create a sample
PUT /samples/{id} Update a sample
DELETE /samples/{id} Delete a sample
PATCH /samples/{id}/status Change sample status

WebhooksController (api/v1/webhooks)

Method Route Description
GET /webhooks/twitter Twitter CRC challenge verification
POST /webhooks/twitter Receive Twitter webhook events (DMs, follows, etc.)
POST /webhooks/twitter/register Register webhook URL with Twitter
POST /webhooks/twitter/subscribe Subscribe to Twitter webhook events

Commands (28 total)

Twitter Account Commands (3)

Command Result Description
ConnectTwitterAccountCommand(MerchantId, TwitterUserId, Username, OAuthToken, OAuthTokenSecret, DisplayName?, ProfileImageUrl?) ConnectTwitterAccountResult(Success, AccountId?, Error?) Connect a Twitter account
DisconnectTwitterAccountCommand(AccountId) bool Disconnect a Twitter account
RefreshTwitterTokensCommand(AccountId, NewOAuthToken, NewOAuthTokenSecret) bool Refresh OAuth tokens

Campaign Commands (6)

Command Result Description
CreateCampaignCommand(MerchantId, Name, Type) CreateCampaignResult(Success, CampaignId?, Error?) Create a campaign (types: bulk_dm, welcome, follow_up, drip, broadcast)
StartCampaignCommand(CampaignId) bool Start a campaign
PauseCampaignCommand(CampaignId) bool Pause a running campaign
ResumeCampaignCommand(CampaignId) bool Resume a paused campaign
CancelCampaignCommand(CampaignId) bool Cancel a campaign
UpdateCampaignMetricsCommand(CampaignId, Sent, Delivered, Read, Clicked, Failed) bool Update campaign metrics

Conversation Commands (5)

Command Result Description
SendMessageCommand(ConversationId, Content, MediaUrls?) bool Send a message (max 4 media)
CloseConversationCommand(ConversationId, Reason?) bool Close a conversation (reason max 500 chars)
ReopenConversationCommand(ConversationId) bool Reopen a conversation
AssignConversationCommand(ConversationId, UserId) bool Assign to a user
MarkConversationPendingCommand(ConversationId) bool Mark as pending

Contact Commands (5)

Command Result Description
CreateContactCommand(AccountId, TwitterUserId, Username, Source, DisplayName?, ProfileImageUrl?, Attributes?, Tags?) CreateContactResult(Success, ContactId?, Error?) Create a contact
UpdateContactCommand(ContactId, Username, DisplayName?, ProfileImageUrl?) bool Update contact profile
AddContactTagCommand(ContactId, TagName) bool Add tag to contact
RemoveContactTagCommand(ContactId, TagName) bool Remove tag from contact
UpdateContactAttributesCommand(ContactId, Attributes) bool Update custom attributes

Template Commands (4)

Command Result Description
CreateTemplateCommand(MerchantId, Name, Type, Content) CreateTemplateResult(Success, TemplateId?, Error?) Create a template
UpdateTemplateCommand(TemplateId, Name, Content) bool Update a template
DeleteTemplateCommand(TemplateId) bool Delete a template
PreviewTemplateCommand(TemplateId, Variables) PreviewTemplateResult(Success, RenderedContent?, Error?) Preview template rendering

Sample Commands (4)

Command Result Description
CreateSampleCommand(Name, Description?) CreateSampleResult(Success, SampleId?, Error?) Create a sample
UpdateSampleCommand(Id, Name, Description?) bool Update a sample
DeleteSampleCommand(Id) bool Delete a sample
ChangeSampleStatusCommand(Id, NewStatus) bool Change status (Draft->Active->Completed, Draft/Active->Cancelled)

Queries

Twitter Account Queries

Query Result
GetTwitterAccountsQuery(MerchantId) List<TwitterAccountDto>
GetTwitterAccountByIdQuery(AccountId) TwitterAccountDto?

Campaign Queries

Query Result
GetCampaignsQuery(MerchantId, Status?, Skip, Take) CampaignsResult(Items, TotalCount)
GetCampaignByIdQuery(CampaignId) CampaignDetailDto?

Conversation Queries

Query Result
GetConversationsQuery(AccountId, Status?, AssignedToUserId?, Skip, Take) ConversationsResult(Items, TotalCount)
GetConversationByIdQuery(ConversationId, IncludeMessages) ConversationDetailDto?

Contact Queries

Query Result
GetContactsQuery(AccountId, Search?, Tags?, Skip, Take) ContactsResult(Items, TotalCount)
GetContactByIdQuery(ContactId) ContactDetailDto?

Template Queries

Query Result
GetTemplatesQuery(MerchantId, Type?) List<TemplateDto>
GetTemplateByIdQuery(TemplateId) TemplateDetailDto?

Sample Queries

Query Result
GetSamplesQuery List<SampleDto>
GetSampleByIdQuery(Id) SampleDto?

DTOs

  • TwitterAccountDto: Id, MerchantId, TwitterUserId, Username, DisplayName, ProfileImageUrl, Status, ConnectedAt
  • CampaignDto: Id, MerchantId, Name, Type, Status, CreatedAt, UpdatedAt
  • CampaignDetailDto: CampaignDto + TemplateId, SegmentIds, Schedule (StartAt, EndAt, TimeZone), Metrics (Sent, Delivered, Read, Clicked, Failed)
  • ConversationDto: Id, AccountId, ContactId, Status, LastMessagePreview, LastMessageAt, AssignedToUserId
  • ConversationDetailDto: ConversationDto + Messages (list of MessageDto)
  • MessageDto: Id, Content, Direction, Type, SentAt, IsFromBot, TwitterMessageId
  • ContactDto: Id, TwitterUserId, Username, DisplayName, ProfileImageUrl, Tags, LastInteraction
  • ContactDetailDto: ContactDto + Attributes, Source, CreatedAt
  • TemplateDto: Id, Name, Type, CreatedAt
  • TemplateDetailDto: TemplateDto + Content, Variables (extracted from {{var}} placeholders)

Domain Model

Aggregates

TwitterAccount (Aggregate Root)

  • Properties: MerchantId, TwitterUserId, Username, DisplayName, ProfileImageUrl, StatusId (TwitterAccountStatus), OAuthToken, OAuthTokenSecret, WebhookId, Settings (Dictionary), ConnectedAt, CreatedAt, UpdatedAt
  • Methods: Activate(), SetWebhookId(), UpdateCredentials(), UpdateProfile(), UpdateSettings(), MarkAsError(reason), Deactivate(), Disconnect()
  • Domain Events: TwitterAccountConnectedDomainEvent, TwitterAccountStatusChangedDomainEvent, TwitterAccountDisconnectedDomainEvent

TwitterAccountStatus (Enumeration)

  • Pending(1), Active(2), Inactive(3), Error(4), Disconnected(5)

Campaign (Aggregate Root)

  • Properties: MerchantId, Name, Type, StatusId (CampaignStatus), TemplateId, SegmentIds (List - JSONB), Schedule (CampaignSchedule value object), Metrics (CampaignMetrics value object), CreatedAt, UpdatedAt
  • Value Objects:
    • CampaignSchedule: StartAt, EndAt, TimeZone
    • CampaignMetrics: Sent, Delivered, Read, Clicked, Failed
  • Methods: SetTemplate(), AddSegment(), RemoveSegment(), SetSchedule(), Start(), Pause(), Resume(), Complete(), Cancel(), UpdateMetrics()
  • Domain Events: CampaignStartedDomainEvent, CampaignCompletedDomainEvent

CampaignStatus (Enumeration)

  • Draft(1), Scheduled(2), Running(3), Paused(4), Completed(5), Cancelled(6)

Conversation (Aggregate Root)

  • Properties: AccountId, ContactId, Status (ConversationStatus), AssignedToUserId, StartedAt, EndedAt, LastMessagePreview, LastMessageAt, CreatedAt, UpdatedAt
  • Child Entity: Message — Direction (inbound/outbound), Type (text/image/card/quick_reply), Content, Attachments (MessageAttachment value object with Url, Type), IsFromBot, TwitterMessageId, SentAt
  • Methods: AddMessage(), Close(reason?), Reopen(), AssignTo(userId), Unassign(), MarkAsPending()
  • Domain Events: ConversationStartedDomainEvent, MessageReceivedDomainEvent, MessageSentDomainEvent

ConversationStatus (Enumeration)

  • Open(1), Closed(2), Pending(3)

Contact (Aggregate Root)

  • Properties: AccountId, TwitterUserId, Username, DisplayName, ProfileImageUrl, Source, Attributes (Dictionary<string,object>), CreatedAt, UpdatedAt, LastInteractionAt
  • Child Entity: ContactTag — Name (normalized lowercase), CreatedAt
  • Methods: UpdateProfile(), AddTag(), RemoveTag(), SetAttribute(), RemoveAttribute(), UpdateAttributes(), RecordInteraction()
  • Domain Events: ContactCreatedDomainEvent, ContactTaggedDomainEvent

Template (Aggregate Root)

  • Properties: MerchantId, Name, Type, Content, Variables (extracted from {{variable}} via regex), CreatedAt, UpdatedAt
  • Constants: MaxContentLength = 10000
  • Methods: Update(name, content), Render(variables), ValidateVariables(variables), GetMissingVariables(variables)

Sample (Aggregate Root — template/demo)

  • Properties: Name, Description, StatusId (SampleStatus), CreatedAt, UpdatedAt
  • Status Transitions: Draft->Active->Completed, Draft/Active->Cancelled
  • Methods: Activate(), Complete(), Cancel()
  • Domain Events: SampleCreatedDomainEvent, SampleStatusChangedDomainEvent

Segment (Aggregate Root)

  • Properties: AccountId, Name, Description, CreatedAt, UpdatedAt
  • Child Entity: SegmentCondition — Field, Operator, Value

AIConversationSession (Aggregate Root)

  • Properties: ConversationId, Intent, Slots (Dictionary), ContextItems (List), IsActive, CreatedAt, UpdatedAt, ExpiresAt
  • Constants: MaxContextItems = 10, EscalationThreshold = 0.6
  • Methods: AddContext(), ClearContext(), SetIntent(), SetSlot(), Deactivate(), Reactivate(), IsExpired()
  • Domain Events: AIEscalationRequestedDomainEvent, IntentDetectedDomainEvent

AutomationFlow (Aggregate Root)

  • Properties: AccountId, Name, Description, StatusId (FlowStatus), CreatedAt, UpdatedAt
  • Child Entities: FlowTrigger, FlowNode, FlowConnection
  • FlowStatus: Draft(1), Active(2), Inactive(3)
  • Methods: AddNode(), RemoveNode(), Connect(), Activate(), Deactivate()

Database Schema

DbContext: MktXServiceContext (17 DbSets)

  • Implements IUnitOfWork, dispatches domain events before SaveChanges
  • Transaction support: BeginTransactionAsync, CommitTransactionAsync, RollbackTransaction

Tables

twitter_accounts

Column Type Constraints
id uuid PK
merchant_id uuid NOT NULL, indexed
twitter_user_id varchar NOT NULL, unique index
username varchar
display_name varchar
profile_image_url varchar
status_id int FK -> twitter_account_statuses, indexed
oauth_token varchar
oauth_token_secret varchar
webhook_id varchar
settings jsonb
connected_at timestamp
created_at timestamp NOT NULL
updated_at timestamp NOT NULL

Indexes: ix_twitter_accounts_twitter_user_id (unique), ix_twitter_accounts_merchant_id, ix_twitter_accounts_status_id

twitter_account_statuses

Column Type
id int
name varchar

Seed data: 1=Pending, 2=Active, 3=Inactive, 4=Error, 5=Disconnected

campaigns

Column Type Constraints
id uuid PK
merchant_id uuid NOT NULL, indexed
name varchar NOT NULL
type varchar NOT NULL
status_id int FK -> campaign_statuses, indexed
template_id uuid nullable
segment_ids jsonb List
schedule jsonb CampaignSchedule value object
metrics jsonb CampaignMetrics value object
created_at timestamp NOT NULL, desc index
updated_at timestamp NOT NULL

Indexes: ix_campaigns_merchant_id, ix_campaigns_status_id, ix_campaigns_created_at (desc), ix_campaigns_merchant_status (composite)

campaign_statuses

Column Type
id int
name varchar

Seed data: 1=Draft, 2=Scheduled, 3=Running, 4=Paused, 5=Completed, 6=Cancelled

conversations, messages, contacts, contact_tags, templates, samples, segments, segment_conditions, ai_conversation_sessions, automation_flows, flow_triggers, flow_nodes, flow_connections

(Configured via EF Core FluentAPI — snake_case columns, private field mapping)


Domain Events (14 total)

Event Payload
SampleCreatedDomainEvent Sample entity
SampleStatusChangedDomainEvent Sample entity
TwitterAccountConnectedDomainEvent TwitterAccount entity
TwitterAccountDisconnectedDomainEvent TwitterAccount entity
TwitterAccountStatusChangedDomainEvent TwitterAccount entity
CampaignStartedDomainEvent Campaign entity
CampaignCompletedDomainEvent Campaign entity
ConversationStartedDomainEvent Conversation entity
MessageReceivedDomainEvent Conversation entity
MessageSentDomainEvent Conversation entity
ContactCreatedDomainEvent Contact entity
ContactTaggedDomainEvent Contact entity, TagName
AIEscalationRequestedDomainEvent AIConversationSession entity
IntentDetectedDomainEvent AIConversationSession entity

Validators

Validator Rules
CreateCampaignCommandValidator MerchantId required; Name required, max 255; Type in [bulk_dm, welcome, follow_up, drip, broadcast]
StartCampaignCommandValidator CampaignId required
PauseCampaignCommandValidator CampaignId required
ResumeCampaignCommandValidator CampaignId required
CancelCampaignCommandValidator CampaignId required
UpdateCampaignMetricsCommandValidator CampaignId required; all metric fields >= 0
ConnectTwitterAccountCommandValidator MerchantId required; TwitterUserId required; Username required; OAuthToken required; OAuthTokenSecret required
DisconnectTwitterAccountCommandValidator AccountId required
RefreshTwitterTokensCommandValidator AccountId required; NewOAuthToken required; NewOAuthTokenSecret required
SendMessageCommandValidator ConversationId required; Content required, max 10000; MediaUrls max 4 items
CloseConversationCommandValidator ConversationId required; Reason max 500 chars
AssignConversationCommandValidator ConversationId required; UserId required
ReopenConversationCommandValidator ConversationId required
MarkConversationPendingCommandValidator ConversationId required
CreateSampleCommandValidator Name required, max 200
UpdateSampleCommandValidator Id required; Name required, max 200

External Services

Twitter API Client (ITwitterApiClient / TwitterApiClient)

  • Authentication: OAuth 1.0a HMAC-SHA1 signing
  • Resilience: Polly retry (3 attempts, exponential backoff) + circuit breaker (5 failures, 1 min break)
  • Methods: SendDirectMessage, GetDirectMessages, GetUserById, GetUserByUsername, RegisterWebhook, SubscribeWebhook, DeleteWebhook, UploadMedia, GetRateLimitStatus
  • Config: TwitterApiOptions (ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, EnvironmentName)

OpenAI Service Client (IAIServiceClient / OpenAIServiceClient)

  • Resilience: Circuit breaker
  • Methods: GetChatCompletionAsync, GetChatCompletionWithFunctionsAsync, StreamChatCompletionAsync, DetectIntentAsync, ExtractEntitiesAsync, GetEmbeddingsAsync, ModerateContentAsync
  • Config: OpenAIOptions (ApiKey, DefaultModel=gpt-4o-mini, EmbeddingModel=text-embedding-3-small, MaxTokens, DefaultTemperature)

Dependencies (NuGet)

API Project

  • MediatR 12.4.1
  • FluentValidation.DependencyInjectionExtensions 11.11.0
  • Swashbuckle.AspNetCore 7.2.0
  • Asp.Versioning.Mvc 8.1.0
  • Asp.Versioning.Mvc.ApiExplorer 8.1.0
  • Hellang.Middleware.ProblemDetails 6.5.1
  • Serilog.AspNetCore 8.0.3
  • AspNetCore.HealthChecks.NpgSql 8.0.2

Infrastructure Project

  • Microsoft.EntityFrameworkCore 10.0.0
  • Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
  • MediatR 12.4.1
  • Dapper 2.1.35
  • Polly 8.5.0
  • Polly.Extensions.Http 3.0.0
  • StackExchange.Redis 2.8.16
  • Quartz 3.13.1

Configuration

Environment Variables / Settings

  • ConnectionStrings:DefaultConnection or DATABASE_URL — PostgreSQL connection
  • TwitterApi:ConsumerKey — Twitter API consumer key
  • TwitterApi:ConsumerSecret — Twitter API consumer secret
  • TwitterApi:AccessToken — Twitter API access token
  • TwitterApi:AccessTokenSecret — Twitter API access token secret
  • TwitterApi:EnvironmentName — Twitter webhook environment
  • OpenAI:ApiKey — OpenAI API key
  • OpenAI:DefaultModel — Default model (gpt-4o-mini)
  • OpenAI:EmbeddingModel — Embedding model (text-embedding-3-small)

MediatR Pipeline Behaviors

  1. LoggingBehavior — Request/response logging with Stopwatch
  2. ValidatorBehavior — FluentValidation in pipeline
  3. TransactionBehavior — Auto transaction for Commands (skips Queries)

Health Checks

  • PostgreSQL health check via NpgSql

Known Issues

  • Incomplete DI Registration: Only ISampleRepository is registered in DependencyInjection.cs. The other 8 repository interfaces (ITwitterAccountRepository, ICampaignRepository, IConversationRepository, IContactRepository, ITemplateRepository, ISegmentRepository, IAIConversationSessionRepository, IAutomationFlowRepository) are NOT registered. This means most functionality beyond Samples will fail at runtime until DI is fixed.