diff --git a/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java b/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java index 519dc9e..7c7e9d6 100644 --- a/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java +++ b/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java @@ -7,6 +7,7 @@ import kz.konturai.parser.service.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.util.StringUtils; @@ -35,11 +36,13 @@ public class TargetingCampaignController { if (StringUtils.hasText(userIdHeader)) { return userIdHeader; } - String userId = jwtService.extractUserIdFromHeader(authHeader); - if (userId == null) { - throw new IllegalArgumentException("Cannot resolve user identity: X-User-Id header or valid Authorization token required"); + try { + String userId = jwtService.extractUserIdFromHeader(authHeader); + return userId; // Может быть null если токен истёк + } catch (Exception e) { + log.warn("[Targeting] Failed to extract userId from token: {}", e.getMessage()); + return null; } - return userId; } @PostMapping("/campaigns") @@ -48,6 +51,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody TargetingCampaignRequest request) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Create campaign requested by {}", userId); return ResponseEntity.ok(campaignService.createCampaign(request, userId)); } @@ -59,6 +64,8 @@ public class TargetingCampaignController { @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Get campaigns for {}", userId); return ResponseEntity.ok(campaignService.getUserCampaigns(userId, page, size)); } @@ -69,6 +76,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Get campaign {} for {}", id, userId); return ResponseEntity.ok(campaignService.getCampaignById(id, userId)); } @@ -79,6 +88,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Pause campaign {}", id); return ResponseEntity.ok(campaignService.pauseCampaign(id, userId)); } @@ -89,6 +100,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Resume campaign {}", id); return ResponseEntity.ok(campaignService.resumeCampaign(id, userId)); } @@ -100,6 +113,8 @@ public class TargetingCampaignController { @PathVariable String id, @RequestParam(defaultValue = "last_7d") String datePreset) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Get insights for campaign {}", id); return ResponseEntity.ok(campaignService.getCampaignInsights(id, userId, datePreset)); } @@ -110,6 +125,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Force sync insights for campaign {}", id); campaignService.getCampaignById(id, userId); campaignService.syncInsights(id); @@ -122,6 +139,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Evaluate AB Test for {}", id); TargetingCampaign campaign = campaignService.getCampaignById(id, userId); return ResponseEntity.ok(abTestingService.evaluateABTest(campaign)); @@ -158,6 +177,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody Map body) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); String campaignId = body.get("campaignId"); log.info("[Targeting] Optimize budget for campaign {}", campaignId); TargetingCampaign campaign = campaignService.getCampaignById(campaignId, userId); @@ -173,6 +194,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody Map body) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); String platform = body.get("platform"); String adAccountId = body.get("adAccountId"); @@ -190,6 +213,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); TargetingCampaign campaign = campaignService.getCampaignById(id, userId); String status = campaign.getStatus(); boolean isTerminal = "active".equals(status) || "failed".equals(status) || "paused".equals(status); @@ -210,6 +235,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); // Проверяем существование кампании и права доступа TargetingCampaign campaign = campaignService.getCampaignById(id, userId); @@ -224,8 +251,7 @@ public class TargetingCampaignController { return ResponseEntity.accepted().body(Map.of( "status", "PROCESSING", - "message", "Предсказание еще не готово и было запущено автоматически. Попробуйте через 5-10 секунд." - )); + "message", "Предсказание еще не готово и было запущено автоматически. Попробуйте через 5-10 секунд.")); } @PostMapping("/campaigns/{id}/predict") @@ -234,6 +260,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); TargetingCampaign campaign = campaignService.getCampaignById(id, userId); log.info("[Targeting] Manual predict trigger for campaign {}", id); predictionService.generateAsync(campaign); @@ -248,6 +276,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); TargetingCampaign campaign = campaignService.getCampaignById(id, userId); return ResponseEntity.ok(campaign.getInsights() != null ? campaign.getInsights() : List.of()); } @@ -260,6 +290,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Retry campaign {}", id); return ResponseEntity.ok(campaignService.retryCampaign(id, userId)); } @@ -273,6 +305,8 @@ public class TargetingCampaignController { @PathVariable String id, @RequestBody Map body) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Update budget for campaign {}", id); return ResponseEntity.ok(campaignService.updateBudget(id, userId, body.get("totalBudget"), body.get("dailyBudget"))); @@ -285,6 +319,8 @@ public class TargetingCampaignController { @PathVariable String id, @RequestBody TargetingAudienceProfile audience) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Update audience for campaign {}", id); return ResponseEntity.ok(campaignService.updateAudience(id, userId, audience)); } @@ -297,33 +333,39 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String strategyId) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Direct Facebook publish for strategy {} requested by {}", strategyId, userId); - + try { String postId = marketingStrategyV3Service.publishFirstFacebookPost(strategyId); return ResponseEntity.ok(Map.of( "success", true, "message", "Пост успешно опубликован в Facebook", - "postId", postId - )); + "postId", postId)); } catch (IllegalArgumentException | IllegalStateException e) { return ResponseEntity.badRequest().body(Map.of("success", false, "error", e.getMessage())); } catch (kz.konturai.parser.exception.FacebookTokenExpiredException e) { log.error("Facebook token expired: {}", e.getMessage()); - return ResponseEntity.badRequest().body(Map.of("success", false, "error", "Токен Facebook истёк. Пожалуйста, обновите токен (facebook.page.token) в файле application.properties.")); + return ResponseEntity.badRequest().body(Map.of("success", false, "error", + "Токен Facebook истёк. Пожалуйста, обновите токен (facebook.page.token) в файле application.properties.")); } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().contains("[Facebook] API Error")) { - log.error("Facebook API Error: {}", e.getMessage()); - if (e.getMessage().contains("publish_actions")) { - return ResponseEntity.badRequest().body(Map.of("success", false, "error", "Указан токен Пользователя, а не Страницы (User Token). Facebook запрещает публикацию через личные токены. Сгенерируйте именно Page Access Token.")); - } - return ResponseEntity.badRequest().body(Map.of("success", false, "error", "Ошибка со стороны Facebook API: " + e.getMessage())); + log.error("Facebook API Error: {}", e.getMessage()); + if (e.getMessage().contains("publish_actions")) { + return ResponseEntity.badRequest().body(Map.of("success", false, "error", + "Указан токен Пользователя, а не Страницы (User Token). Facebook запрещает публикацию через личные токены. Сгенерируйте именно Page Access Token.")); + } + return ResponseEntity.badRequest() + .body(Map.of("success", false, "error", "Ошибка со стороны Facebook API: " + e.getMessage())); } log.error("Failed to publish post: ", e); - return ResponseEntity.internalServerError().body(Map.of("success", false, "error", "Не удалось опубликовать пост")); + return ResponseEntity.internalServerError() + .body(Map.of("success", false, "error", "Не удалось опубликовать пост")); } catch (Exception e) { log.error("Failed to publish post: ", e); - return ResponseEntity.internalServerError().body(Map.of("success", false, "error", "Не удалось опубликовать пост")); + return ResponseEntity.internalServerError() + .body(Map.of("success", false, "error", "Не удалось опубликовать пост")); } } @@ -335,6 +377,8 @@ public class TargetingCampaignController { @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); return ResponseEntity.ok(campaignService.getAdSets(id, userId)); } @@ -346,7 +390,27 @@ public class TargetingCampaignController { @PathVariable String adSetId, @RequestBody TargetingAdSet adSet) { String userId = resolveUserId(userIdHeader, authHeader); + if (userId == null) + return unauthorizedResponse(); log.info("[Targeting] Update adSet {} for campaign {}", adSetId, id); return ResponseEntity.ok(campaignService.updateAdSet(id, adSetId, userId, adSet)); } + + // ── Helper methods ─────────────────────────────────────────────────────── + + @SuppressWarnings("unchecked") + private ResponseEntity unauthorizedResponse() { + return (ResponseEntity) ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.error( + "Не авторизован", + new ErrorResponse("UNAUTHORIZED", "Требуется авторизация. Предоставьте валидный JWT токен."))); + } + + @SuppressWarnings("unchecked") + private ResponseEntity forbiddenResponse() { + return (ResponseEntity) ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(ApiResponse.error( + "Доступ запрещён", + new ErrorResponse("FORBIDDEN", "У вас нет прав для доступа к этому ресурсу."))); + } }