diff --git a/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java b/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java index 65b8126..458c6aa 100644 --- a/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java +++ b/src/main/java/kz/konturai/parser/controller/TargetingCampaignController.java @@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.util.StringUtils; import java.util.List; import java.util.Map; @@ -29,63 +30,88 @@ public class TargetingCampaignController { private final ABTestingService abTestingService; private final MarketingAnalysisV3Repository analysisRepository; private final SocialMediaCredentialsService credentialsService; + private final JwtService jwtService; + + private String resolveUserId(String userIdHeader, String authHeader) { + 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"); + } + return userId; + } @PostMapping("/campaigns") public ResponseEntity createCampaign( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody TargetingCampaignRequest request) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Create campaign requested by {}", userId); return ResponseEntity.ok(campaignService.createCampaign(request, userId)); } @GetMapping("/campaigns") public ResponseEntity> getUserCampaigns( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Get campaigns for {}", userId); return ResponseEntity.ok(campaignService.getUserCampaigns(userId, page, size)); } @GetMapping("/campaigns/{id}") public ResponseEntity getCampaign( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Get campaign {} for {}", id, userId); return ResponseEntity.ok(campaignService.getCampaignById(id, userId)); } @PostMapping("/campaigns/{id}/pause") public ResponseEntity pauseCampaign( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Pause campaign {}", id); return ResponseEntity.ok(campaignService.pauseCampaign(id, userId)); } @PostMapping("/campaigns/{id}/resume") public ResponseEntity resumeCampaign( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Resume campaign {}", id); return ResponseEntity.ok(campaignService.resumeCampaign(id, userId)); } @GetMapping("/campaigns/{id}/insights") public ResponseEntity getInsights( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id, @RequestParam(defaultValue = "last_7d") String datePreset) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Get insights for campaign {}", id); return ResponseEntity.ok(campaignService.getCampaignInsights(id, userId, datePreset)); } @PostMapping("/campaigns/{id}/sync-insights") public ResponseEntity syncInsights( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Force sync insights for campaign {}", id); - // Authorize campaignService.getCampaignById(id, userId); campaignService.syncInsights(id); return ResponseEntity.ok().build(); @@ -93,8 +119,10 @@ public class TargetingCampaignController { @GetMapping("/campaigns/{id}/ab-test/evaluate") public ResponseEntity evaluateAbTest( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @PathVariable String id) { + String userId = resolveUserId(userIdHeader, authHeader); log.info("[Targeting] Evaluate AB Test for {}", id); TargetingCampaign campaign = campaignService.getCampaignById(id, userId); return ResponseEntity.ok(abTestingService.evaluateABTest(campaign)); @@ -104,7 +132,8 @@ public class TargetingCampaignController { @PostMapping("/audience/segment") public ResponseEntity> segmentAudience( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody Map body) { String analysisId = body.get("analysisId"); log.info("[Targeting] Segment audience for analysis {}", analysisId); @@ -114,7 +143,8 @@ public class TargetingCampaignController { @PostMapping("/audience/estimate") public ResponseEntity> estimateAudience( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestParam String platform, @RequestBody TargetingAudienceProfile profile) { log.info("[Targeting] Estimate audience for platform {}", platform); @@ -125,8 +155,10 @@ public class TargetingCampaignController { @PostMapping("/budget/optimize") public ResponseEntity optimizeBudget( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody Map body) { + String userId = resolveUserId(userIdHeader, authHeader); String campaignId = body.get("campaignId"); log.info("[Targeting] Optimize budget for campaign {}", campaignId); TargetingCampaign campaign = campaignService.getCampaignById(campaignId, userId); @@ -138,14 +170,16 @@ public class TargetingCampaignController { @PostMapping("/account/select") public ResponseEntity selectAdAccount( - @RequestHeader("X-User-Id") String userId, + @RequestHeader(value = "X-User-Id", required = false) String userIdHeader, + @RequestHeader(value = "Authorization", required = false) String authHeader, @RequestBody Map body) { + String userId = resolveUserId(userIdHeader, authHeader); String platform = body.get("platform"); String adAccountId = body.get("adAccountId"); - + log.info("[Targeting] Select ad account {} for platform {} by user {}", adAccountId, platform, userId); credentialsService.updateAdAccountId(userId, platform, adAccountId); - + return ResponseEntity.ok().build(); } diff --git a/src/main/java/kz/konturai/parser/service/TargetingCampaignService.java b/src/main/java/kz/konturai/parser/service/TargetingCampaignService.java index 3dbbdad..bcdb99d 100644 --- a/src/main/java/kz/konturai/parser/service/TargetingCampaignService.java +++ b/src/main/java/kz/konturai/parser/service/TargetingCampaignService.java @@ -410,25 +410,35 @@ public class TargetingCampaignService { } return rawPlatforms.stream() - .map(this::mapPlatform) + .flatMap(p -> expandPlatform(p).stream()) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); } - private kz.konturai.parser.enums.TargetingPlatform mapPlatform(String platform) { + private List expandPlatform(String platform) { if (platform == null || platform.isBlank()) { - return null; + return List.of(); } String normalized = platform.trim().toUpperCase(Locale.ROOT); + // FB_IG / META expand to both Facebook + Instagram + if ("FB_IG".equals(normalized) || "FB+IG".equals(normalized)) { + return List.of( + kz.konturai.parser.enums.TargetingPlatform.FACEBOOK, + kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM + ); + } if ("META".equals(normalized)) { - return kz.konturai.parser.enums.TargetingPlatform.FACEBOOK; + return List.of( + kz.konturai.parser.enums.TargetingPlatform.FACEBOOK, + kz.konturai.parser.enums.TargetingPlatform.INSTAGRAM + ); } try { - return kz.konturai.parser.enums.TargetingPlatform.valueOf(normalized); + return List.of(kz.konturai.parser.enums.TargetingPlatform.valueOf(normalized)); } catch (IllegalArgumentException ex) { log.warn("Unknown targeting platform '{}', skipping", platform); - return null; + return List.of(); } }