.
This commit is contained in:
@@ -1,22 +1,127 @@
|
||||
package kz.konturai.parser.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "facebook.leads.call-center")
|
||||
@Data
|
||||
public class FacebookLeadCallCenterProperties {
|
||||
|
||||
private boolean enabled = false;
|
||||
private String baseUrl = "http://localhost:8080";
|
||||
private String customerPath = "/proxy/customer/customers";
|
||||
private String interactionPath = "/proxy/interaction/interactions";
|
||||
private String authLoginPath = "/proxy/auth/auth/login";
|
||||
private String authUsername = "admin";
|
||||
private String authPassword = "admin123";
|
||||
private String actorUser = "facebook-lead-bot";
|
||||
private String actorRole = "admin";
|
||||
private String interactionChannel = "webchat";
|
||||
private String interactionQueueId = "q_main";
|
||||
private int timeoutMs = 30000;
|
||||
private String subjectPrefix = "[Facebook]";
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String getCustomerPath() {
|
||||
return customerPath;
|
||||
}
|
||||
|
||||
public void setCustomerPath(String customerPath) {
|
||||
this.customerPath = customerPath;
|
||||
}
|
||||
|
||||
public String getInteractionPath() {
|
||||
return interactionPath;
|
||||
}
|
||||
|
||||
public void setInteractionPath(String interactionPath) {
|
||||
this.interactionPath = interactionPath;
|
||||
}
|
||||
|
||||
public String getAuthLoginPath() {
|
||||
return authLoginPath;
|
||||
}
|
||||
|
||||
public void setAuthLoginPath(String authLoginPath) {
|
||||
this.authLoginPath = authLoginPath;
|
||||
}
|
||||
|
||||
public String getAuthUsername() {
|
||||
return authUsername;
|
||||
}
|
||||
|
||||
public void setAuthUsername(String authUsername) {
|
||||
this.authUsername = authUsername;
|
||||
}
|
||||
|
||||
public String getAuthPassword() {
|
||||
return authPassword;
|
||||
}
|
||||
|
||||
public void setAuthPassword(String authPassword) {
|
||||
this.authPassword = authPassword;
|
||||
}
|
||||
|
||||
public String getActorUser() {
|
||||
return actorUser;
|
||||
}
|
||||
|
||||
public void setActorUser(String actorUser) {
|
||||
this.actorUser = actorUser;
|
||||
}
|
||||
|
||||
public String getActorRole() {
|
||||
return actorRole;
|
||||
}
|
||||
|
||||
public void setActorRole(String actorRole) {
|
||||
this.actorRole = actorRole;
|
||||
}
|
||||
|
||||
public String getInteractionChannel() {
|
||||
return interactionChannel;
|
||||
}
|
||||
|
||||
public void setInteractionChannel(String interactionChannel) {
|
||||
this.interactionChannel = interactionChannel;
|
||||
}
|
||||
|
||||
public String getInteractionQueueId() {
|
||||
return interactionQueueId;
|
||||
}
|
||||
|
||||
public void setInteractionQueueId(String interactionQueueId) {
|
||||
this.interactionQueueId = interactionQueueId;
|
||||
}
|
||||
|
||||
public int getTimeoutMs() {
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
public void setTimeoutMs(int timeoutMs) {
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
public String getSubjectPrefix() {
|
||||
return subjectPrefix;
|
||||
}
|
||||
|
||||
public void setSubjectPrefix(String subjectPrefix) {
|
||||
this.subjectPrefix = subjectPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import kz.konturai.parser.config.FacebookLeadCallCenterProperties;
|
||||
import kz.konturai.parser.model.FacebookLead;
|
||||
import kz.konturai.parser.repository.FacebookLeadRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -18,16 +18,24 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class FacebookLeadCallCenterSyncService {
|
||||
|
||||
private static final List<String> RETRYABLE_SYNC_STATUSES = List.of("PENDING", "FAILED");
|
||||
private static final Logger log = LoggerFactory.getLogger(FacebookLeadCallCenterSyncService.class);
|
||||
|
||||
private final FacebookLeadCallCenterProperties properties;
|
||||
private final FacebookLeadRepository facebookLeadRepository;
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
|
||||
public FacebookLeadCallCenterSyncService(
|
||||
FacebookLeadCallCenterProperties properties,
|
||||
FacebookLeadRepository facebookLeadRepository,
|
||||
WebClient.Builder webClientBuilder) {
|
||||
this.properties = properties;
|
||||
this.facebookLeadRepository = facebookLeadRepository;
|
||||
this.webClientBuilder = webClientBuilder;
|
||||
}
|
||||
|
||||
public void syncPendingLeads() {
|
||||
if (!properties.isEnabled()) {
|
||||
return;
|
||||
@@ -64,8 +72,10 @@ public class FacebookLeadCallCenterSyncService {
|
||||
|
||||
FacebookLead managedLead = loadManagedLead(lead);
|
||||
try {
|
||||
Map<String, String> authHeaders = resolveAuthHeaders();
|
||||
|
||||
if (!StringUtils.hasText(managedLead.getCallCenterCustomerId())) {
|
||||
String customerId = createCustomer(managedLead);
|
||||
String customerId = createCustomer(managedLead, authHeaders);
|
||||
managedLead.setCallCenterCustomerId(customerId);
|
||||
managedLead.setCallCenterSyncStatus("CUSTOMER_CREATED");
|
||||
managedLead.setCallCenterSyncError(null);
|
||||
@@ -75,7 +85,7 @@ public class FacebookLeadCallCenterSyncService {
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(managedLead.getCallCenterInteractionId())) {
|
||||
String interactionId = createInteraction(managedLead);
|
||||
String interactionId = createInteraction(managedLead, authHeaders);
|
||||
managedLead.setCallCenterInteractionId(interactionId);
|
||||
log.info("Facebook lead {} linked to call-center interaction {}",
|
||||
managedLead.getExternalCommentId(), interactionId);
|
||||
@@ -107,7 +117,50 @@ public class FacebookLeadCallCenterSyncService {
|
||||
return lead;
|
||||
}
|
||||
|
||||
private String createCustomer(FacebookLead lead) {
|
||||
private Map<String, String> resolveAuthHeaders() {
|
||||
if (StringUtils.hasText(properties.getAuthUsername()) && StringUtils.hasText(properties.getAuthPassword())) {
|
||||
try {
|
||||
String accessToken = loginAndGetAccessToken();
|
||||
return Map.of("Authorization", "Bearer " + accessToken);
|
||||
} catch (Exception e) {
|
||||
log.warn("Call-center bearer auth failed. Falling back to legacy headers. message={}",
|
||||
shorten(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
headers.put("X-User", properties.getActorUser());
|
||||
headers.put("X-Role", properties.getActorRole());
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String loginAndGetAccessToken() {
|
||||
Map<String, Object> payload = Map.of(
|
||||
"username", properties.getAuthUsername(),
|
||||
"password", properties.getAuthPassword()
|
||||
);
|
||||
|
||||
try {
|
||||
JsonNode response = callCenterClient().post()
|
||||
.uri(properties.getAuthLoginPath())
|
||||
.bodyValue(payload)
|
||||
.retrieve()
|
||||
.bodyToMono(JsonNode.class)
|
||||
.block(Duration.ofMillis(properties.getTimeoutMs()));
|
||||
|
||||
String accessToken = response != null ? response.path("access_token").asText(null) : null;
|
||||
if (!StringUtils.hasText(accessToken)) {
|
||||
throw new IllegalStateException("Call-center auth response does not contain access_token");
|
||||
}
|
||||
return accessToken;
|
||||
} catch (WebClientResponseException e) {
|
||||
log.error("Call-center auth login failed. status={}, body={}",
|
||||
e.getStatusCode().value(), e.getResponseBodyAsString());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String createCustomer(FacebookLead lead, Map<String, String> authHeaders) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("display_name", customerDisplayName(lead));
|
||||
payload.put("phones", List.of());
|
||||
@@ -115,10 +168,11 @@ public class FacebookLeadCallCenterSyncService {
|
||||
payload.put("tags", List.of("facebook", "hot-lead"));
|
||||
|
||||
try {
|
||||
JsonNode response = callCenterClient().post()
|
||||
.uri(properties.getCustomerPath())
|
||||
.header("X-User", properties.getActorUser())
|
||||
.header("X-Role", properties.getActorRole())
|
||||
WebClient.RequestBodySpec request = callCenterClient().post()
|
||||
.uri(properties.getCustomerPath());
|
||||
request = applyHeaders(request, authHeaders);
|
||||
|
||||
JsonNode response = request
|
||||
.bodyValue(payload)
|
||||
.retrieve()
|
||||
.bodyToMono(JsonNode.class)
|
||||
@@ -136,7 +190,7 @@ public class FacebookLeadCallCenterSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
private String createInteraction(FacebookLead lead) {
|
||||
private String createInteraction(FacebookLead lead, Map<String, String> authHeaders) {
|
||||
if (!StringUtils.hasText(lead.getCallCenterCustomerId())) {
|
||||
throw new IllegalStateException("Call-center customer must be created before interaction");
|
||||
}
|
||||
@@ -150,10 +204,11 @@ public class FacebookLeadCallCenterSyncService {
|
||||
);
|
||||
|
||||
try {
|
||||
JsonNode response = callCenterClient().post()
|
||||
.uri(properties.getInteractionPath())
|
||||
.header("X-User", properties.getActorUser())
|
||||
.header("X-Role", properties.getActorRole())
|
||||
WebClient.RequestBodySpec request = callCenterClient().post()
|
||||
.uri(properties.getInteractionPath());
|
||||
request = applyHeaders(request, authHeaders);
|
||||
|
||||
JsonNode response = request
|
||||
.bodyValue(payload)
|
||||
.retrieve()
|
||||
.bodyToMono(JsonNode.class)
|
||||
@@ -177,6 +232,18 @@ public class FacebookLeadCallCenterSyncService {
|
||||
.build();
|
||||
}
|
||||
|
||||
private WebClient.RequestBodySpec applyHeaders(
|
||||
WebClient.RequestBodySpec request,
|
||||
Map<String, String> headers) {
|
||||
WebClient.RequestBodySpec current = request;
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (StringUtils.hasText(header.getValue())) {
|
||||
current = current.header(header.getKey(), header.getValue());
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private String customerDisplayName(FacebookLead lead) {
|
||||
String authorName = StringUtils.hasText(lead.getAuthorName())
|
||||
? lead.getAuthorName().trim()
|
||||
|
||||
@@ -155,6 +155,9 @@ facebook.leads.call-center.enabled=${FACEBOOK_LEADS_CALL_CENTER_ENABLED:true}
|
||||
facebook.leads.call-center.base-url=${FACEBOOK_LEADS_CALL_CENTER_BASE_URL:http://localhost:8080}
|
||||
facebook.leads.call-center.customer-path=${FACEBOOK_LEADS_CALL_CENTER_CUSTOMER_PATH:/proxy/customer/customers}
|
||||
facebook.leads.call-center.interaction-path=${FACEBOOK_LEADS_CALL_CENTER_INTERACTION_PATH:/proxy/interaction/interactions}
|
||||
facebook.leads.call-center.auth-login-path=${FACEBOOK_LEADS_CALL_CENTER_AUTH_LOGIN_PATH:/proxy/auth/auth/login}
|
||||
facebook.leads.call-center.auth-username=${FACEBOOK_LEADS_CALL_CENTER_AUTH_USERNAME:admin}
|
||||
facebook.leads.call-center.auth-password=${FACEBOOK_LEADS_CALL_CENTER_AUTH_PASSWORD:admin123}
|
||||
facebook.leads.call-center.actor-user=${FACEBOOK_LEADS_CALL_CENTER_ACTOR_USER:facebook-lead-bot}
|
||||
facebook.leads.call-center.actor-role=${FACEBOOK_LEADS_CALL_CENTER_ACTOR_ROLE:admin}
|
||||
facebook.leads.call-center.interaction-channel=${FACEBOOK_LEADS_CALL_CENTER_INTERACTION_CHANNEL:webchat}
|
||||
|
||||
Reference in New Issue
Block a user