new endpoints for analys history
This commit is contained in:
@@ -3,10 +3,12 @@ package kz.konturai.parser.controller;
|
||||
import kz.konturai.parser.dto.*;
|
||||
import kz.konturai.parser.model.MarketingAnalysis;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.service.JwtService;
|
||||
import kz.konturai.parser.service.MarketingAnalysisService;
|
||||
import kz.konturai.parser.service.MarketingStrategyService;
|
||||
import kz.konturai.parser.service.MinIOService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
@@ -16,8 +18,10 @@ import jakarta.validation.Valid;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/marketing/analysis")
|
||||
@@ -26,22 +30,46 @@ public class MarketingController {
|
||||
private final MarketingAnalysisService marketingAnalysisService;
|
||||
private final MarketingStrategyService marketingStrategyService;
|
||||
private final MinIOService minIOService;
|
||||
private final JwtService jwtService;
|
||||
|
||||
public MarketingController(
|
||||
MarketingAnalysisService marketingAnalysisService,
|
||||
MarketingStrategyService marketingStrategyService,
|
||||
MinIOService minIOService) {
|
||||
MinIOService minIOService,
|
||||
JwtService jwtService) {
|
||||
this.marketingAnalysisService = marketingAnalysisService;
|
||||
this.marketingStrategyService = marketingStrategyService;
|
||||
this.minIOService = minIOService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
private String extractUserIdFromHeader(String authHeader) {
|
||||
if (authHeader == null || authHeader.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return jwtService.extractUserIdFromHeader(authHeader);
|
||||
}
|
||||
|
||||
private ResponseEntity<?> unauthorizedResponse() {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"UNAUTHORIZED",
|
||||
"Требуется аутентификация. Пожалуйста, предоставьте валидный JWT токен.");
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(ApiResponse.error("Не авторизован", error));
|
||||
}
|
||||
|
||||
@PostMapping("/start")
|
||||
public ResponseEntity<ApiResponse<MarketingAnalysisResponse>> startAnalysis(
|
||||
public ResponseEntity<?> startAnalysis(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@Valid @RequestBody MarketingAnalysisRequest request) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
// Create analysis record
|
||||
MarketingAnalysis analysis = marketingAnalysisService.startAnalysis(request);
|
||||
MarketingAnalysis analysis = marketingAnalysisService.startAnalysis(request, userId);
|
||||
|
||||
// Start async processing
|
||||
marketingAnalysisService.processAnalysis(analysis.getId(), request);
|
||||
@@ -60,11 +88,16 @@ public class MarketingController {
|
||||
|
||||
@GetMapping("/{analysisId}")
|
||||
public ResponseEntity<?> getAnalysis(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String analysisId) {
|
||||
|
||||
MarketingAnalysisResult result = marketingAnalysisService.getAnalysisResult(analysisId);
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Анализ с указанным ID не найден");
|
||||
@@ -72,31 +105,55 @@ public class MarketingController {
|
||||
.body(ApiResponse.error("Анализ не найден", error));
|
||||
}
|
||||
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
if (!userId.equals(analysis.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этому анализу");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
MarketingAnalysisResult result = marketingAnalysisService.getAnalysisResult(analysisId);
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@GetMapping("/{analysisId}/download")
|
||||
public ResponseEntity<byte[]> downloadPdf(@PathVariable String analysisId) {
|
||||
MarketingAnalysisResult result = marketingAnalysisService.getAnalysisResult(analysisId);
|
||||
public ResponseEntity<?> downloadPdf(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String analysisId) {
|
||||
|
||||
if (result == null || result.getReport() == null || result.getReport().getPdfUrl() == null) {
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// Get the analysis to find PDF filename
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty() || optAnalysis.get().getPdfFilePath() == null) {
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
if (!userId.equals(analysis.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этому анализу");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
if (analysis.getPdfFilePath() == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream inputStream = minIOService.downloadFile(optAnalysis.get().getPdfFilePath());
|
||||
InputStream inputStream = minIOService.downloadFile(analysis.getPdfFilePath());
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
inputStream.close();
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + optAnalysis.get().getPdfFilename() + "\"")
|
||||
"attachment; filename=\"" + analysis.getPdfFilename() + "\"")
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(bytes);
|
||||
} catch (Exception e) {
|
||||
@@ -106,15 +163,38 @@ public class MarketingController {
|
||||
|
||||
@PostMapping("/strategy/generate")
|
||||
public ResponseEntity<?> generateStrategy(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestParam String analysisId,
|
||||
@Valid @RequestBody(required = false) MarketingStrategyRequest request) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
// Check if user owns the analysis
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", "Анализ не найден");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Анализ не найден", error));
|
||||
}
|
||||
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
if (!userId.equals(analysis.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этому анализу");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
if (request == null) {
|
||||
request = new MarketingStrategyRequest();
|
||||
}
|
||||
|
||||
try {
|
||||
MarketingStrategy strategy = marketingStrategyService.generateStrategy(analysisId, request);
|
||||
MarketingStrategy strategy = marketingStrategyService.generateStrategy(analysisId, request, userId);
|
||||
|
||||
MarketingStrategyResponse response = new MarketingStrategyResponse();
|
||||
response.setStrategyId(strategy.getId());
|
||||
@@ -145,10 +225,17 @@ public class MarketingController {
|
||||
}
|
||||
|
||||
@GetMapping("/strategy/{strategyId}")
|
||||
public ResponseEntity<?> getStrategy(@PathVariable String strategyId) {
|
||||
MarketingStrategyResponse result = marketingStrategyService.getStrategyResult(strategyId);
|
||||
public ResponseEntity<?> getStrategy(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String strategyId) {
|
||||
|
||||
if (result == null) {
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
|
||||
if (optStrategy.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Стратегия с указанным ID не найдена");
|
||||
@@ -156,11 +243,48 @@ public class MarketingController {
|
||||
.body(ApiResponse.error("Стратегия не найдена", error));
|
||||
}
|
||||
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
if (!userId.equals(strategy.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этой стратегии");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
MarketingStrategyResponse result = marketingStrategyService.getStrategyResult(strategyId);
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@GetMapping("/{analysisId}/strategy")
|
||||
public ResponseEntity<?> getStrategyByAnalysis(@PathVariable String analysisId) {
|
||||
public ResponseEntity<?> getStrategyByAnalysis(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String analysisId) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
// Check if user owns the analysis
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Анализ с указанным ID не найден");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Анализ не найден", error));
|
||||
}
|
||||
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
if (!userId.equals(analysis.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этому анализу");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
MarketingStrategyResponse result = marketingStrategyService.getStrategyByAnalysisId(analysisId);
|
||||
|
||||
if (result == null) {
|
||||
@@ -174,6 +298,133 @@ public class MarketingController {
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@GetMapping("/my")
|
||||
public ResponseEntity<?> getMyAnalyses(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
List<MarketingAnalysis> analyses = marketingAnalysisService.getUserAnalyses(userId);
|
||||
List<AnalysisHistoryResponse> responseList = analyses.stream()
|
||||
.map(this::convertToHistoryResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(responseList));
|
||||
}
|
||||
|
||||
@GetMapping("/strategy/my")
|
||||
public ResponseEntity<?> getMyStrategies(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
List<MarketingStrategy> strategies = marketingStrategyService.getUserStrategies(userId);
|
||||
List<StrategyHistoryResponse> responseList = strategies.stream()
|
||||
.map(this::convertToStrategyHistoryResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(responseList));
|
||||
}
|
||||
|
||||
@GetMapping("/{analysisId}/history")
|
||||
public ResponseEntity<?> getAnalysisHistory(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String analysisId) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
Optional<MarketingAnalysis> optAnalysis = marketingAnalysisService.getAnalysisById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Анализ с указанным ID не найден");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Анализ не найден", error));
|
||||
}
|
||||
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
if (!userId.equals(analysis.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этому анализу");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
AnalysisHistoryResponse response = convertToHistoryResponse(analysis);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
|
||||
@GetMapping("/strategy/{strategyId}/history")
|
||||
public ResponseEntity<?> getStrategyHistory(
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable String strategyId) {
|
||||
|
||||
String userId = extractUserIdFromHeader(authHeader);
|
||||
if (userId == null) {
|
||||
return unauthorizedResponse();
|
||||
}
|
||||
|
||||
Optional<MarketingStrategy> optStrategy = marketingStrategyService.getStrategyById(strategyId);
|
||||
if (optStrategy.isEmpty()) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Стратегия с указанным ID не найдена");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Стратегия не найдена", error));
|
||||
}
|
||||
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
if (!userId.equals(strategy.getUserId())) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"FORBIDDEN",
|
||||
"У вас нет доступа к этой стратегии");
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(ApiResponse.error("Доступ запрещен", error));
|
||||
}
|
||||
|
||||
StrategyHistoryResponse response = convertToStrategyHistoryResponse(strategy);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
|
||||
private AnalysisHistoryResponse convertToHistoryResponse(MarketingAnalysis analysis) {
|
||||
AnalysisHistoryResponse response = new AnalysisHistoryResponse();
|
||||
response.setAnalysisId(analysis.getId());
|
||||
response.setProduct(analysis.getProduct());
|
||||
response.setLocation(analysis.getLocation());
|
||||
response.setClientType(analysis.getClientType());
|
||||
response.setDifferentiator(analysis.getDifferentiator());
|
||||
response.setStatus(analysis.getStatus());
|
||||
response.setUserId(analysis.getUserId());
|
||||
response.setCreatedAt(analysis.getCreatedAt());
|
||||
response.setCompletedAt(analysis.getCompletedAt());
|
||||
response.setStatusHistory(analysis.getStatusHistory());
|
||||
return response;
|
||||
}
|
||||
|
||||
private StrategyHistoryResponse convertToStrategyHistoryResponse(MarketingStrategy strategy) {
|
||||
StrategyHistoryResponse response = new StrategyHistoryResponse();
|
||||
response.setStrategyId(strategy.getId());
|
||||
response.setAnalysisId(strategy.getAnalysisId());
|
||||
response.setStatus(strategy.getStatus());
|
||||
response.setUserId(strategy.getUserId());
|
||||
response.setDurationWeeks(strategy.getDurationWeeks());
|
||||
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
|
||||
response.setCreatedAt(strategy.getCreatedAt());
|
||||
response.setCompletedAt(strategy.getCompletedAt());
|
||||
response.setStatusHistory(strategy.getStatusHistory());
|
||||
return response;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<ErrorResponse>> handleValidationException(
|
||||
MethodArgumentNotValidException ex) {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class AnalysisHistoryResponse {
|
||||
private String analysisId;
|
||||
private String product;
|
||||
private String location;
|
||||
private String clientType;
|
||||
private String differentiator;
|
||||
private String status;
|
||||
private String userId;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime completedAt;
|
||||
private List<StatusHistoryEntry> statusHistory;
|
||||
|
||||
public AnalysisHistoryResponse() {
|
||||
}
|
||||
|
||||
public String getAnalysisId() {
|
||||
return analysisId;
|
||||
}
|
||||
|
||||
public void setAnalysisId(String analysisId) {
|
||||
this.analysisId = analysisId;
|
||||
}
|
||||
|
||||
public String getProduct() {
|
||||
return product;
|
||||
}
|
||||
|
||||
public void setProduct(String product) {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getClientType() {
|
||||
return clientType;
|
||||
}
|
||||
|
||||
public void setClientType(String clientType) {
|
||||
this.clientType = clientType;
|
||||
}
|
||||
|
||||
public String getDifferentiator() {
|
||||
return differentiator;
|
||||
}
|
||||
|
||||
public void setDifferentiator(String differentiator) {
|
||||
this.differentiator = differentiator;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getCompletedAt() {
|
||||
return completedAt;
|
||||
}
|
||||
|
||||
public void setCompletedAt(LocalDateTime completedAt) {
|
||||
this.completedAt = completedAt;
|
||||
}
|
||||
|
||||
public List<StatusHistoryEntry> getStatusHistory() {
|
||||
return statusHistory;
|
||||
}
|
||||
|
||||
public void setStatusHistory(List<StatusHistoryEntry> statusHistory) {
|
||||
this.statusHistory = statusHistory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class StatusHistoryEntry {
|
||||
private String status;
|
||||
private LocalDateTime timestamp;
|
||||
private String message;
|
||||
|
||||
public StatusHistoryEntry() {
|
||||
}
|
||||
|
||||
public StatusHistoryEntry(String status, LocalDateTime timestamp) {
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public StatusHistoryEntry(String status, LocalDateTime timestamp, String message) {
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(LocalDateTime timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class StrategyHistoryResponse {
|
||||
private String strategyId;
|
||||
private String analysisId;
|
||||
private String status;
|
||||
private String userId;
|
||||
private Integer durationWeeks;
|
||||
private List<String> priorityPlatforms;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime completedAt;
|
||||
private List<StatusHistoryEntry> statusHistory;
|
||||
|
||||
public StrategyHistoryResponse() {
|
||||
}
|
||||
|
||||
public String getStrategyId() {
|
||||
return strategyId;
|
||||
}
|
||||
|
||||
public void setStrategyId(String strategyId) {
|
||||
this.strategyId = strategyId;
|
||||
}
|
||||
|
||||
public String getAnalysisId() {
|
||||
return analysisId;
|
||||
}
|
||||
|
||||
public void setAnalysisId(String analysisId) {
|
||||
this.analysisId = analysisId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Integer getDurationWeeks() {
|
||||
return durationWeeks;
|
||||
}
|
||||
|
||||
public void setDurationWeeks(Integer durationWeeks) {
|
||||
this.durationWeeks = durationWeeks;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getCompletedAt() {
|
||||
return completedAt;
|
||||
}
|
||||
|
||||
public void setCompletedAt(LocalDateTime completedAt) {
|
||||
this.completedAt = completedAt;
|
||||
}
|
||||
|
||||
public List<StatusHistoryEntry> getStatusHistory() {
|
||||
return statusHistory;
|
||||
}
|
||||
|
||||
public void setStatusHistory(List<StatusHistoryEntry> statusHistory) {
|
||||
this.statusHistory = statusHistory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package kz.konturai.parser.model;
|
||||
|
||||
import kz.konturai.parser.dto.StatusHistoryEntry;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Document(collection = "marketing_analysis")
|
||||
@@ -46,9 +49,13 @@ public class MarketingAnalysis {
|
||||
@Field("user_id")
|
||||
private String userId; // Optional, for future authentication
|
||||
|
||||
@Field("status_history")
|
||||
private List<StatusHistoryEntry> statusHistory;
|
||||
|
||||
public MarketingAnalysis() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.status = "queued";
|
||||
this.statusHistory = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MarketingAnalysis(String product, String location, String clientType, String differentiator) {
|
||||
@@ -154,4 +161,12 @@ public class MarketingAnalysis {
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public List<StatusHistoryEntry> getStatusHistory() {
|
||||
return statusHistory;
|
||||
}
|
||||
|
||||
public void setStatusHistory(List<StatusHistoryEntry> statusHistory) {
|
||||
this.statusHistory = statusHistory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package kz.konturai.parser.model;
|
||||
|
||||
import kz.konturai.parser.dto.StatusHistoryEntry;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -41,9 +43,16 @@ public class MarketingStrategy {
|
||||
@Field("strategy_data")
|
||||
private Map<String, Object> strategyData; // JSON data with full strategy content
|
||||
|
||||
@Field("user_id")
|
||||
private String userId;
|
||||
|
||||
@Field("status_history")
|
||||
private List<StatusHistoryEntry> statusHistory;
|
||||
|
||||
public MarketingStrategy() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.status = "queued";
|
||||
this.statusHistory = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MarketingStrategy(String analysisId) {
|
||||
@@ -131,6 +140,22 @@ public class MarketingStrategy {
|
||||
this.strategyData = strategyData;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public List<StatusHistoryEntry> getStatusHistory() {
|
||||
return statusHistory;
|
||||
}
|
||||
|
||||
public void setStatusHistory(List<StatusHistoryEntry> statusHistory) {
|
||||
this.statusHistory = statusHistory;
|
||||
}
|
||||
|
||||
public static class WeeklyPlan {
|
||||
@Field("week_number")
|
||||
private Integer weekNumber;
|
||||
|
||||
@@ -4,9 +4,11 @@ import kz.konturai.parser.model.MarketingAnalysis;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MarketingAnalysisRepository extends MongoRepository<MarketingAnalysis, String> {
|
||||
Optional<MarketingAnalysis> findById(String id);
|
||||
List<MarketingAnalysis> findByUserIdOrderByCreatedAtDesc(String userId);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import kz.konturai.parser.model.MarketingStrategy;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MarketingStrategyRepository extends MongoRepository<MarketingStrategy, String> {
|
||||
Optional<MarketingStrategy> findById(String id);
|
||||
Optional<MarketingStrategy> findByAnalysisId(String analysisId);
|
||||
List<MarketingStrategy> findByUserIdOrderByCreatedAtDesc(String userId);
|
||||
List<MarketingStrategy> findByUserIdAndAnalysisId(String userId, String analysisId);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
|
||||
private final SecretKey signingKey;
|
||||
|
||||
public JwtService(
|
||||
@Value("${security.jwt.secret-base64:}") String base64Secret) {
|
||||
if (StringUtils.hasText(base64Secret)) {
|
||||
this.signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret));
|
||||
} else {
|
||||
// Если секрет не настроен, создаем временный ключ (для разработки)
|
||||
// В продакшене это должно быть обязательно настроено
|
||||
logger.warn("JWT secret not configured, using default key. This should be configured in production!");
|
||||
String defaultSecret = "dGVzdC1zZWNyZXQta2V5LWZvci1kZXZlbG9wbWVudC1vbmx5LWRvLW5vdC11c2UtaW4tcHJvZHVjdGlvbg==";
|
||||
this.signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(defaultSecret));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит и валидирует JWT токен
|
||||
*
|
||||
* @param token JWT токен
|
||||
* @return Claims из токена
|
||||
* @throws JwtException если токен невалиден или истёк
|
||||
*/
|
||||
public Claims parseAndValidate(String token) throws JwtException {
|
||||
return Jwts.parser()
|
||||
.verifyWith(signingKey)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает JWT токен из заголовка Authorization
|
||||
*
|
||||
* @param authHeader значение заголовка Authorization (например, "Bearer
|
||||
* <token>")
|
||||
* @return JWT токен или null если не найден
|
||||
*/
|
||||
public String extractTokenFromHeader(String authHeader) {
|
||||
if (!StringUtils.hasText(authHeader)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authHeader.startsWith("Bearer ")) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
|
||||
return authHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает userId (uid) из JWT токена
|
||||
* Согласно документации, userId находится в поле "uid" как Long
|
||||
*
|
||||
* Структура JWT токена:
|
||||
* - sub: Email пользователя
|
||||
* - uid: ID пользователя (Long)
|
||||
* - roles: Роли пользователя (String, разделённые запятыми)
|
||||
*
|
||||
* @param token JWT токен
|
||||
* @return userId как String или null если не найден
|
||||
*/
|
||||
public String extractUserIdFromToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Claims claims = parseAndValidate(token);
|
||||
|
||||
// Основной способ: извлекаем uid (Long) из claims согласно документации
|
||||
Long uid = claims.get("uid", Long.class);
|
||||
if (uid != null) {
|
||||
return String.valueOf(uid);
|
||||
}
|
||||
|
||||
// Fallback: пробуем другие поля, если uid не найден (для совместимости)
|
||||
if (claims.get("userId") != null) {
|
||||
logger.debug("Using 'userId' field as fallback");
|
||||
return String.valueOf(claims.get("userId"));
|
||||
}
|
||||
if (claims.get("id") != null) {
|
||||
logger.debug("Using 'id' field as fallback");
|
||||
return String.valueOf(claims.get("id"));
|
||||
}
|
||||
if (claims.get("user_id") != null) {
|
||||
logger.debug("Using 'user_id' field as fallback");
|
||||
return String.valueOf(claims.get("user_id"));
|
||||
}
|
||||
|
||||
// Если ничего не найдено, пробуем subject (email) как последний вариант
|
||||
// (не рекомендуется, но может быть полезно для обратной совместимости)
|
||||
String subject = claims.getSubject();
|
||||
if (subject != null && !subject.isEmpty()) {
|
||||
logger.warn("Using subject (email) as userId fallback. Token should contain 'uid' field: {}", subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
logger.warn("No userId field (uid) found in JWT token");
|
||||
return null;
|
||||
|
||||
} catch (io.jsonwebtoken.ExpiredJwtException e) {
|
||||
logger.error("JWT token has expired: {}", e.getMessage());
|
||||
return null;
|
||||
} catch (io.jsonwebtoken.MalformedJwtException e) {
|
||||
logger.error("Malformed JWT token: {}", e.getMessage());
|
||||
return null;
|
||||
} catch (io.jsonwebtoken.security.SignatureException e) {
|
||||
logger.error("Invalid JWT signature: {}", e.getMessage());
|
||||
return null;
|
||||
} catch (JwtException e) {
|
||||
logger.error("Error parsing/validating JWT token: {}", e.getMessage());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Unexpected error parsing JWT token: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает userId из заголовка Authorization
|
||||
*
|
||||
* @param authHeader значение заголовка Authorization
|
||||
* @return userId или null если не найден
|
||||
*/
|
||||
public String extractUserIdFromHeader(String authHeader) {
|
||||
String token = extractTokenFromHeader(authHeader);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
return extractUserIdFromToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает email (subject) из JWT токена
|
||||
*
|
||||
* @param token JWT токен
|
||||
* @return email или null если не найден
|
||||
*/
|
||||
public String extractEmailFromToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Claims claims = parseAndValidate(token);
|
||||
return claims.getSubject();
|
||||
} catch (JwtException e) {
|
||||
logger.error("Error parsing/validating JWT token: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает роли из JWT токена
|
||||
*
|
||||
* @param token JWT токен
|
||||
* @return список ролей или пустой список
|
||||
*/
|
||||
public java.util.List<String> extractRolesFromToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return java.util.List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
Claims claims = parseAndValidate(token);
|
||||
String rolesString = claims.get("roles", String.class);
|
||||
|
||||
if (rolesString == null || rolesString.isBlank()) {
|
||||
return java.util.List.of();
|
||||
}
|
||||
|
||||
return java.util.Arrays.stream(rolesString.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} catch (JwtException e) {
|
||||
logger.error("Error parsing/validating JWT token: {}", e.getMessage());
|
||||
return java.util.List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package kz.konturai.parser.service;
|
||||
|
||||
import kz.konturai.parser.dto.MarketingAnalysisRequest;
|
||||
import kz.konturai.parser.dto.MarketingAnalysisResult;
|
||||
import kz.konturai.parser.dto.StatusHistoryEntry;
|
||||
import kz.konturai.parser.model.MarketingAnalysis;
|
||||
import kz.konturai.parser.repository.MarketingAnalysisRepository;
|
||||
import org.slf4j.Logger;
|
||||
@@ -34,18 +35,28 @@ public class MarketingAnalysisService {
|
||||
this.researchPdfService = researchPdfService;
|
||||
}
|
||||
|
||||
public MarketingAnalysis startAnalysis(MarketingAnalysisRequest request) {
|
||||
public MarketingAnalysis startAnalysis(MarketingAnalysisRequest request, String userId) {
|
||||
MarketingAnalysis analysis = new MarketingAnalysis(
|
||||
request.getProduct(),
|
||||
request.getLocation(),
|
||||
request.getClient(),
|
||||
request.getDifferentiator());
|
||||
analysis.setUserId(userId);
|
||||
analysis.setStatus("queued");
|
||||
addStatusHistoryEntry(analysis, "queued", "Анализ создан и добавлен в очередь");
|
||||
analysis = repository.save(analysis);
|
||||
logger.info("Marketing analysis created with ID: {}", analysis.getId());
|
||||
logger.info("Marketing analysis created with ID: {} for user: {}", analysis.getId(), userId);
|
||||
return analysis;
|
||||
}
|
||||
|
||||
private void addStatusHistoryEntry(MarketingAnalysis analysis, String status, String message) {
|
||||
if (analysis.getStatusHistory() == null) {
|
||||
analysis.setStatusHistory(new ArrayList<>());
|
||||
}
|
||||
StatusHistoryEntry entry = new StatusHistoryEntry(status, LocalDateTime.now(), message);
|
||||
analysis.getStatusHistory().add(entry);
|
||||
}
|
||||
|
||||
@Async("reportGenerationExecutor")
|
||||
public void processAnalysis(String analysisId, MarketingAnalysisRequest request) {
|
||||
try {
|
||||
@@ -57,6 +68,7 @@ public class MarketingAnalysisService {
|
||||
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
analysis.setStatus("processing");
|
||||
addStatusHistoryEntry(analysis, "processing", "Начата обработка анализа");
|
||||
repository.save(analysis);
|
||||
|
||||
logger.info("Starting marketing analysis processing for ID: {}", analysisId);
|
||||
@@ -83,6 +95,7 @@ public class MarketingAnalysisService {
|
||||
analysis.setReportData(reportData);
|
||||
analysis.setPdfFilename(filename);
|
||||
analysis.setPdfFilePath(filename);
|
||||
addStatusHistoryEntry(analysis, "completed", "Анализ успешно завершен");
|
||||
repository.save(analysis);
|
||||
|
||||
logger.info("Marketing analysis completed successfully for ID: {}", analysisId);
|
||||
@@ -94,6 +107,7 @@ public class MarketingAnalysisService {
|
||||
if (optAnalysis.isPresent()) {
|
||||
MarketingAnalysis analysis = optAnalysis.get();
|
||||
analysis.setStatus("failed");
|
||||
addStatusHistoryEntry(analysis, "failed", "Ошибка при обработке: " + e.getMessage());
|
||||
repository.save(analysis);
|
||||
}
|
||||
} catch (Exception saveError) {
|
||||
@@ -350,6 +364,10 @@ public class MarketingAnalysisService {
|
||||
return repository.findById(analysisId);
|
||||
}
|
||||
|
||||
public List<MarketingAnalysis> getUserAnalyses(String userId) {
|
||||
return repository.findByUserIdOrderByCreatedAtDesc(userId);
|
||||
}
|
||||
|
||||
private MarketingAnalysisResult.MarketingReport buildReportFromData(Map<String, Object> reportData,
|
||||
MarketingAnalysis analysis) {
|
||||
MarketingAnalysisResult.MarketingReport report = new MarketingAnalysisResult.MarketingReport();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kz.konturai.parser.dto.MarketingAnalysisResult;
|
||||
import kz.konturai.parser.dto.MarketingStrategyRequest;
|
||||
import kz.konturai.parser.dto.MarketingStrategyResponse;
|
||||
import kz.konturai.parser.dto.StatusHistoryEntry;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.repository.MarketingStrategyRepository;
|
||||
import org.slf4j.Logger;
|
||||
@@ -36,7 +37,7 @@ public class MarketingStrategyService {
|
||||
this.openAIAnalyticsService = openAIAnalyticsService;
|
||||
}
|
||||
|
||||
public MarketingStrategy generateStrategy(String analysisId, MarketingStrategyRequest request) {
|
||||
public MarketingStrategy generateStrategy(String analysisId, MarketingStrategyRequest request, String userId) {
|
||||
// Check if analysis exists and is completed
|
||||
MarketingAnalysisResult analysisResult = marketingAnalysisService.getAnalysisResult(analysisId);
|
||||
if (analysisResult == null) {
|
||||
@@ -55,12 +56,14 @@ public class MarketingStrategyService {
|
||||
|
||||
// Create new strategy
|
||||
MarketingStrategy strategy = new MarketingStrategy(analysisId);
|
||||
strategy.setUserId(userId);
|
||||
strategy.setDurationWeeks(request.getDurationWeeks() != null ? request.getDurationWeeks() : 4);
|
||||
strategy.setPriorityPlatforms(request.getPriorityPlatforms());
|
||||
strategy.setStatus("queued");
|
||||
addStatusHistoryEntry(strategy, "queued", "Стратегия создана и добавлена в очередь");
|
||||
strategy = repository.save(strategy);
|
||||
|
||||
logger.info("Marketing strategy created with ID: {}", strategy.getId());
|
||||
logger.info("Marketing strategy created with ID: {} for user: {}", strategy.getId(), userId);
|
||||
|
||||
// Start async processing
|
||||
processStrategyGeneration(strategy.getId(), analysisId, analysisResult);
|
||||
@@ -68,6 +71,14 @@ public class MarketingStrategyService {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
private void addStatusHistoryEntry(MarketingStrategy strategy, String status, String message) {
|
||||
if (strategy.getStatusHistory() == null) {
|
||||
strategy.setStatusHistory(new ArrayList<>());
|
||||
}
|
||||
StatusHistoryEntry entry = new StatusHistoryEntry(status, LocalDateTime.now(), message);
|
||||
strategy.getStatusHistory().add(entry);
|
||||
}
|
||||
|
||||
@Async("reportGenerationExecutor")
|
||||
public void processStrategyGeneration(String strategyId, String analysisId, MarketingAnalysisResult analysisResult) {
|
||||
try {
|
||||
@@ -79,6 +90,7 @@ public class MarketingStrategyService {
|
||||
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
strategy.setStatus("processing");
|
||||
addStatusHistoryEntry(strategy, "processing", "Начата генерация стратегии");
|
||||
repository.save(strategy);
|
||||
|
||||
logger.info("Starting marketing strategy generation for ID: {}", strategyId);
|
||||
@@ -106,6 +118,7 @@ public class MarketingStrategyService {
|
||||
strategyData.put("postCalendar", postCalendar);
|
||||
strategy.setStrategyData(strategyData);
|
||||
|
||||
addStatusHistoryEntry(strategy, "completed", "Стратегия успешно сгенерирована");
|
||||
repository.save(strategy);
|
||||
|
||||
logger.info("Marketing strategy generation completed successfully for ID: {}", strategyId);
|
||||
@@ -117,6 +130,7 @@ public class MarketingStrategyService {
|
||||
if (optStrategy.isPresent()) {
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
strategy.setStatus("failed");
|
||||
addStatusHistoryEntry(strategy, "failed", "Ошибка при генерации стратегии: " + e.getMessage());
|
||||
repository.save(strategy);
|
||||
}
|
||||
} catch (Exception saveError) {
|
||||
@@ -497,5 +511,13 @@ public class MarketingStrategyService {
|
||||
|
||||
return getStrategyResult(optStrategy.get().getId());
|
||||
}
|
||||
|
||||
public List<MarketingStrategy> getUserStrategies(String userId) {
|
||||
return repository.findByUserIdOrderByCreatedAtDesc(userId);
|
||||
}
|
||||
|
||||
public Optional<MarketingStrategy> getStrategyById(String strategyId) {
|
||||
return repository.findById(strategyId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,3 +77,8 @@ spring.mail.properties.mail.smtp.starttls.required=true
|
||||
# Deep Research API Configuration
|
||||
deep-research.api.url=http://185.35.223.45:3051
|
||||
deep-research.api.timeout=1800000
|
||||
|
||||
# JWT Configuration
|
||||
# Используйте тот же secret-base64, что и в сервисе, выдающем токены
|
||||
# Формат: base64-encoded secret key
|
||||
security.jwt.secret-base64=ZmFrZV9zZWNyZXRfMTIzNDU2Nzg5MGFiY2RlZmFrZV9zZWNyZXRfMTIzNDU2Nzg5MGFiY2Rl
|
||||
|
||||
Reference in New Issue
Block a user