dev add token spent logic
Test Workflow / test (push) Successful in 4s

This commit is contained in:
vladp
2026-04-10 20:13:59 +07:00
parent c1360fce87
commit aa6c0c1b94
18 changed files with 820 additions and 9 deletions
@@ -37,7 +37,8 @@ public class HandlerConfig {
UserInfoHandler userInfoHandler,
NotificationHandler notificationHandler,
UserVerificationHandler userVerificationHandler,
LawCaseHandler lawCaseHandler
LawCaseHandler lawCaseHandler,
TokenOperationHandler tokenOperationHandler
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
@@ -66,6 +67,7 @@ public class HandlerConfig {
map.put(30015, notificationHandler);
map.put(30016, userVerificationHandler);
map.put(30017, lawCaseHandler);
map.put(30018, tokenOperationHandler);
return map;
}
@@ -70,7 +70,7 @@ public class PaymentController {
}
paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal disabled"));
return ResponseEntity.ok(Map.of("message", "Auto-renewal changed:" + renewal));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
@@ -48,6 +48,7 @@ public enum MessageCode {
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"),
OPERATION_NOT_FOUND(4, "Operation not found"),
INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"),
@@ -0,0 +1,34 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Data
@Builder
public class TokenOperationPageDto {
@JsonProperty("content")
private List<TokenOperation> content;
@JsonProperty("page_number")
private int pageNumber;
@JsonProperty("page_size")
private int pageSize;
@JsonProperty("total_elements")
private long totalElements;
@JsonProperty("total_pages")
private int totalPages;
@JsonProperty("is_last")
private boolean last;
@JsonProperty("is_first")
private boolean first;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
@Data
public class TokenOperationRequest {
@JsonProperty("action")
private String action;
@JsonProperty("token")
private String token;
@JsonProperty("operation_id")
private Long operationId;
@JsonProperty("operation_type")
private OperationType operationType;
@JsonProperty("spent")
private Long spent;
@JsonProperty("page")
private Integer page = 0;
@JsonProperty("size")
private Integer size = 20;
@JsonProperty("sort_by")
private String sortBy = "createdAt";
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("min_spent")
private Long minSpent;
@JsonProperty("max_spent")
private Long maxSpent;
@JsonProperty("updates")
private Object updates;
}
@@ -0,0 +1,37 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TokenOperationResponse {
@JsonProperty("success")
private Boolean success;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("total_elements")
private Long totalElements;
@JsonProperty("total_pages")
private Integer totalPages;
@JsonProperty("current_page")
private Integer currentPage;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("statistics")
private Map<String, Object> statistics;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.entity.tokenoperation;
public enum OperationType {
SEARCH("search"), MONITORING("monitoring"), CREATE_LEGAL_CASE("legal_case"),
FILE_UPLOAD("file_upload"), GLOBAL_SEARCH("global_search");
private final String name;
private OperationType(String name) {
this.name = name;
}
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.entity.tokenoperation;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Table(name = "token_operation")
@Entity
@Setter @Getter
@NoArgsConstructor
public class TokenOperation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "operation_type")
private OperationType operationType;
@Column(name = "user_id")
private Long userId;
@Column(name = "spent")
private Long spent;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
}
@@ -11,6 +11,7 @@ import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ImageSearchRequest;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
@@ -117,7 +118,8 @@ public class ImageFoundRequestHandler implements RequestHandler {
log.info("Results only from Yandex: {} images", allUniqueImages.size());
}
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH);
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH,
OperationType.SEARCH);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
@@ -15,6 +15,7 @@ import ru.soune.nocopy.dto.complaint.PaginatedResponse;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.TariffNotFoundException;
@@ -114,7 +115,7 @@ public class LawCaseHandler implements RequestHandler {
private BaseResponse createLawCase(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
try {
tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST);
tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST, OperationType.CREATE_LEGAL_CASE);
} catch (TariffNotFoundException e) {
return new BaseResponse(request.getMsgId(), MessageCode.USER_NOT_HAVE_TOKEN.getCode(),
MessageCode.USER_NOT_HAVE_TOKEN.getDescription(),
@@ -0,0 +1,472 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationPageDto;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationRequest;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenOperationHandler implements RequestHandler {
private final TokenOperationService tokenOperationService;
private final ObjectMapper objectMapper;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
log.info("Handling TokenOperation request: msg_id={}, version={}",
request.getMsgId(), request.getVersion());
try {
TokenOperationRequest tokenRequest = objectMapper.convertValue(
request.getMessageBody(),
TokenOperationRequest.class
);
String action = tokenRequest.getAction();
if (action == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("error", "Action is required")
);
}
switch (action.toLowerCase()) {
case "get_list":
return handleGetList(request.getMsgId(), tokenRequest);
case "get_by_id":
return handleGetById(request.getMsgId(), tokenRequest);
case "get_by_user":
return handleGetByUser(request.getMsgId(), tokenRequest);
case "create":
return handleCreate(request.getMsgId(), tokenRequest);
case "update":
return handleUpdate(request.getMsgId(), tokenRequest);
case "delete":
return handleDelete(request.getMsgId(), tokenRequest);
case "delete_all_by_user":
return handleDeleteAllByUser(request.getMsgId(), tokenRequest);
case "get_stats":
return handleGetStats(request.getMsgId(), tokenRequest);
case "filter":
return handleFilter(request.getMsgId(), tokenRequest);
default:
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("available_actions",
"get_list, get_by_id, get_by_user, create, update, " +
"delete, delete_all_by_user, get_stats, filter")
);
}
} catch (Exception e) {
log.error("Error handling TokenOperation request", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetList(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByUserId(
userId,
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error getting token operations list", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetById(Integer msgId, TokenOperationRequest request) {
log.info("Getting token operation by id: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation operation = tokenOperationService.findById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error getting token operation by id", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetByUser(Integer msgId, TokenOperationRequest request) {
return handleGetList(msgId, request);
}
private BaseResponse handleCreate(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
TokenOperation operation = tokenOperationService.create(
request.getOperationType(),
userId,
request.getSpent()
);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (Exception e) {
log.error("Error creating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error creating operation: " + e.getMessage())
);
}
}
private BaseResponse handleUpdate(Integer msgId, TokenOperationRequest request) {
log.info("Updating token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation existing = tokenOperationService.findById(request.getOperationId());
if (request.getOperationType() != null) {
existing.setOperationType(request.getOperationType());
}
if (request.getSpent() != null) {
existing.setSpent(request.getSpent());
}
TokenOperation updated = tokenOperationService.update(request.getOperationId(), existing);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
updated
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error updating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error updating operation: " + e.getMessage())
);
}
}
private BaseResponse handleDelete(Integer msgId, TokenOperationRequest request) {
log.info("Deleting token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
tokenOperationService.deleteById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "Operation deleted successfully",
"operation_id", request.getOperationId())
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error deleting token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operation: " + e.getMessage())
);
}
}
private BaseResponse handleDeleteAllByUser(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
tokenOperationService.deleteByUserId(userId);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "All operations deleted successfully for user",
"user_id", userId)
);
} catch (Exception e) {
log.error("Error deleting all operations for user", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetStats(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
if (userId == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
try {
Map<String, Long> statsByType = new HashMap<>();
long totalSpent = 0L;
for (OperationType type : OperationType.values()) {
Long spent = tokenOperationService.getTotalSpentByUserAndType(userId, type);
statsByType.put(type.name().toLowerCase(), spent != null ? spent : 0L);
totalSpent += spent != null ? spent : 0L;
}
Map<String, Object> statistics = new HashMap<>();
statistics.put("user_id", userId);
statistics.put("total_spent", totalSpent);
statistics.put("total_operations", tokenOperationService.countByUserId(userId));
statistics.put("stats_by_type", statsByType);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
} catch (Exception e) {
log.error("Error getting statistics", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting statistics: " + e.getMessage())
);
}
}
private BaseResponse handleFilter(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByFilters(
userId,
request.getOperationType(),
request.getMinSpent(),
request.getMaxSpent(),
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20,
request.getSortBy() != null ? request.getSortBy() : "createdAt",
request.getSortDirection() != null ? request.getSortDirection() : "desc"
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
responseData.put("applied_filters", Map.of(
"user_id",userId != null ? userId : "all",
"operation_type", request.getOperationType() != null ? request.getOperationType() : "all",
"min_spent", request.getMinSpent() != null ? request.getMinSpent() : "none",
"max_spent", request.getMaxSpent() != null ? request.getMaxSpent() : "none"
));
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error filtering operations", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error filtering operations: " + e.getMessage())
);
}
}
}
@@ -0,0 +1,41 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Repository
public interface TokenOperationRepository extends JpaRepository<TokenOperation, Long> {
Page<TokenOperation> findAll(Pageable pageable);
List<TokenOperation> findByUserId(Long userId);
Page<TokenOperation> findByUserId(Long userId, Pageable pageable);
Page<TokenOperation> findByOperationType(OperationType operationType, Pageable pageable);
@Query("SELECT t FROM TokenOperation t WHERE " +
"(:userId IS NULL OR t.userId = :userId) AND " +
"(:operationType IS NULL OR t.operationType = :operationType) AND " +
"(:minSpent IS NULL OR t.spent >= :minSpent) AND " +
"(:maxSpent IS NULL OR t.spent <= :maxSpent)")
Page<TokenOperation> findByFilters(
@Param("userId") Long userId,
@Param("operationType") OperationType operationType,
@Param("minSpent") Long minSpent,
@Param("maxSpent") Long maxSpent,
Pageable pageable
);
@Query("SELECT SUM(t.spent) FROM TokenOperation t WHERE t.userId = :userId AND t.operationType = :operationType")
Long sumSpentByUserAndType(@Param("userId") Long userId, @Param("operationType") OperationType operationType);
Long countByUserId(Long userId);
}
@@ -18,6 +18,7 @@ import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.file.UploadProgressResponse;
import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileUploadSessionRepository;
@@ -441,7 +442,7 @@ public class FileUploadServiceImpl implements FileUploadService {
if (status != FileStatus.TEMP) {
String fileType = session.getFileType();
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType));
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType), OperationType.FILE_UPLOAD);
}
} else {
sessionRepository.save(session);
@@ -15,6 +15,7 @@ import ru.soune.nocopy.entity.notification.NotificationMessage;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.repository.FileMonitoringRepository;
@@ -66,7 +67,7 @@ public class MonitoringSearchService {
TariffDTO tariffMonitoring = tariffService.getTariffByType(TariffType.valueOf(monitoringType.name()));
try {
tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens());
tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens(), OperationType.MONITORING);
boolean useYandex = searchProperties.getEngines().getOrDefault("yandex",
new SearchProperties.EngineConfig()).isEnabled();
@@ -12,6 +12,7 @@ import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.search.GlobalSearchResult;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.GlobalSearchResultRepository;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
@@ -59,7 +60,7 @@ public class GlobalSearchAsyncProcessor {
FileEntity file = fileEntityRepository.findById(uuid)
.orElseThrow(() -> new RuntimeException("File not found: " + uuid));
tariffInfoService.writeOffTokens(userId, TariffConstants.TOKEN_VALUE_FOR_SEARCH);
tariffInfoService.writeOffTokens(userId, TariffConstants.TOKEN_VALUE_FOR_SEARCH, OperationType.GLOBAL_SEARCH);
GlobalSearchResult result = processFile(file, taskId, userId);
globalSearchResultRepository.save(result);
@@ -10,12 +10,14 @@ import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffStatus;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.repository.TariffInfoRepository;
import ru.soune.nocopy.repository.TariffRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.mail.EmailService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;
import java.io.IOException;
import java.time.LocalDateTime;
@@ -35,6 +37,8 @@ public class TariffInfoService {
private EmailService emailService;
private TokenOperationService tokenOperationService;
public TariffInfo createTariffInfo(LocalDateTime startTariff, LocalDateTime endTariff, TariffStatus tariffStatus,
Integer tokens) {
TariffInfo tariffInfo = new TariffInfo();
@@ -48,7 +52,7 @@ public class TariffInfoService {
}
// @Transactional
public void writeOffTokens(long userId, Integer value) throws MessagingException, IOException {
public void writeOffTokens(long userId, Integer value, OperationType operationType) throws MessagingException, IOException {
User user = userRepository.findById(userId).orElseThrow();
TariffInfo activeTariffInfo;
@@ -60,6 +64,8 @@ public class TariffInfoService {
}
writeOffTokensForTariff(activeTariffInfo, value);
tokenOperationService.create(operationType, userId, Long.valueOf(value));
}
public void writeOffTokensForTariff(TariffInfo activeTariffInfo, Integer value) throws MessagingException, IOException {
@@ -0,0 +1,122 @@
package ru.soune.nocopy.service.tokenoperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import ru.soune.nocopy.repository.TokenOperationRepository;
import jakarta.persistence.EntityNotFoundException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TokenOperationService {
private final TokenOperationRepository repository;
@Transactional
public TokenOperation create(OperationType operationType, Long userId, Long spent) {
TokenOperation tokenOperation = new TokenOperation();
tokenOperation.setOperationType(operationType);
tokenOperation.setUserId(userId);
tokenOperation.setSpent(spent);
tokenOperation.setCreatedAt(LocalDateTime.now());
return repository.save(tokenOperation);
}
public TokenOperation findById(Long id) {
log.debug("Finding token operation by id: {}", id);
return repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("TokenOperation not found with id: " + id));
}
public Page<TokenOperation> findAll(int page, int size, String sortBy, String direction) {
log.debug("Finding all operations with pagination: page={}, size={}, sortBy={}, dir={}",
page, size, sortBy, direction);
Sort sort = direction.equalsIgnoreCase("desc")
? Sort.by(sortBy).descending()
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
return repository.findAll(pageable);
}
public Page<TokenOperation> findByFilters(
Long userId,
OperationType operationType,
Long minSpent,
Long maxSpent,
int page,
int size,
String sortBy,
String direction) {
log.debug("Finding by filters: userId={}, type={}, minSpent={}, maxSpent={}",
userId, operationType, minSpent, maxSpent);
Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
Pageable pageable = PageRequest.of(page, size, sort);
return repository.findByFilters(userId, operationType, minSpent, maxSpent, pageable);
}
public Page<TokenOperation> findByUserId(Long userId, int page, int size) {
log.debug("Finding operations for user id: {}", userId);
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
return repository.findByUserId(userId, pageable);
}
@Transactional
public TokenOperation update(Long id, TokenOperation updatedOperation) {
log.info("Updating token operation with id: {}", id);
TokenOperation existing = findById(id);
existing.setOperationType(updatedOperation.getOperationType());
existing.setUserId(updatedOperation.getUserId());
existing.setSpent(updatedOperation.getSpent());
return repository.save(existing);
}
@Transactional
public void deleteById(Long id) {
log.info("Deleting token operation with id: {}", id);
if (!repository.existsById(id)) {
throw new EntityNotFoundException("TokenOperation not found with id: " + id);
}
repository.deleteById(id);
}
@Transactional
public void deleteByUserId(Long userId) {
log.warn("Deleting all operations for user id: {}", userId);
List<TokenOperation> userOps = repository.findByUserId(userId);
repository.deleteAll(userOps);
}
public Long getTotalSpentByUserAndType(Long userId, OperationType type) {
Long sum = repository.sumSpentByUserAndType(userId, type);
return sum != null ? sum : 0L;
}
public Long countByUserId(Long userId) {
return repository.countByUserId(userId);
}
}