target fix
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package kz.konturai.parser.controller;
|
||||
|
||||
import kz.konturai.parser.dto.ApiResponse;
|
||||
import kz.konturai.parser.service.FacebookPostingService;
|
||||
import kz.konturai.parser.service.JwtService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Контроллер для настройки Facebook (обмен токенов и т.д.)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/facebook/config")
|
||||
@RequiredArgsConstructor
|
||||
public class FacebookConfigController {
|
||||
|
||||
private final FacebookPostingService facebookPostingService;
|
||||
private final JwtService jwtService;
|
||||
|
||||
private String extractUserIdFromHeader(String authHeader) {
|
||||
if (authHeader == null || authHeader.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return jwtService.extractUserIdFromHeader(authHeader);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Помощник для обмена временного токена на долгосрочный.
|
||||
*
|
||||
* POST /api/facebook/config/exchange-token
|
||||
* Body: { "shortToken": "...", "pageId": "..." }
|
||||
*/
|
||||
@PostMapping("/exchange-token")
|
||||
public ResponseEntity<?> exchangeToken(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestBody Map<String, String> request) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return ResponseEntity.status(401).body(ApiResponse.error("Не авторизован", null));
|
||||
}
|
||||
|
||||
String shortToken = request.get("shortToken");
|
||||
String pageId = request.get("pageId");
|
||||
|
||||
if (shortToken == null || pageId == null) {
|
||||
return ResponseEntity.badRequest().body(ApiResponse.error("shortToken и pageId обязательны", null));
|
||||
}
|
||||
|
||||
try {
|
||||
String longLivedToken = facebookPostingService.exchangeForLongLivedPageToken(shortToken, pageId);
|
||||
return ResponseEntity.ok(ApiResponse.success("Токен успешно обменян. Сохраните его в настройках.",
|
||||
Map.of("longLivedToken", longLivedToken)));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(500).body(ApiResponse.error("Ошибка обмена: " + e.getMessage(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,8 +286,8 @@ public class TargetingCampaignController {
|
||||
|
||||
// ── Инсайты ───────────────────────────────────────────────────────────────
|
||||
|
||||
@GetMapping("/campaigns/{id}/insights")
|
||||
public ResponseEntity<?> getInsights(
|
||||
@GetMapping("/campaigns/{id}/insights/raw")
|
||||
public ResponseEntity<?> getRawInsights(
|
||||
@RequestHeader(value = "X-User-Id", required = false) String userIdHeader,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String id) {
|
||||
|
||||
@@ -24,6 +24,9 @@ public class SocialMediaCredentials {
|
||||
@Field("ad_account_id")
|
||||
private String adAccountId;
|
||||
|
||||
@Field("page_id")
|
||||
private String pageId;
|
||||
|
||||
@Field("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -84,6 +87,15 @@ public class SocialMediaCredentials {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getPageId() {
|
||||
return pageId;
|
||||
}
|
||||
|
||||
public void setPageId(String pageId) {
|
||||
this.pageId = pageId;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -485,13 +485,13 @@ public class AiTargetingSystemService {
|
||||
|
||||
if (imageBytes != null) {
|
||||
facebookPostId = facebookPostingService.postToPageWithImage(
|
||||
null, post.getPostText(), post.getHashtags(), imageBytes);
|
||||
post.getPostText(), post.getHashtags(), imageBytes);
|
||||
imagePosted = true;
|
||||
facebookStatus = "PUBLISHED";
|
||||
logger.info("Facebook post published WITH image. postId={}", facebookPostId);
|
||||
} else {
|
||||
facebookPostId = facebookPostingService.postToPage(
|
||||
null, post.getPostText(), post.getHashtags());
|
||||
post.getPostText(), post.getHashtags());
|
||||
facebookStatus = "TEXT_ONLY";
|
||||
logger.info("Facebook post published TEXT ONLY. postId={}", facebookPostId);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kz.konturai.parser.exception.FacebookTokenExpiredException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -19,27 +17,42 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Сервис публикации постов в Facebook.
|
||||
*
|
||||
* Использует надежную схему с Long-Lived Page Access Token.
|
||||
* Настройки приоритетно берутся из application.properties.
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class FacebookPostingService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FacebookPostingService.class);
|
||||
private static final String FACEBOOK_GRAPH_API_BASE = "https://graph.facebook.com/v19.0";
|
||||
|
||||
private static final String HARDCODED_PAGE_TOKEN = "EAAaocqgT3JoBQhROtnPQ3echpBNU7pnrRxmj7XOoyFzDzOeJIZCjjck4ilraxYEBNXgDZCWfvUZBjDWPLGKrvF7ZCc1ZAmnDj1RHnW06I69ZBvCVbpoiKzsMqPihLZAwZB7WJykDAlaCVbhSBFmOY2YdMlf1fwi3cUq0tvKAaiJesKJbfOuXgMV0c7lSFKNz2yxsWiRfTzpMjhjk8Y9EsYpM8FW0HMEktzeHaoGOGOoI8oEZD";
|
||||
private static final String HARDCODED_PAGE_ID = "918835061309575";
|
||||
@Value("${facebook.app.id:}")
|
||||
private String appId;
|
||||
|
||||
private final WebClient webClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Value("${facebook.app.secret:}")
|
||||
private String appSecret;
|
||||
|
||||
@Value("${facebook.page.token:EAAaocqgT3JoBQhROtnPQ3echpBNU7pnrRxmj7XOoyFzDzOeJIZCjjck4ilraxYEBNXgDZCWfvUZBjDWPLGKrvF7ZCc1ZAmnDj1RHnW06I69ZBvCVbpoiKzsMqPihLZAwZB7WJykDAlaCVbhSBFmOY2YdMlf1fwi3cUq0tvKAaiJesKJbfOuXgMV0c7lSFKNz2yxsWiRfTzpMjhjk8Y9EsYpM8FW0HMEktzeHaoGOGOoI8oEZD}")
|
||||
private String pageToken;
|
||||
|
||||
@Value("${facebook.page.id:918835061309575}")
|
||||
private String pageId;
|
||||
|
||||
@Value("${facebook.api.timeout:30000}")
|
||||
private int timeoutMs;
|
||||
|
||||
private final WebClient webClient;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public FacebookPostingService() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.responseTimeout(Duration.ofMillis(30000));
|
||||
|
||||
@@ -47,89 +60,141 @@ public class FacebookPostingService {
|
||||
.baseUrl(FACEBOOK_GRAPH_API_BASE)
|
||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||
.build();
|
||||
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
public PageCredentials getPageCredentials(String ignoredAccessToken) {
|
||||
return new PageCredentials(HARDCODED_PAGE_ID, HARDCODED_PAGE_TOKEN);
|
||||
// ── Credentials management ────────────────────────────────────────────────
|
||||
|
||||
public PageCredentials getPageCredentials() {
|
||||
return new PageCredentials(pageId, pageToken);
|
||||
}
|
||||
|
||||
public void sendPrivateMessage(String ignoredToken, String recipientId, String messageText) {
|
||||
/**
|
||||
* Обмен короткого токена пользователя на долгосрочный токен страницы.
|
||||
*/
|
||||
public String exchangeForLongLivedPageToken(String shortLivedUserToken, String targetPageId) {
|
||||
log.info("[Facebook] Запуск обмена токена для страницы {}", targetPageId);
|
||||
|
||||
if (appId == null || appId.contains("dummy") || appSecret == null || appSecret.contains("dummy")) {
|
||||
throw new IllegalStateException("Hеобходимo настроить facebook.app.id и facebook.app.secret");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> recipient = new HashMap<>();
|
||||
recipient.put("id", recipientId);
|
||||
// 1. Короткий токен -> Долгосрочный User Token (60 дней)
|
||||
JsonNode userTokenNode = webClient.get()
|
||||
.uri(u -> u.path("/oauth/access_token")
|
||||
.queryParam("grant_type", "fb_exchange_token")
|
||||
.queryParam("client_id", appId)
|
||||
.queryParam("client_secret", appSecret)
|
||||
.queryParam("fb_exchange_token", shortLivedUserToken)
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(JsonNode.class)
|
||||
.block(Duration.ofMillis(timeoutMs));
|
||||
|
||||
Map<String, String> messageContent = new HashMap<>();
|
||||
messageContent.put("text", messageText);
|
||||
String longLivedUserToken = userTokenNode.path("access_token").asText();
|
||||
log.info("[Facebook] Получен долгосрочный User Token");
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("recipient", recipient);
|
||||
body.put("message", messageContent);
|
||||
// 2. Список страниц этого пользователя
|
||||
JsonNode accounts = webClient.get()
|
||||
.uri(u -> u.path("/me/accounts")
|
||||
.queryParam("access_token", longLivedUserToken)
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(JsonNode.class)
|
||||
.block(Duration.ofMillis(timeoutMs));
|
||||
|
||||
if (accounts != null && accounts.has("data")) {
|
||||
for (JsonNode page : accounts.get("data")) {
|
||||
if (page.path("id").asText().equals(targetPageId)) {
|
||||
String permToken = page.path("access_token").asText();
|
||||
log.info("[Facebook] УСПЕХ! Получен надежный Page Token для {}", targetPageId);
|
||||
log.info("[Facebook] ПЕРЕЗАПИШИТЕ ЕГО В application.properties: {}", permToken);
|
||||
return permToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Страница " + targetPageId + " не найдена в аккаунте");
|
||||
} catch (Exception e) {
|
||||
log.error("[Facebook] Ошибка обмена токена: {}", e.getMessage());
|
||||
throw new RuntimeException("Ошибка Meta API: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Posting methods ───────────────────────────────────────────────────────
|
||||
|
||||
public String postToPage(String postText, List<String> hashtags) {
|
||||
return internalPost(getPageCredentials(), postText, hashtags, null);
|
||||
}
|
||||
|
||||
public String postToPageWithImage(String postText, List<String> hashtags, byte[] imageData) {
|
||||
return internalPost(getPageCredentials(), postText, hashtags, imageData);
|
||||
}
|
||||
|
||||
// ── Messaging ────────────────────────────────────────────────────────────
|
||||
|
||||
public void sendPrivateMessage(String recipientId, String messageText) {
|
||||
PageCredentials creds = getPageCredentials();
|
||||
try {
|
||||
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
||||
body.put("recipient", java.util.Map.of("id", recipientId));
|
||||
body.put("message", java.util.Map.of("text", messageText));
|
||||
body.put("messaging_type", "RESPONSE");
|
||||
|
||||
webClient.post()
|
||||
.uri(uriBuilder -> uriBuilder.path("/me/messages")
|
||||
.queryParam("access_token", HARDCODED_PAGE_TOKEN).build())
|
||||
.uri(u -> u.path("/me/messages")
|
||||
.queryParam("access_token", creds.pageToken).build())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(body)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block(Duration.ofMillis(timeoutMs));
|
||||
|
||||
logger.info("Message sent to user {}", recipientId);
|
||||
log.info("[Facebook] Сообщение отправлено пользователю {}", recipientId);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to send text message: ", e);
|
||||
log.error("[Facebook] Ошибка отправки сообщения: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPrivateImageMessage(String ignoredToken, String recipientId, byte[] imageData) {
|
||||
public void sendPrivateImageMessage(String recipientId, byte[] imageData) {
|
||||
PageCredentials creds = getPageCredentials();
|
||||
try {
|
||||
ByteArrayResource imageResource = new ByteArrayResource(imageData) {
|
||||
@Override public String getFilename() { return "image.jpg"; }
|
||||
};
|
||||
|
||||
String recipientJson = "{\"id\":\"" + recipientId + "\"}";
|
||||
String messageJson = "{\"attachment\":{\"type\":\"image\", \"payload\":{\"is_reusable\":true}}}";
|
||||
|
||||
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("recipient", recipientJson);
|
||||
formData.add("message", messageJson);
|
||||
formData.add("recipient", "{\"id\":\"" + recipientId + "\"}");
|
||||
formData.add("message", "{\"attachment\":{\"type\":\"image\", \"payload\":{\"is_reusable\":true}}}");
|
||||
formData.add("filedata", imageResource);
|
||||
formData.add("access_token", HARDCODED_PAGE_TOKEN);
|
||||
formData.add("access_token", creds.pageToken);
|
||||
|
||||
webClient.post()
|
||||
.uri(uriBuilder -> uriBuilder.path("/me/messages").build())
|
||||
.uri(u -> u.path("/me/messages").build())
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
.body(BodyInserters.fromMultipartData(formData))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block(Duration.ofMillis(timeoutMs * 2));
|
||||
|
||||
logger.info("Image message sent to user {}", recipientId);
|
||||
log.info("[Facebook] Изображение отправлено пользователю {}", recipientId);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to send image message: ", e);
|
||||
log.error("[Facebook] Ошибка отправки изображения: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String postToPage(String ignoredToken, String postText, List<String> hashtags) throws JsonProcessingException {
|
||||
return internalPost(postText, hashtags, null);
|
||||
}
|
||||
// ── Internal ──────────────────────────────────────────────────────────────
|
||||
|
||||
public String postToPageWithImage(String ignoredToken, String postText, List<String> hashtags, byte[] imageData) {
|
||||
return internalPost(postText, hashtags, imageData);
|
||||
}
|
||||
private String internalPost(PageCredentials creds, String postText, List<String> hashtags, byte[] imageData) {
|
||||
if (creds.pageToken == null || creds.pageToken.length() < 10) {
|
||||
throw new IllegalStateException("Facebook Access Token не настроен! Проверьте application.properties");
|
||||
}
|
||||
|
||||
private String internalPost(String postText, List<String> hashtags, byte[] imageData) {
|
||||
try {
|
||||
String fullPostText = buildPostText(postText, hashtags);
|
||||
|
||||
String fullText = buildPostText(postText, hashtags);
|
||||
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
|
||||
formData.add("access_token", HARDCODED_PAGE_TOKEN);
|
||||
formData.add("message", fullPostText);
|
||||
formData.add("access_token", creds.pageToken);
|
||||
formData.add("message", fullText);
|
||||
|
||||
String endpoint;
|
||||
|
||||
if (imageData != null) {
|
||||
endpoint = "/{pageId}/photos";
|
||||
formData.add("source", new ByteArrayResource(imageData) {
|
||||
@@ -140,54 +205,52 @@ public class FacebookPostingService {
|
||||
}
|
||||
|
||||
String response = webClient.post()
|
||||
.uri(uriBuilder -> uriBuilder.path(endpoint).build(HARDCODED_PAGE_ID))
|
||||
.uri(u -> u.path(endpoint).build(creds.pageId))
|
||||
.contentType(imageData != null ? MediaType.MULTIPART_FORM_DATA : MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body(BodyInserters.fromMultipartData(formData))
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block(Duration.ofMillis(timeoutMs * 2));
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
String postId = jsonNode.get("id").asText();
|
||||
logger.info("Published to Facebook. ID: {}", postId);
|
||||
JsonNode json = objectMapper.readTree(response);
|
||||
String postId = json.path("id").asText(null);
|
||||
log.info("[Facebook] Пост опубликован. id={}, pageId={}", postId, creds.pageId);
|
||||
return postId;
|
||||
|
||||
} catch (WebClientResponseException e) {
|
||||
handleFacebookError(e, "internalPost");
|
||||
handleFacebookError(e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error posting: " + e.getMessage(), e);
|
||||
throw new RuntimeException("[Facebook] Ошибка: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPostText(String postText, List<String> hashtags) {
|
||||
StringBuilder fullText = new StringBuilder(postText != null ? postText : "");
|
||||
StringBuilder sb = new StringBuilder(postText != null ? postText : "");
|
||||
if (hashtags != null && !hashtags.isEmpty()) {
|
||||
if (fullText.length() > 0) fullText.append("\n\n");
|
||||
String hashtagsText = hashtags.stream()
|
||||
.map(tag -> tag.startsWith("#") ? tag : "#" + tag)
|
||||
.collect(Collectors.joining(" "));
|
||||
fullText.append(hashtagsText);
|
||||
if (sb.length() > 0) sb.append("\n\n");
|
||||
sb.append(hashtags.stream()
|
||||
.map(t -> t.startsWith("#") ? t : "#" + t)
|
||||
.collect(Collectors.joining(" ")));
|
||||
}
|
||||
return fullText.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void handleFacebookError(WebClientResponseException e) {
|
||||
log.error("[Facebook] API Error: status={}, body={}", e.getStatusCode().value(), e.getResponseBodyAsString());
|
||||
String body = e.getResponseBodyAsString();
|
||||
if (body.contains("access token") || body.contains("expired")) {
|
||||
throw new FacebookTokenExpiredException("Токен Facebook истёк. Получите новый через утилиту обмена.",
|
||||
"Token Expired", 190, 463, e);
|
||||
}
|
||||
throw new RuntimeException("[Facebook] API Error: " + body, e);
|
||||
}
|
||||
|
||||
public static class PageCredentials {
|
||||
public String pageId; public String pageToken;
|
||||
public PageCredentials(String pageId, String pageToken) { this.pageId = pageId; this.pageToken = pageToken; }
|
||||
public final String pageId;
|
||||
public final String pageToken;
|
||||
public PageCredentials(String pageId, String pageToken) {
|
||||
this.pageId = pageId;
|
||||
this.pageToken = pageToken;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFacebookError(WebClientResponseException e, String context) {
|
||||
logger.error("FB API Error [{}]: Body={}", context, e.getResponseBodyAsString());
|
||||
if (isTokenExpiredError(e)) throw parseFacebookError(e);
|
||||
throw new RuntimeException("Facebook API Error: " + e.getResponseBodyAsString(), e);
|
||||
}
|
||||
|
||||
private boolean isTokenExpiredError(WebClientResponseException e) {
|
||||
return e.getStatusCode().value() == 400 || e.getStatusCode().value() == 401;
|
||||
}
|
||||
|
||||
private FacebookTokenExpiredException parseFacebookError(WebClientResponseException e) {
|
||||
return new FacebookTokenExpiredException("Token Expired", "Token Expired", 190, 463, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +146,9 @@ public class PostingTaskService {
|
||||
if ("facebook".equalsIgnoreCase(task.getPlatform())) {
|
||||
|
||||
if (imageData != null && imageData.length > 0) {
|
||||
postId = facebookPostingService.postToPageWithImage(credentials, task.getPostText(), task.getHashtags(), imageData);
|
||||
postId = facebookPostingService.postToPageWithImage(task.getPostText(), task.getHashtags(), imageData);
|
||||
} else {
|
||||
postId = facebookPostingService.postToPage(credentials, task.getPostText(), task.getHashtags());
|
||||
postId = facebookPostingService.postToPage(task.getPostText(), task.getHashtags());
|
||||
}
|
||||
|
||||
if (postId != null) {
|
||||
@@ -158,10 +158,10 @@ public class PostingTaskService {
|
||||
"🔗 Ссылка: https://facebook.com/" + postId + "\n" +
|
||||
"🕒 Время: " + LocalDateTime.now();
|
||||
|
||||
facebookPostingService.sendPrivateMessage(credentials, TEST_RECIPIENT_ID, reportText);
|
||||
facebookPostingService.sendPrivateMessage(TEST_RECIPIENT_ID, reportText);
|
||||
|
||||
if (imageData != null && imageData.length > 0) {
|
||||
facebookPostingService.sendPrivateImageMessage(credentials, TEST_RECIPIENT_ID, imageData);
|
||||
facebookPostingService.sendPrivateImageMessage(TEST_RECIPIENT_ID, imageData);
|
||||
}
|
||||
} catch (Exception msgEx) {
|
||||
logger.warn("Post published but failed to send private notification: {}", msgEx.getMessage());
|
||||
|
||||
@@ -134,6 +134,12 @@ telegram.api.timeout=30000
|
||||
# targeting.tiktok.oauth-redirect-uri=${TIKTOK_REDIRECT_URI:http://localhost:8080/api/v1/targeting/tiktok/callback}
|
||||
# targeting.tiktok.base-url=https://business-api.tiktok.com/open_api/v1.3
|
||||
|
||||
# Facebook Posting Configuration
|
||||
facebook.app.id=1874060259875994
|
||||
facebook.app.secret=1e096c34d375641b7c4d05fff64299ea
|
||||
facebook.page.id=918835061309575
|
||||
facebook.page.token=EAAaocqgT3JoBRIdErZC0vZAleU3lrszUXzX8aSylq7sr9aFDGnxUt4cZAJGPec3uUONzEdlgwNclXyDhp77vJMTie5PcSb9CLAZAQZBMX3KR3RkmLOZChZCjgjoN58RJ6DwQqZA8N0PbW27Hdr7xGfdlobYopEL2nNbpdX1DHvMdXbGa4M3C92crZAjqyMkggCeiSjTQ8WU5FuZB8T8bQXrAYjGuZAImJF82mx9gcjqtDkxcarLxZA1M41nJpf5Q2Dx6F3tpcsuc1hcHbnhdZCn96nq8aHwGa
|
||||
|
||||
targeting.ai.audience-model=gpt-4o
|
||||
targeting.ai.max-audience-tokens=2500
|
||||
targeting.ai.budget-model=gpt-4o-mini
|
||||
|
||||
@@ -240,8 +240,8 @@ class PostingTaskServiceTest {
|
||||
savedStatuses.add(savedTask.getStatus());
|
||||
return savedTask;
|
||||
});
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(facebookPostingService.postToPage("fb-token", "Product launch", List.of("#launch")))
|
||||
// FacebookPostingService now handles credentials internally
|
||||
when(facebookPostingService.postToPage("Product launch", List.of("#launch")))
|
||||
.thenReturn("fb-post-1");
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
@@ -250,12 +250,11 @@ class PostingTaskServiceTest {
|
||||
assertEquals("completed", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
assertNotNull(task.getExecutedAt());
|
||||
verify(facebookPostingService).postToPage("fb-token", "Product launch", List.of("#launch"));
|
||||
verify(facebookPostingService).postToPage("Product launch", List.of("#launch"));
|
||||
verify(facebookPostingService).sendPrivateMessage(
|
||||
eq("fb-token"),
|
||||
eq(TEST_RECIPIENT_ID),
|
||||
argThat(message -> message.contains("Product launch") && message.contains("https://facebook.com/fb-post-1")));
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), anyString(), any());
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), any());
|
||||
verify(minIOService, never()).downloadFile(anyString());
|
||||
}
|
||||
|
||||
@@ -269,28 +268,25 @@ class PostingTaskServiceTest {
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(minIOService.downloadFile("banner.png")).thenReturn(streamOf(imageBytes));
|
||||
when(facebookPostingService.postToPageWithImage(
|
||||
eq("fb-token"),
|
||||
eq("Image post"),
|
||||
eq(List.of("#image")),
|
||||
aryEq(imageBytes)))
|
||||
.thenReturn("fb-post-2");
|
||||
doThrow(new RuntimeException("Messenger unavailable"))
|
||||
.when(facebookPostingService)
|
||||
.sendPrivateMessage(eq("fb-token"), eq(TEST_RECIPIENT_ID), anyString());
|
||||
.sendPrivateMessage(eq(TEST_RECIPIENT_ID), anyString());
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
assertEquals("completed", task.getStatus());
|
||||
assertNull(task.getErrorMessage());
|
||||
verify(facebookPostingService).postToPageWithImage(
|
||||
eq("fb-token"),
|
||||
eq("Image post"),
|
||||
eq(List.of("#image")),
|
||||
aryEq(imageBytes));
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), anyString(), any());
|
||||
verify(facebookPostingService, never()).sendPrivateImageMessage(anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -393,8 +389,8 @@ class PostingTaskServiceTest {
|
||||
|
||||
when(taskRepository.findById("task-1")).thenReturn(Optional.of(task));
|
||||
when(taskRepository.save(any(PostingTask.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(credentialsService.getCredentials("user-1", "facebook")).thenReturn("fb-token");
|
||||
when(facebookPostingService.postToPage("fb-token", task.getPostText(), task.getHashtags()))
|
||||
// FacebookPostingService now handles credentials internally
|
||||
when(facebookPostingService.postToPage(task.getPostText(), task.getHashtags()))
|
||||
.thenThrow(new FacebookTokenExpiredException("Expired", "code 190", 190, 463));
|
||||
|
||||
postingTaskService.executeTask("task-1");
|
||||
|
||||
Reference in New Issue
Block a user