.
This commit is contained in:
@@ -1,22 +1,127 @@
|
|||||||
package kz.konturai.parser.config;
|
package kz.konturai.parser.config;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "facebook.leads.call-center")
|
@ConfigurationProperties(prefix = "facebook.leads.call-center")
|
||||||
@Data
|
|
||||||
public class FacebookLeadCallCenterProperties {
|
public class FacebookLeadCallCenterProperties {
|
||||||
|
|
||||||
private boolean enabled = false;
|
private boolean enabled = false;
|
||||||
private String baseUrl = "http://localhost:8080";
|
private String baseUrl = "http://localhost:8080";
|
||||||
private String customerPath = "/proxy/customer/customers";
|
private String customerPath = "/proxy/customer/customers";
|
||||||
private String interactionPath = "/proxy/interaction/interactions";
|
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 actorUser = "facebook-lead-bot";
|
||||||
private String actorRole = "admin";
|
private String actorRole = "admin";
|
||||||
private String interactionChannel = "webchat";
|
private String interactionChannel = "webchat";
|
||||||
private String interactionQueueId = "q_main";
|
private String interactionQueueId = "q_main";
|
||||||
private int timeoutMs = 30000;
|
private int timeoutMs = 30000;
|
||||||
private String subjectPrefix = "[Facebook]";
|
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.config.FacebookLeadCallCenterProperties;
|
||||||
import kz.konturai.parser.model.FacebookLead;
|
import kz.konturai.parser.model.FacebookLead;
|
||||||
import kz.konturai.parser.repository.FacebookLeadRepository;
|
import kz.konturai.parser.repository.FacebookLeadRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.slf4j.Logger;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
@@ -18,16 +18,24 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class FacebookLeadCallCenterSyncService {
|
public class FacebookLeadCallCenterSyncService {
|
||||||
|
|
||||||
private static final List<String> RETRYABLE_SYNC_STATUSES = List.of("PENDING", "FAILED");
|
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 FacebookLeadCallCenterProperties properties;
|
||||||
private final FacebookLeadRepository facebookLeadRepository;
|
private final FacebookLeadRepository facebookLeadRepository;
|
||||||
private final WebClient.Builder webClientBuilder;
|
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() {
|
public void syncPendingLeads() {
|
||||||
if (!properties.isEnabled()) {
|
if (!properties.isEnabled()) {
|
||||||
return;
|
return;
|
||||||
@@ -64,8 +72,10 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
|
|
||||||
FacebookLead managedLead = loadManagedLead(lead);
|
FacebookLead managedLead = loadManagedLead(lead);
|
||||||
try {
|
try {
|
||||||
|
Map<String, String> authHeaders = resolveAuthHeaders();
|
||||||
|
|
||||||
if (!StringUtils.hasText(managedLead.getCallCenterCustomerId())) {
|
if (!StringUtils.hasText(managedLead.getCallCenterCustomerId())) {
|
||||||
String customerId = createCustomer(managedLead);
|
String customerId = createCustomer(managedLead, authHeaders);
|
||||||
managedLead.setCallCenterCustomerId(customerId);
|
managedLead.setCallCenterCustomerId(customerId);
|
||||||
managedLead.setCallCenterSyncStatus("CUSTOMER_CREATED");
|
managedLead.setCallCenterSyncStatus("CUSTOMER_CREATED");
|
||||||
managedLead.setCallCenterSyncError(null);
|
managedLead.setCallCenterSyncError(null);
|
||||||
@@ -75,7 +85,7 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!StringUtils.hasText(managedLead.getCallCenterInteractionId())) {
|
if (!StringUtils.hasText(managedLead.getCallCenterInteractionId())) {
|
||||||
String interactionId = createInteraction(managedLead);
|
String interactionId = createInteraction(managedLead, authHeaders);
|
||||||
managedLead.setCallCenterInteractionId(interactionId);
|
managedLead.setCallCenterInteractionId(interactionId);
|
||||||
log.info("Facebook lead {} linked to call-center interaction {}",
|
log.info("Facebook lead {} linked to call-center interaction {}",
|
||||||
managedLead.getExternalCommentId(), interactionId);
|
managedLead.getExternalCommentId(), interactionId);
|
||||||
@@ -107,7 +117,50 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
return lead;
|
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<>();
|
Map<String, Object> payload = new LinkedHashMap<>();
|
||||||
payload.put("display_name", customerDisplayName(lead));
|
payload.put("display_name", customerDisplayName(lead));
|
||||||
payload.put("phones", List.of());
|
payload.put("phones", List.of());
|
||||||
@@ -115,10 +168,11 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
payload.put("tags", List.of("facebook", "hot-lead"));
|
payload.put("tags", List.of("facebook", "hot-lead"));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JsonNode response = callCenterClient().post()
|
WebClient.RequestBodySpec request = callCenterClient().post()
|
||||||
.uri(properties.getCustomerPath())
|
.uri(properties.getCustomerPath());
|
||||||
.header("X-User", properties.getActorUser())
|
request = applyHeaders(request, authHeaders);
|
||||||
.header("X-Role", properties.getActorRole())
|
|
||||||
|
JsonNode response = request
|
||||||
.bodyValue(payload)
|
.bodyValue(payload)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(JsonNode.class)
|
.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())) {
|
if (!StringUtils.hasText(lead.getCallCenterCustomerId())) {
|
||||||
throw new IllegalStateException("Call-center customer must be created before interaction");
|
throw new IllegalStateException("Call-center customer must be created before interaction");
|
||||||
}
|
}
|
||||||
@@ -150,10 +204,11 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JsonNode response = callCenterClient().post()
|
WebClient.RequestBodySpec request = callCenterClient().post()
|
||||||
.uri(properties.getInteractionPath())
|
.uri(properties.getInteractionPath());
|
||||||
.header("X-User", properties.getActorUser())
|
request = applyHeaders(request, authHeaders);
|
||||||
.header("X-Role", properties.getActorRole())
|
|
||||||
|
JsonNode response = request
|
||||||
.bodyValue(payload)
|
.bodyValue(payload)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(JsonNode.class)
|
.bodyToMono(JsonNode.class)
|
||||||
@@ -177,6 +232,18 @@ public class FacebookLeadCallCenterSyncService {
|
|||||||
.build();
|
.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) {
|
private String customerDisplayName(FacebookLead lead) {
|
||||||
String authorName = StringUtils.hasText(lead.getAuthorName())
|
String authorName = StringUtils.hasText(lead.getAuthorName())
|
||||||
? lead.getAuthorName().trim()
|
? 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.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.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.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-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.actor-role=${FACEBOOK_LEADS_CALL_CENTER_ACTOR_ROLE:admin}
|
||||||
facebook.leads.call-center.interaction-channel=${FACEBOOK_LEADS_CALL_CENTER_INTERACTION_CHANNEL:webchat}
|
facebook.leads.call-center.interaction-channel=${FACEBOOK_LEADS_CALL_CENTER_INTERACTION_CHANNEL:webchat}
|
||||||
|
|||||||
Reference in New Issue
Block a user