Compare commits
51
Commits
NC_YA
...
daef30ec11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daef30ec11 | ||
|
|
a25b33fe13 | ||
|
|
2985d84b04 | ||
|
|
03a330b570 | ||
|
|
b372180f83 | ||
|
|
920fc67962 | ||
|
|
fb2ac0cf03 | ||
|
|
8a2be0d310 | ||
|
|
0743000c10 | ||
|
|
640deef98d | ||
|
|
28b5a64a33 | ||
|
|
6818b74307 | ||
|
|
6b73d01aeb | ||
|
|
1cb9edd5bf | ||
|
|
081ca05f88 | ||
|
|
970e0fa615 | ||
|
|
52e1a7dd02 | ||
|
|
19330d853d | ||
|
|
1472c342f5 | ||
|
|
1ced86ff8c | ||
|
|
8149c450b2 | ||
|
|
fea2d5ad89 | ||
|
|
2ef1619ef9 | ||
|
|
266bcaefcd | ||
|
|
bf48725244 | ||
|
|
86f7fc593a | ||
|
|
90be1d9d2b | ||
|
|
9692fe48b7 | ||
|
|
d50c565405 | ||
|
|
98fc145f1b | ||
|
|
e7e870ee30 | ||
|
|
8103960b45 | ||
|
|
792e0048cc | ||
|
|
33883faa98 | ||
|
|
7cc744829b | ||
|
|
5a70c091f8 | ||
|
|
69aabe3619 | ||
|
|
27a8766171 | ||
|
|
2bba70e583 | ||
|
|
8e8193cda6 | ||
|
|
b96b50c01f | ||
|
|
e926934473 | ||
|
|
a5003958a8 | ||
|
|
5b73fd2c39 | ||
|
|
daadc82bbb | ||
|
|
682c21ba73 | ||
|
|
1c7a33be32 | ||
|
|
aceb70484f | ||
|
|
3fc83e5458 | ||
|
|
a7e94639d2 | ||
|
|
f52ce6db3c |
@@ -14,6 +14,9 @@ COPY --from=build /app/build/libs/*.jar app.jar
|
||||
|
||||
RUN mkdir -p /data/uploads && chmod 755 /data/uploads
|
||||
|
||||
ENV BUILD_TIME_BACK="unknown"\
|
||||
BUILD_TIME_FRONT="unknown"
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["java", "-jar", "app.jar"]
|
||||
@@ -122,3 +122,42 @@ SELECT 1
|
||||
FROM protect_check pc
|
||||
WHERE pc.user_id = u.id
|
||||
);
|
||||
|
||||
|
||||
------
|
||||
Скрипты рефералки
|
||||
|
||||
INSERT INTO referrals (user_id, referral_link, inviter_id, level_id, total_income, is_active, created_at, hold_balance)
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
CONCAT('ref-', u.id, '-', LOWER(SUBSTRING(MD5(RANDOM()::text) FROM 1 FOR 8))) as referral_link,
|
||||
NULL as inviter_id,
|
||||
'bronze' as level_id,
|
||||
0 as total_income,
|
||||
false as is_active,
|
||||
NOW() as created_at,
|
||||
0 as hold_balance
|
||||
FROM users u
|
||||
LEFT JOIN referrals r ON u.id = r.user_id
|
||||
WHERE
|
||||
u.company_id IS NULL
|
||||
AND r.user_id IS NULL
|
||||
ORDER BY u.id;
|
||||
|
||||
-------
|
||||
|
||||
INSERT INTO referral_levels (id, name, min_invitees, reward_percentage) VALUES
|
||||
('bronze', 'BRONZE', 0, 15),
|
||||
('silver', 'SILVER', 6, 18),
|
||||
('gold', 'GOLD', 16, 22),
|
||||
('platinum', 'PLATINUM', 50, 25);
|
||||
|
||||
------
|
||||
|
||||
Настройка Яндекс Клауд
|
||||
|
||||
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
|
||||
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
|
||||
|
||||
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
|
||||
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
|
||||
+10
-2
@@ -34,7 +34,7 @@ dependencies {
|
||||
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
|
||||
implementation 'commons-validator:commons-validator:1.7'
|
||||
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '4.0.1'
|
||||
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.2'
|
||||
implementation group: 'com.google.cloud', name: 'google-cloud-vision', version: '3.55.0'
|
||||
implementation group: 'com.google.api-client', name: 'google-api-client', version: '2.7.2'
|
||||
|
||||
@@ -59,10 +59,18 @@ dependencies {
|
||||
testImplementation 'org.mockito:mockito-core:5.3.1'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
implementation name: 'testlib-fat-0.2.1-all'
|
||||
implementation name: 'testlib-fat-0.3.0-all'
|
||||
|
||||
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
|
||||
|
||||
implementation project(':referral')
|
||||
|
||||
//cloud
|
||||
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
|
||||
implementation("software.amazon.awssdk:apache-client:2.29.33")
|
||||
|
||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
||||
+1
-1
@@ -181,7 +181,7 @@ services:
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--storage.tsdb.retention.time=15d' # Удерживать 15 дней
|
||||
- '--storage.tsdb.retention.time=15d'
|
||||
- '--web.enable-lifecycle'
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
|
||||
|
||||
@@ -39,6 +39,7 @@ pipeline {
|
||||
stage('Deploy with docker-compose') {
|
||||
steps {
|
||||
script {
|
||||
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'server-root-password',
|
||||
@@ -85,6 +86,7 @@ pipeline {
|
||||
-e POSTGRES_USER=${DB_USER} \\
|
||||
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
|
||||
-v pgdata_${params.PORT}:/var/lib/postgresql/data \\
|
||||
-e BUILD_TIME_BACK='${buildTime}' \\
|
||||
postgres:17.7
|
||||
|
||||
echo '4. Запускаем storage...'
|
||||
|
||||
@@ -85,6 +85,8 @@ pipeline {
|
||||
stage('Build on server') {
|
||||
steps {
|
||||
script {
|
||||
def BUILD_TIME_FRONT = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
|
||||
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'server-root-password',
|
||||
@@ -93,23 +95,20 @@ pipeline {
|
||||
)
|
||||
]) {
|
||||
sh """
|
||||
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
|
||||
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
|
||||
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
|
||||
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
|
||||
|
||||
echo '=== Редактируем docker-compose.yml ==='
|
||||
# 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
|
||||
mkdir -p public
|
||||
echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
|
||||
|
||||
# Меняем имя контейнера
|
||||
sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
|
||||
# 2. Меняем имя контейнера и порт
|
||||
sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
|
||||
sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
|
||||
|
||||
# Меняем порт (2998 на нужный порт)
|
||||
sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
|
||||
|
||||
echo 'Проверяем изменения:'
|
||||
grep -n 'container_name\\|ports' docker-compose.yml
|
||||
|
||||
echo '=== Строим образ ==='
|
||||
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
|
||||
"
|
||||
# 3. Собираем образ
|
||||
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
|
||||
"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pipeline {
|
||||
stage('Deploy with docker-compose') {
|
||||
steps {
|
||||
script {
|
||||
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'server-root-password',
|
||||
@@ -92,6 +93,7 @@ pipeline {
|
||||
-e POSTGRES_PASSWORD=$DB_PASSWORD \\
|
||||
-e POSTGRES_PORT=5432 \\
|
||||
-e POSTGRES_HOST=db \\
|
||||
-e BUILD_TIME_BACK='${buildTime}' \\
|
||||
--restart unless-stopped \\
|
||||
app-backend:latest
|
||||
|
||||
|
||||
Binary file not shown.
@@ -2,9 +2,9 @@ package ru.soune
|
||||
|
||||
interface Referral {
|
||||
|
||||
val userId: String
|
||||
val userId: Long
|
||||
val referralLink: String
|
||||
val inviter: String?
|
||||
val inviter: Long?
|
||||
val currentLevel: String
|
||||
val totalIncome: Int
|
||||
val availableIncome: Int
|
||||
|
||||
@@ -2,32 +2,32 @@ package ru.soune
|
||||
|
||||
interface ReferralRepo {
|
||||
|
||||
fun getUserReferralLink(userId: String): String
|
||||
fun getUserReferralLink(userId: Long): String
|
||||
|
||||
fun getUserIdByLink(link: String): String
|
||||
fun getUserIdByLink(link: String): Long?
|
||||
|
||||
fun getInviterIdForUser(userId: String): String?
|
||||
fun getInviterIdForUser(userId: Long): Long?
|
||||
|
||||
fun getReferralLevelForUser(userId: String): String
|
||||
fun getReferralLevelForUser(userId: Long): String
|
||||
|
||||
fun getReferralByUserId(userId: String): Referral
|
||||
fun getReferralByUserId(userId: Long): Referral
|
||||
|
||||
fun getReferralInvitees(userId: String, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
|
||||
fun getReferralInvitees(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
|
||||
|
||||
// создаем новую запись в БД
|
||||
fun createReferralEntity(entity: Referral)
|
||||
|
||||
// прибавить к totalIncome и availableIncome значение transferAmount
|
||||
fun increaseIncome(userId: String, transferAmount: Int)
|
||||
fun increaseIncome(userId: Long, transferAmount: Int)
|
||||
|
||||
// установить active в true
|
||||
fun activateUser(userId: String): Boolean
|
||||
fun activateUser(userId: Long): Boolean
|
||||
|
||||
// посчитать записи, у которых inviter == userId и active == true
|
||||
fun getActiveInviteeForUser(userId: String): Int
|
||||
fun getActiveInviteeForUser(userId: Long): Int
|
||||
|
||||
// посчитать записи, у которых inviter == userId
|
||||
fun getTotalInviteeForUser(userId: String): Int
|
||||
fun getTotalInviteeForUser(userId: Long): Int
|
||||
|
||||
fun upgradeReferralLevelForUser(userId: String, newLevelId: String)
|
||||
fun upgradeReferralLevelForUser(userId: Long, newLevelId: String)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ class ReferralService(
|
||||
private val referralRepo: ReferralRepo,
|
||||
) {
|
||||
|
||||
fun onRegister(userId: String, inviterReferralLink: String?) {
|
||||
fun onRegister(userId: Long, inviterReferralLink: String?) {
|
||||
val initialReferralLevel = referralLevelProvider.getInitialReferralLevelId()
|
||||
val referralLink = referralRepo.getUserReferralLink(userId)
|
||||
val inviter = inviterReferralLink?.let(referralRepo::getUserIdByLink)
|
||||
@@ -24,7 +24,7 @@ class ReferralService(
|
||||
referralRepo.createReferralEntity(referral)
|
||||
}
|
||||
|
||||
fun onUserAccountRefill(userId: String, transferAmount: Int) {
|
||||
fun onUserAccountRefill(userId: Long, transferAmount: Int) {
|
||||
val wasActivated = referralRepo.activateUser(userId)
|
||||
val inviterId = referralRepo.getInviterIdForUser(userId) ?: return
|
||||
val currentInviterLevel = referralRepo.getReferralLevelForUser(inviterId)
|
||||
@@ -47,11 +47,11 @@ class ReferralService(
|
||||
referralLevelProvider.getAvailableReferralLevels()
|
||||
|
||||
|
||||
fun getReferralLevelForUser(userId: String): ReferralLevel =
|
||||
fun getReferralLevelForUser(userId: Long): ReferralLevel =
|
||||
referralRepo.getReferralLevelForUser(userId)
|
||||
.let { referralLevelProvider.getReferralLevelById(it) }
|
||||
|
||||
fun getReferralStatForUser(userId: String): ReferralStat {
|
||||
fun getReferralStatForUser(userId: Long): ReferralStat {
|
||||
var referral = referralRepo.getReferralByUserId(userId)
|
||||
return ReferralStat(
|
||||
totalInvitee = referralRepo.getTotalInviteeForUser(userId),
|
||||
@@ -59,9 +59,10 @@ class ReferralService(
|
||||
totalIncome = referral.totalIncome,
|
||||
availableIncome = referral.availableIncome,
|
||||
holdBalance = referral.holdBalance,
|
||||
referralLink = referral.referralLink
|
||||
)
|
||||
}
|
||||
|
||||
fun getInviteeForUser(userId: String, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
|
||||
fun getInviteeForUser(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
|
||||
referralRepo.getReferralInvitees(userId, pageSize, pageNumber)
|
||||
}
|
||||
@@ -6,4 +6,5 @@ data class ReferralStat(
|
||||
val totalIncome: Int,
|
||||
val availableIncome: Int,
|
||||
val holdBalance: Int,
|
||||
val referralLink: String,
|
||||
)
|
||||
@@ -3,9 +3,9 @@ package ru.soune.nocopy.configuration;
|
||||
import com.vrt.AudioFilePathProvider;
|
||||
import com.vrt.fileprotection.FileProtector;
|
||||
import com.vrt.fileprotection.audio.AudioLocalSearch;
|
||||
import com.vrt.fileprotection.documents.DocumentLocalSearch;
|
||||
import com.vrt.fileprotection.image.ImageLocalSearch;
|
||||
import com.vrt.fileprotection.image.ImageUniqueCheck;
|
||||
import com.vrt.fileprotection.image.phash.PerceptualHashHelper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -33,7 +33,8 @@ public class ApplicationConfig {
|
||||
ImageUniqueCheck imageUniqueCheck,
|
||||
ImageLocalSearch imageLocalSearch,
|
||||
AudioLocalSearch audioLocalSearch,
|
||||
AudioFilePathProvider audioFilePathProvider) {
|
||||
AudioFilePathProvider audioFilePathProvider,
|
||||
DocumentLocalSearch documentLocalSearch) {
|
||||
|
||||
return new com.vrt.NoCopyFileService(
|
||||
Collections.emptyList(),
|
||||
@@ -42,7 +43,8 @@ public class ApplicationConfig {
|
||||
imageUniqueCheck,
|
||||
imageLocalSearch,
|
||||
audioLocalSearch,
|
||||
audioFilePathProvider
|
||||
audioFilePathProvider,
|
||||
documentLocalSearch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ public class HandlerConfig {
|
||||
AuthRequestHandler authRequestHandler,
|
||||
CompanyHandler companyHandler,
|
||||
TariffHandler tariffHandler,
|
||||
TariffInfoHandler tariffInfoHandler
|
||||
TariffInfoHandler tariffInfoHandler,
|
||||
ReferralHandler referralHandler,
|
||||
DaDataHandler daDataHandler
|
||||
) {
|
||||
Map<Integer, RequestHandler> map = new HashMap<>();
|
||||
map.put(20001, login);
|
||||
@@ -36,6 +38,8 @@ public class HandlerConfig {
|
||||
map.put(30000, companyHandler);
|
||||
map.put(30001, tariffHandler);
|
||||
map.put(30002, tariffInfoHandler);
|
||||
map.put(30003, referralHandler);
|
||||
map.put(30004, daDataHandler);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -20,7 +21,13 @@ public class JacksonConfig {
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
|
||||
.defaultViewInclusion(true)
|
||||
.autoDetectFields(true)
|
||||
.failOnUnknownProperties(false)
|
||||
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
|
||||
.build();
|
||||
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.soune.nocopy.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.soune.ReferralLevelProvider;
|
||||
import ru.soune.ReferralRepo;
|
||||
import ru.soune.ReferralService;
|
||||
|
||||
@Configuration
|
||||
public class ReferralConfig {
|
||||
|
||||
@Bean
|
||||
public ReferralService referralService(ReferralLevelProvider referralLevelProvider, ReferralRepo referralRepo) {
|
||||
return new ReferralService(referralLevelProvider, referralRepo);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.FileSimilarityService;
|
||||
import ru.soune.nocopy.service.file.ProtectionsLimitService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileUploadService;
|
||||
@@ -73,6 +74,8 @@ public class ApiController {
|
||||
|
||||
private final AuthTokenRepository authTokenRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@PostMapping("/v{version}/data")
|
||||
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
|
||||
@PathVariable("version") int version) {
|
||||
@@ -342,15 +345,14 @@ public class ApiController {
|
||||
errorData));
|
||||
}
|
||||
|
||||
|
||||
Path filePath = Paths.get(entityResponse.getProtectedFilePath());
|
||||
// Resource resource = new UrlResource(filePath.toUri());
|
||||
FileSystemResource resource = new FileSystemResource(filePath);
|
||||
File file = cloudStorageService.readFileFromStorageByPath(
|
||||
entityResponse.getProtectedFilePath());
|
||||
long fileSize = Files.size(filePath);
|
||||
|
||||
if (!resource.exists()) {
|
||||
if (!file.exists()) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("resource", resource.exists());
|
||||
errorData.put("file", file.exists());
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
|
||||
@@ -364,8 +366,8 @@ public class ApiController {
|
||||
.contentLength(fileSize)
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + entityResponse.getOriginalFileName() + "\"")
|
||||
.body(resource);
|
||||
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
|
||||
.body(file);
|
||||
} catch (FileEntityNotFoundException e) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("fileId", fileId);
|
||||
@@ -439,33 +441,6 @@ public class ApiController {
|
||||
return ResponseEntity.ok().body(fileTypeStats);
|
||||
}
|
||||
|
||||
private boolean hasDuplicate(List<SimilarFileDTO> similarFiles) {
|
||||
return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5);
|
||||
}
|
||||
|
||||
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileDTO> similarFiles)
|
||||
throws IOException {
|
||||
fileEntityService.deleteFromDisk(fileEntity);
|
||||
|
||||
fileEntityService.softDeleteFileWithHash(fileEntity);
|
||||
|
||||
Optional<FileEntity> originalFile = fileEntityRepository.findById(similarFiles.get(0).getFileId());
|
||||
|
||||
if (originalFile.isPresent()) {
|
||||
Map<String, String> duplicateInfo = Map.of(
|
||||
"duplicate_file_id", originalFile.get().getId(),
|
||||
"owner_user_id", String.valueOf(originalFile.get().getUserId()));
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(
|
||||
20004,
|
||||
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
|
||||
"Failed to upload chunk, duplicate",
|
||||
duplicateInfo));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
|
||||
String fileId) {
|
||||
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
|
||||
|
||||
@@ -16,6 +16,8 @@ import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.file.CheckCounterService;
|
||||
import ru.soune.nocopy.service.file.FileStorageService;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -45,10 +47,13 @@ public class FileController {
|
||||
|
||||
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
|
||||
|
||||
ContentDisposition contentDisposition = ContentDisposition.inline()
|
||||
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
|
||||
.build();
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
|
||||
.body(resource);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.soune.nocopy.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -15,6 +16,14 @@ public class HealtCheckController {
|
||||
return HttpStatus.OK;
|
||||
}
|
||||
|
||||
@Value("${BUILD_TIME_BACK:unknown}")
|
||||
private String buildTimeBack;
|
||||
|
||||
@GetMapping("/build")
|
||||
public BuildInfo getBuildInfo() {
|
||||
return new BuildInfo(buildTimeBack);
|
||||
}
|
||||
|
||||
@GetMapping("/api/debug/memory")
|
||||
public Map<String, String> getMemoryInfo() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
@@ -32,4 +41,12 @@ public class HealtCheckController {
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static class BuildInfo {
|
||||
public String buildTimeBack;
|
||||
|
||||
public BuildInfo(String buildTimeBack) {
|
||||
this.buildTimeBack = buildTimeBack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class UserController {
|
||||
}
|
||||
|
||||
if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
|
||||
userDTO.setTariffs(tariffService.getAllTariffs());
|
||||
userDTO.setTariffs(tariffService.getTariffByAccountType(user));
|
||||
|
||||
Long fileOnDisk = user.canManageCompanySettings() ?
|
||||
fileStatsService.calculateCompanyFileSize(user.getId()):
|
||||
@@ -116,6 +116,7 @@ public class UserController {
|
||||
.maxFileOnDisk(tariff.getDiskSize())
|
||||
.startTariff(personalTariffInfo.getStartTariff())
|
||||
.endTariff(personalTariffInfo.getEndTariff())
|
||||
.tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
|
||||
.build();
|
||||
|
||||
userDTO.setTariffInfo(infoDTO);
|
||||
|
||||
@@ -4,6 +4,7 @@ public enum MessageCode {
|
||||
SUCCESS(0, "Operation successful"),
|
||||
REG_EMAIL_EXISTS(1, "Email already registered"),
|
||||
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
|
||||
REFERRAL_LINK_IS_NOT_EXIST(1, "Refferal link is not exist"),
|
||||
INVALID_FIELD(2, "Invalid field"),
|
||||
INVALID_TOKEN(2, "Invalid token"),
|
||||
TOKEN_IS_ALIVE(2, "Token is alive"),
|
||||
@@ -40,6 +41,7 @@ public enum MessageCode {
|
||||
INTERNAL_ERROR(4, "Internal server error"),
|
||||
COMPANY_NOT_FOUND(4, "Company not found"),
|
||||
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
|
||||
USER_LIMIT_IS_OVER(2, "Over user limits"),
|
||||
ERROR_TARIFF_INFO(2, "Erorr with tariff info");
|
||||
|
||||
private final Integer code;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DaDataAddress {
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DaDataData {
|
||||
private String inn;
|
||||
private DaDataAddress address;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DaDataRequest {
|
||||
|
||||
@JsonProperty("inn")
|
||||
private String inn;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DaDataResponse {
|
||||
@JsonProperty("companyName")
|
||||
private String companyName;
|
||||
|
||||
@JsonProperty("inn")
|
||||
private String inn;
|
||||
|
||||
@JsonProperty("address")
|
||||
private String address;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DaDataSuggestion {
|
||||
private String value;
|
||||
private DaDataData data;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.soune.nocopy.dto.dadata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DaDataWrapper {
|
||||
private List<DaDataSuggestion> suggestions;
|
||||
}
|
||||
@@ -59,6 +59,21 @@ public class FileInfoUserResponse {
|
||||
@JsonProperty("audios_violations")
|
||||
private Integer audiosViolations;
|
||||
|
||||
@JsonProperty("document_size")
|
||||
private Long documentSize;
|
||||
|
||||
@JsonProperty("document_quantity")
|
||||
private Integer documentCount;
|
||||
|
||||
@JsonProperty("document_check")
|
||||
private Integer documentCheck;
|
||||
|
||||
@JsonProperty("document_violations")
|
||||
private Integer documentViolations;
|
||||
|
||||
@JsonProperty("protected_document_files_count")
|
||||
private Long protectedDocumentFilesCount;
|
||||
|
||||
@JsonProperty("protected_files_count")
|
||||
private Long protectedFilesCount;
|
||||
|
||||
|
||||
@@ -7,4 +7,10 @@ import lombok.Data;
|
||||
public class ImageSearchRequest {
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
@JsonProperty("page")
|
||||
private Integer page = 1;
|
||||
|
||||
@JsonProperty("page_size")
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
|
||||
@@ -12,14 +12,10 @@ public class YandexSearchResponse {
|
||||
@JsonProperty("images")
|
||||
private List<ImageResult> images;
|
||||
|
||||
@JsonProperty("images")
|
||||
public void setImages(List<ImageResult> images) {
|
||||
if (images != null && images.size() > 5) {
|
||||
this.images = images.subList(0, 5);
|
||||
} else {
|
||||
this.images = images;
|
||||
}
|
||||
}
|
||||
// @JsonProperty("images")
|
||||
// public void setImages(List<ImageResult> images) {
|
||||
// this.images = images;
|
||||
// }
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -42,4 +38,12 @@ public class YandexSearchResponse {
|
||||
@JsonProperty("host")
|
||||
private String host;
|
||||
}
|
||||
|
||||
private int page;
|
||||
|
||||
private int pageSize;
|
||||
|
||||
private int totalResults;
|
||||
|
||||
private int totalPages;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ru.soune.nocopy.dto.referral;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class ReferralRequest {
|
||||
private String token;
|
||||
private Integer amount;
|
||||
private Integer pageSize;
|
||||
private Integer pageNumber;
|
||||
private String action;
|
||||
}
|
||||
@@ -29,4 +29,6 @@ public class RegRequest {
|
||||
private String password;
|
||||
|
||||
private String authToken;
|
||||
|
||||
private String referralLink;
|
||||
}
|
||||
|
||||
@@ -46,4 +46,7 @@ public class TariffRequest {
|
||||
|
||||
@JsonProperty("action")
|
||||
private String action;
|
||||
|
||||
@JsonProperty("user_token")
|
||||
private String userToken;
|
||||
}
|
||||
@@ -7,11 +7,12 @@ import java.util.List;
|
||||
|
||||
@Getter
|
||||
public enum FileType {
|
||||
// IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "webp", "jfif")),
|
||||
IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp")),
|
||||
VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg",
|
||||
"3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")),
|
||||
AUDIO("audio", List.of("wav"));
|
||||
AUDIO("audio", List.of("wav")),
|
||||
DOCUMENT("document", Arrays.asList("pdf"));
|
||||
|
||||
|
||||
private final String displayName;
|
||||
private final List<String> allowedExtensions;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package ru.soune.nocopy.entity.referral;
|
||||
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "referrals")
|
||||
@Data
|
||||
public class Referral {
|
||||
@Id
|
||||
private Long userId;
|
||||
|
||||
@Column(name = "referral_link", unique = true, nullable = false)
|
||||
private String referralLink;
|
||||
|
||||
@Column(name = "inviter_id")
|
||||
private Long inviterId = 0L;
|
||||
|
||||
@Column(name = "level_id")
|
||||
private String levelId = "bronze";
|
||||
|
||||
@Column(name = "total_income")
|
||||
private int totalIncome = 0;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private boolean isActive = false;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false, nullable = false)
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
|
||||
@Column(name = "hold_balance")
|
||||
private int holdBalance = 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ru.soune.nocopy.entity.referral;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
|
||||
@Entity
|
||||
@Table(name = "referral_levels")
|
||||
@Data
|
||||
public class ReferralLevelEntity {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "min_invitees")
|
||||
private int minInvitees;
|
||||
|
||||
@Column(name = "reward_percentage")
|
||||
private int rewardPercentage;
|
||||
}
|
||||
@@ -37,4 +37,7 @@ public class Tariff {
|
||||
|
||||
@Column(name = "max_users")
|
||||
private Long maxUsers;
|
||||
|
||||
@Column(name = "tariffAccountType")
|
||||
private String tariffAccountType;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ public class TariffInfo {
|
||||
@Column(name = "tokens")
|
||||
private Integer tokens;
|
||||
|
||||
@Column(name = "bought_tokens")
|
||||
private Integer boughtTokens = 0;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "tariff_id")
|
||||
private Tariff tariff;
|
||||
|
||||
@@ -2,5 +2,5 @@ package ru.soune.nocopy.entity.tarif;
|
||||
|
||||
|
||||
public enum TariffType {
|
||||
START, BASIC, PRO, ENTERPRISE, DEMO;
|
||||
START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.soune.nocopy.exception;
|
||||
|
||||
public class MaxUserCountIsOver extends RuntimeException {
|
||||
public MaxUserCountIsOver(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.dadata.DaDataRequest;
|
||||
import ru.soune.nocopy.dto.dadata.DaDataResponse;
|
||||
import ru.soune.nocopy.service.dadata.DaDataService;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DaDataHandler implements RequestHandler {
|
||||
|
||||
private final DaDataService daDataService;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
DaDataRequest daDataRequest = mapper.convertValue(request.getMessageBody(), DaDataRequest.class);
|
||||
String inn = daDataRequest.getInn();
|
||||
DaDataResponse daData = daDataService.callDaData(inn);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), daData);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import ru.soune.nocopy.entity.file.ProtectionStatus;
|
||||
import ru.soune.nocopy.exception.FileEntityNotFoundException;
|
||||
import ru.soune.nocopy.exception.NotFoundAuthToken;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileStatsService;
|
||||
@@ -35,6 +36,8 @@ public class FileEntityHandler implements RequestHandler {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) {
|
||||
try {
|
||||
@@ -302,7 +305,7 @@ public class FileEntityHandler implements RequestHandler {
|
||||
int fullDelete = fileRequest.getFullDelete() == null ? 0 : fileRequest.getFullDelete();
|
||||
|
||||
if (fileEntity.getStatus().equals(FileStatus.DELETED) || fullDelete == 1) {
|
||||
fileEntityService.deleteFromDisk(fileEntity);
|
||||
cloudStorageService.deleteFromStorageDisk(fileEntity);
|
||||
response = DeleteFileResponse.builder()
|
||||
.fileId(fileRequest.getFileId())
|
||||
.message("File deleted from disk")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -8,17 +9,19 @@ import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
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.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.file.CheckCounterService;
|
||||
import ru.soune.nocopy.service.search.SearchApiToYandexResponseMapper;
|
||||
import ru.soune.nocopy.service.search.YandexSearchService;
|
||||
import ru.soune.nocopy.service.search.SearchImageService;
|
||||
import ru.soune.nocopy.service.search.GoogleVisionSearchService;
|
||||
import ru.soune.nocopy.service.tariff.TariffConstants;
|
||||
import ru.soune.nocopy.service.tariff.TariffInfoService;
|
||||
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -27,7 +30,7 @@ public class ImageFoundRequestHandler implements RequestHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final YandexSearchService yandexSearchService;
|
||||
private final SearchImageService searchImageService;
|
||||
|
||||
private final GoogleVisionSearchService googleVisionSearchService;
|
||||
|
||||
@@ -37,8 +40,6 @@ public class ImageFoundRequestHandler implements RequestHandler {
|
||||
|
||||
private final TariffInfoService tariffInfoService;
|
||||
|
||||
private final SearchApiToYandexResponseMapper mapper = new SearchApiToYandexResponseMapper();
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(),
|
||||
@@ -58,13 +59,54 @@ public class ImageFoundRequestHandler implements RequestHandler {
|
||||
//TODO uncommited when add billing
|
||||
// GoogleVisionSearchResponse googleVisionSearchResponse = googleVisionSearchService.searchByFileEntity(fileId);
|
||||
|
||||
String yandexReverseSearchResponse = yandexSearchService.searchReverseByPublicUrl(fileEntity);
|
||||
String searchResponseYandex;
|
||||
String searchResponseGoogle;
|
||||
try {
|
||||
searchResponseYandex = searchImageService.searchReverseByPublicUrl(fileEntity, "yandex_reverse_image",
|
||||
"visual_matches");
|
||||
searchResponseGoogle = searchImageService.searchReverseByPublicUrl(fileEntity, "google_lens",
|
||||
"exact_matches");
|
||||
} catch (TimeoutException e) {
|
||||
log.warn("Search timeout for file {}, returning empty results", fileId);
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
"Search completed (partial)", Map.of(
|
||||
"results", List.of(),
|
||||
"total", 0,
|
||||
"searchStatus", "timeout"
|
||||
));
|
||||
} catch (IOException e) {
|
||||
log.error("Search failed for file {}", fileId, e);
|
||||
return new BaseResponse(request.getMsgId(), 20007,
|
||||
"Search service temporarily unavailable",
|
||||
Map.of("fileId", fileId));
|
||||
}
|
||||
|
||||
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH);
|
||||
|
||||
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
|
||||
|
||||
List<YandexSearchResponse.ImageResult> allYandexImages = searchImageService.getAllImagesWithoutPagination(
|
||||
searchResponseYandex, "visual_matches");
|
||||
List<YandexSearchResponse.ImageResult> allGoogleImages = searchImageService.getAllImagesWithoutPagination(
|
||||
searchResponseGoogle, "exact_matches");
|
||||
|
||||
List<YandexSearchResponse.ImageResult> allUniqueImages = searchImageService.removeDuplicateUrls(allYandexImages,
|
||||
allGoogleImages);
|
||||
|
||||
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
|
||||
int pageSize = 5;
|
||||
|
||||
List<YandexSearchResponse.ImageResult> pagedResults = searchImageService.paginateResults(allUniqueImages, page,
|
||||
pageSize * 2);
|
||||
|
||||
YandexSearchResponse finalResponse = new YandexSearchResponse();
|
||||
finalResponse.setImages(pagedResults);
|
||||
finalResponse.setPage(page);
|
||||
finalResponse.setPageSize(pageSize * 2);
|
||||
finalResponse.setTotalResults(allUniqueImages.size());
|
||||
finalResponse.setTotalPages((int) Math.ceil((double) allUniqueImages.size() / (pageSize * 2)));
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(),mapper.mapToYandexResponse(yandexReverseSearchResponse));
|
||||
MessageCode.SUCCESS.getDescription(), finalResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package ru.soune.nocopy.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.ReferralInvitee;
|
||||
import ru.soune.ReferralLevel;
|
||||
import ru.soune.ReferralService;
|
||||
import ru.soune.ReferralStat;
|
||||
import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.referral.ReferralRequest;
|
||||
import ru.soune.nocopy.entity.user.AuthToken;
|
||||
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReferralHandler implements RequestHandler {
|
||||
|
||||
private final ReferralService referralService;
|
||||
|
||||
private final AuthTokenRepository authTokenRepository;
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
ReferralRequest referralRequest = mapper.convertValue(request.getMessageBody(), ReferralRequest.class);
|
||||
String action = referralRequest.getAction();
|
||||
String token = referralRequest.getToken();
|
||||
AuthToken authToken = authTokenRepository.findByToken(token).orElseThrow();
|
||||
|
||||
switch (action) {
|
||||
case "refill":
|
||||
return handleRefill(request, authToken.getUser().getId(), referralRequest.getAmount());
|
||||
case "levels":
|
||||
return handleReferralLevels(request);
|
||||
case "level":
|
||||
return handleUserLevel(request, authToken.getUser().getId());
|
||||
case "userStats" :
|
||||
return handleUserStats(request, authToken.getUser().getId());
|
||||
case "invitees":
|
||||
return handleInvitees(referralRequest, request, authToken.getUser().getId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private BaseResponse handleRefill(BaseRequest request, Long userId, Integer amount) {
|
||||
referralService.onUserAccountRefill(userId, amount);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), Map.of("account", "refill"));
|
||||
}
|
||||
|
||||
private BaseResponse handleReferralLevels(BaseRequest request) {
|
||||
List<ReferralLevel> referralLevels = referralService.getReferralLevels();
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), referralLevels);
|
||||
|
||||
}
|
||||
|
||||
private BaseResponse handleUserLevel(BaseRequest request, Long userId) {
|
||||
ReferralLevel referralLevelForUser = referralService.getReferralLevelForUser(userId);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), referralLevelForUser);
|
||||
}
|
||||
|
||||
private BaseResponse handleUserStats(BaseRequest request, Long userId) {
|
||||
ReferralStat referralStatForUser = referralService.getReferralStatForUser(userId);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), referralStatForUser);
|
||||
}
|
||||
|
||||
private BaseResponse handleInvitees(ReferralRequest referralRequest, BaseRequest request, Long userId) {
|
||||
List<ReferralInvitee> inviteeForUser = referralService.getInviteeForUser(userId, referralRequest.getPageSize(),
|
||||
referralRequest.getPageNumber());
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), inviteeForUser);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,16 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import ru.soune.ReferralService;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.dto.register.RegAnswer;
|
||||
import ru.soune.nocopy.dto.register.RegRequest;
|
||||
import ru.soune.nocopy.entity.referral.Referral;
|
||||
import ru.soune.nocopy.entity.user.AuthToken;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.exception.ValidationException;
|
||||
import ru.soune.nocopy.handler.validator.RegRequestValidator;
|
||||
import ru.soune.nocopy.repository.ReferralJpaRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
import ru.soune.nocopy.service.mail.EmailService;
|
||||
@@ -34,6 +37,10 @@ public class RegRequestHandler implements RequestHandler {
|
||||
|
||||
private final EmailService emailService;
|
||||
|
||||
private final ReferralService referralService;
|
||||
|
||||
private final ReferralJpaRepository referralJpaRepository;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws ValidationException {
|
||||
RegRequest regRequest = objectMapper.convertValue(request.getMessageBody(), RegRequest.class);
|
||||
@@ -49,6 +56,19 @@ public class RegRequestHandler implements RequestHandler {
|
||||
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
|
||||
}
|
||||
|
||||
String referralLink = regRequest.getReferralLink();
|
||||
|
||||
if (referralLink != null) {
|
||||
Referral referral = referralJpaRepository.findByReferralLink(referralLink);
|
||||
if (referral == null) {
|
||||
throw new NotValidFieldException("Referral link is not found:" + referralLink +
|
||||
referralLink, new BaseResponse(request.getMsgId(),
|
||||
MessageCode.REFERRAL_LINK_IS_NOT_EXIST.getCode(),
|
||||
MessageCode.REFERRAL_LINK_IS_NOT_EXIST.getDescription(), Map.of(
|
||||
"referralLink", referralLink)));
|
||||
}
|
||||
}
|
||||
|
||||
BindingResult bindingResult = new BeanPropertyBindingResult(regRequest, "regRequest");
|
||||
|
||||
regRequestValidator.validate(regRequest, bindingResult);
|
||||
@@ -76,12 +96,15 @@ public class RegRequestHandler implements RequestHandler {
|
||||
|
||||
RegAnswer regAnswer = new RegAnswer();
|
||||
|
||||
|
||||
regAnswer.setUserId(authToken.getUser().getId());
|
||||
regAnswer.setToken(token);
|
||||
regAnswer.setVerified(true);
|
||||
regAnswer.setActive(true);
|
||||
|
||||
if (regRequest.getAccountType().equals("b2c")) {
|
||||
referralService.onRegister(authToken.getUser().getId(), regRequest.getReferralLink());
|
||||
}
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), regAnswer);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.tarriff.TariffDTO;
|
||||
import ru.soune.nocopy.dto.tarriff.TariffRequest;
|
||||
import ru.soune.nocopy.dto.tarriff.TariffResponse;
|
||||
import ru.soune.nocopy.entity.user.AuthToken;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
import ru.soune.nocopy.service.tariff.TariffService;
|
||||
|
||||
import java.util.List;
|
||||
@@ -24,6 +27,8 @@ public class TariffHandler implements RequestHandler {
|
||||
|
||||
private final TariffService tariffService;
|
||||
|
||||
private final AuthTokenRepository authTokenRepository;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
try {
|
||||
@@ -113,7 +118,9 @@ public class TariffHandler implements RequestHandler {
|
||||
}
|
||||
|
||||
private BaseResponse handleGetTariffs(BaseRequest request, TariffRequest tariffRequest) {
|
||||
List<TariffDTO> allTariffs = tariffService.getAllTariffs();
|
||||
AuthToken authToken = authTokenRepository.findByToken(tariffRequest.getUserToken()).orElseThrow();
|
||||
User user = authToken.getUser();
|
||||
List<TariffDTO> allTariffs = tariffService.getTariffByAccountType(user);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(),
|
||||
|
||||
@@ -15,6 +15,8 @@ import java.util.Optional;
|
||||
public interface FileEntityRepository extends JpaRepository<FileEntity, String> {
|
||||
List<FileEntity> findByUserId(Long userId);
|
||||
|
||||
FileEntity findByIdAndUserId(String id, Long userId);
|
||||
|
||||
Optional<FileEntity> findByUserIdAndChecksum(Long userId, String imageHash);
|
||||
|
||||
List<FileEntity> findByUserIdAndStatus(Long userId, FileStatus status);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package ru.soune.nocopy.repository;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.entity.referral.Referral;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ReferralJpaRepository extends JpaRepository<Referral, Long> {
|
||||
Referral findByReferralLink(String link);
|
||||
|
||||
Referral findByUserId(Long userId);
|
||||
|
||||
List<Referral> findByInviterId(Long inviterId);
|
||||
|
||||
@Query("SELECT r FROM Referral r WHERE r.inviterId = :inviterId AND r.isActive = true")
|
||||
List<Referral> findByInviterIdWhenUserActivated(Long inviterId, Pageable pageable);
|
||||
|
||||
@Query("SELECT r FROM Referral r WHERE r.inviterId = :inviterId")
|
||||
List<Referral> findByInviterId(Long inviterId, Pageable pageable);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Referral r SET r.totalIncome = r.totalIncome + :amount WHERE r.userId = :userId")
|
||||
void increaseIncome(@Param("userId") Long userId, @Param("amount") int amount);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Referral r SET r.levelId = :newLevelId WHERE r.userId = :userId")
|
||||
void updateLevel(@Param("userId") Long userId, @Param("newLevelId") String newLevelId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Referral r SET r.isActive = true WHERE r.userId = :userId")
|
||||
void activateUser(@Param("userId") Long userId);
|
||||
|
||||
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId AND r.isActive = true")
|
||||
int countActiveInvitees(@Param("userId") Long userId);
|
||||
|
||||
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
|
||||
int countTotalInvitees(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ru.soune.nocopy.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.entity.referral.ReferralLevelEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ReferralLevelRepository extends JpaRepository<ReferralLevelEntity, Long> {
|
||||
List<ReferralLevelEntity> findAllByOrderByMinInviteesAsc();
|
||||
ReferralLevelEntity findById(String id);
|
||||
}
|
||||
@@ -12,5 +12,6 @@ import java.util.Optional;
|
||||
public interface TariffRepository extends JpaRepository<Tariff, Long> {
|
||||
Optional<Tariff> findByType(String type);
|
||||
List<Tariff> findAllByOrderByPriceAsc();
|
||||
List<Tariff> findByTariffAccountType(String typeAccount);
|
||||
Tariff findByName(String name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package ru.soune.nocopy.service.dadata;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.dto.dadata.DaDataResponse;
|
||||
import ru.soune.nocopy.dto.dadata.DaDataSuggestion;
|
||||
import ru.soune.nocopy.dto.dadata.DaDataWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DaDataService {
|
||||
|
||||
@Value("${dadata.api-key}")
|
||||
private String apiKey ;
|
||||
|
||||
@Value("${dadata.url}")
|
||||
private String url ;
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
public DaDataService() {
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.callTimeout(20, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public DaDataResponse callDaData(String inn) throws IOException {
|
||||
if (apiKey == null || apiKey.isBlank()) {
|
||||
throw new IllegalStateException("SearchAPI key not configured");
|
||||
}
|
||||
|
||||
String jsonBody = String.format("{\"query\": \"%s\"}", inn);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://suggestions.dadata.ru/suggestions/api/4_1/rs/findById/party")
|
||||
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonBody))
|
||||
.header("Accept", "application/json")
|
||||
.header("Authorization", "Token " + apiKey)
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
throw new IOException("Empty response body");
|
||||
}
|
||||
|
||||
String jsonResponse = body.string();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
DaDataWrapper wrapper = mapper.readValue(jsonResponse, DaDataWrapper.class);
|
||||
|
||||
DaDataResponse daDataResponse = new DaDataResponse();
|
||||
|
||||
if (wrapper.getSuggestions() != null && !wrapper.getSuggestions().isEmpty()) {
|
||||
DaDataSuggestion suggestion = wrapper.getSuggestions().get(0);
|
||||
|
||||
daDataResponse.setCompanyName(suggestion.getValue());
|
||||
|
||||
if (suggestion.getData() != null) {
|
||||
daDataResponse.setInn(suggestion.getData().getInn());
|
||||
|
||||
if (suggestion.getData().getAddress() != null) {
|
||||
daDataResponse.setAddress(suggestion.getData().getAddress().getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return daDataResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
|
||||
import ru.soune.nocopy.repository.ProtectedFileCheckRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
@@ -15,7 +16,10 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -35,6 +39,7 @@ public class FileCleanupService {
|
||||
log.info("Starting cleanup of expired temporary files");
|
||||
|
||||
cleanupOldPeriods();
|
||||
cleanupTempFilesNightly();
|
||||
|
||||
Path tempDir = Paths.get(basePath, "temp");
|
||||
if (!Files.exists(tempDir)) {
|
||||
@@ -98,4 +103,50 @@ public class FileCleanupService {
|
||||
protectedFileCheckRepository.saveAll(checks);
|
||||
log.info("Cleaned up {} old protection usage records", checks.size());
|
||||
}
|
||||
|
||||
private void cleanupTempFilesNightly() {
|
||||
log.info("Starting nightly cleanup of temporary files");
|
||||
|
||||
String tempDir = System.getProperty("java.io.tmpdir");
|
||||
Path tempPath = Paths.get(tempDir);
|
||||
|
||||
if (!Files.exists(tempPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Instant cutoffTime = Instant.now().minus(24, ChronoUnit.HOURS); // 24 часа
|
||||
int deletedCount = 0;
|
||||
|
||||
try (Stream<Path> files = Files.list(tempPath)) {
|
||||
List<Path> tempFiles = files
|
||||
.filter(path -> {
|
||||
String filename = path.getFileName().toString();
|
||||
return filename.contains("_") &&
|
||||
!filename.endsWith(".tmp") &&
|
||||
Files.isRegularFile(path);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Path filePath : tempFiles) {
|
||||
try {
|
||||
File file = filePath.toFile();
|
||||
Instant lastModified = Instant.ofEpochMilli(file.lastModified());
|
||||
|
||||
if (lastModified.isBefore(cutoffTime)) {
|
||||
boolean deleted = Files.deleteIfExists(filePath);
|
||||
if (deleted) {
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting file: {}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Nightly cleanup deleted {} temporary files", deletedCount);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to list temp directory", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ import ru.soune.nocopy.repository.SimilarImageProjection;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.FileSimilarityService;
|
||||
import ru.soune.nocopy.service.ImageHashService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -42,6 +43,8 @@ public class FileEntityService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Transactional(noRollbackFor = DuplicateImageException.class)
|
||||
public FileEntity createFromUploadSession(FileUploadSession session, String checksum) {
|
||||
log.info("Creating FileEntity for upload session: {}", session.getUploadId());
|
||||
@@ -55,7 +58,7 @@ public class FileEntityService {
|
||||
|
||||
Map<String, Long> imageHash = Map.of();
|
||||
|
||||
if (session.getFileType().startsWith("image")) {
|
||||
if (!session.getFileType().startsWith("video")) {
|
||||
imageHash = imageHashService.calculateHash(filePath);
|
||||
List<SimilarImageProjection> duplicatedByHash = fileSimilarityService.findDuplicatedByHash(
|
||||
imageHash.get("hi"), imageHash.get("low"));
|
||||
@@ -116,43 +119,22 @@ public class FileEntityService {
|
||||
return convertToResponse(fileEntity, version);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public FileEntityResponse getByFilePath(String filePath, int version) {
|
||||
FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath)
|
||||
.orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath));
|
||||
|
||||
return convertToResponse(fileEntity, version);
|
||||
}
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public FileResponse getAllUserFiles(Long userId, int version) {
|
||||
List<FileEntity> fileEntities = fileEntityRepository.findByUserIdAndStatus(
|
||||
userId, FileStatus.ACTIVE);
|
||||
|
||||
List<FileEntityResponse> files = fileEntities.stream()
|
||||
.map(file -> convertToResponse(file, version))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long totalSize = fileEntities.stream()
|
||||
.mapToLong(FileEntity::getFileSize)
|
||||
.sum();
|
||||
|
||||
return FileResponse.builder()
|
||||
.files(files)
|
||||
.totalCount(files.size())
|
||||
.totalSize(totalSize)
|
||||
.formattedTotalSize(formatFileSize(totalSize))
|
||||
.page(1)
|
||||
.pageSize(files.size())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) {
|
||||
List<FileEntity> allFiles = fileEntityRepository.findByUserIdAndStatus(
|
||||
userId, FileStatus.ACTIVE);
|
||||
User user = userRepository.findById(userId).orElseThrow();
|
||||
List<FileEntity> allFiles = new ArrayList<>();
|
||||
|
||||
if (user.getCompany() != null) {
|
||||
for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream()
|
||||
.map(User::getId)
|
||||
.toList()) {
|
||||
allFiles.addAll(fileEntityRepository.findByUserIdAndStatus(uId, FileStatus.ACTIVE));
|
||||
}
|
||||
|
||||
} else {
|
||||
allFiles = fileEntityRepository.findByUserIdAndStatus(
|
||||
userId, FileStatus.ACTIVE);
|
||||
}
|
||||
|
||||
int start = (page - 1) * pageSize;
|
||||
int end = Math.min(start + pageSize, allFiles.size());
|
||||
@@ -188,19 +170,6 @@ public class FileEntityService {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public FileEntity markAsDeleted(FileEntity fileEntity) throws IOException {
|
||||
fileEntity.setStatus(FileStatus.DELETED);
|
||||
fileEntity.setUpdatedAt(LocalDateTime.now());
|
||||
fileEntity.setProtectionStatus(ProtectionStatus.NOT_PROTECTED);
|
||||
|
||||
Path path = Paths.get(fileEntity.getProtectedFilePath());
|
||||
Files.deleteIfExists(path);
|
||||
fileEntity.setProtectedFilePath("");
|
||||
|
||||
return fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public void softDeleteFileWithHash(FileEntity fileEntity) throws IOException {
|
||||
if (fileEntity.getImageHash() != null) {
|
||||
fileEntity.setImageHash(null);
|
||||
@@ -217,16 +186,8 @@ public class FileEntityService {
|
||||
fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public void deleteFromDisk(FileEntity fileEntity) throws IOException {
|
||||
Path path = Paths.get(fileEntity.getFilePath());
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Files.delete(path);
|
||||
|
||||
fileEntityRepository.delete(fileEntity);
|
||||
public void deleteFromDisk(String deleteFilePath) throws IOException {
|
||||
Files.deleteIfExists(Paths.get(deleteFilePath));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@@ -244,7 +205,7 @@ public class FileEntityService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
|
||||
public FileEntity writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("File not found: " + id));
|
||||
|
||||
@@ -263,14 +224,14 @@ public class FileEntityService {
|
||||
fileEntity.setFileExtension(extension);
|
||||
fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED);
|
||||
|
||||
fileEntityRepository.save(fileEntity);
|
||||
return fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public void clearTempFiles(long userId) throws IOException {
|
||||
public void clearTempFiles(long userId) {
|
||||
List<FileEntity> fileByUserIdAndStatus = fileEntityRepository.findFileByUserIdAndStatus(userId, FileStatus.TEMP);
|
||||
|
||||
for (FileEntity fileEntity : fileByUserIdAndStatus) {
|
||||
deleteFromDisk(fileEntity);
|
||||
cloudStorageService.deleteFromStorageDisk(fileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,24 +243,6 @@ public class FileEntityService {
|
||||
return fileEntityRepository.findFileIdByFilePath(filePath);
|
||||
}
|
||||
|
||||
public File getFileById(String id) {
|
||||
try {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
|
||||
File file = new File(fileEntity.getFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("File not found on disk: " + fileEntity.getFilePath());
|
||||
}
|
||||
|
||||
return file;
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting file: {}", id, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Path prepareProtectedPath(FileEntity fileEntity, String extension) throws IOException {
|
||||
Path originalPath = Paths.get(fileEntity.getFilePath());
|
||||
|
||||
@@ -336,6 +279,18 @@ public class FileEntityService {
|
||||
fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkFileExistsOnDisk(String filePath) {
|
||||
try {
|
||||
return Files.exists(Paths.get(filePath));
|
||||
@@ -346,7 +301,7 @@ public class FileEntityService {
|
||||
}
|
||||
|
||||
private String determineFileExtension(String fileExt, FileEntity fileEntity) {
|
||||
if (fileExt != null && !fileExt.trim().isEmpty()) {
|
||||
if (fileExt != null) {
|
||||
return fileExt;
|
||||
} else {
|
||||
return fileEntity.getFileExtension();
|
||||
@@ -376,7 +331,9 @@ public class FileEntityService {
|
||||
.existsOnDisk(existsOnDisk)
|
||||
.supportId(fileEntity.getSupportId())
|
||||
.protectStatus(fileEntity.getProtectionStatus().toString())
|
||||
.fileName(fileEntity.getOriginalFileName())
|
||||
.fileName(fileEntity.getOriginalFileName().replace("." +
|
||||
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
|
||||
"." + fileEntity.getFileExtension())
|
||||
.ownerName(user.getFullName())
|
||||
.ownerEmail(user.getEmail())
|
||||
.ownerCompany(user.getCompanyName())
|
||||
@@ -386,16 +343,4 @@ public class FileEntityService {
|
||||
.protectedFilePath(fileEntity.getProtectedFilePath())
|
||||
.build();
|
||||
}
|
||||
|
||||
public String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,12 @@ public class FileStatsService {
|
||||
.audiosCheck(calculateMediaByStatus(files, FileType.AUDIO, FileStatus.CHECKED))
|
||||
.audiosViolations(calculateMediaByStatus(files, FileType.AUDIO, FileStatus.VIOLATION))
|
||||
.protectedAudioFilesCount(protectedUserFiles(files, FileType.AUDIO))
|
||||
|
||||
.documentSize(calculateMediaSize(files, FileType.DOCUMENT))
|
||||
.documentCount(calculateMediaCount(files, FileType.DOCUMENT))
|
||||
.documentCheck(calculateMediaByStatus(files, FileType.DOCUMENT, FileStatus.CHECKED))
|
||||
.documentViolations(calculateMediaByStatus(files, FileType.DOCUMENT, FileStatus.VIOLATION))
|
||||
.protectedDocumentFilesCount(protectedUserFiles(files, FileType.DOCUMENT))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package ru.soune.nocopy.service.file;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.soune.nocopy.dto.file.UploadProgressResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.FileStatus;
|
||||
import ru.soune.nocopy.entity.file.FileUploadSession;
|
||||
|
||||
@@ -19,5 +20,5 @@ public interface FileUploadService {
|
||||
|
||||
UploadProgressResponse getUploadProgress(String uploadId);
|
||||
|
||||
void completeFileProcessingAsync(FileUploadSession session, FileStatus status);
|
||||
FileEntity completeFileProcessingAsync(FileUploadSession session, FileStatus status);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package ru.soune.nocopy.service.file.cloud;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.http.apache.ApacheHttpClient;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CloudStorageService {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
@Value("${yandex.cloud.key}")
|
||||
private String key;
|
||||
|
||||
@Value("${yandex.cloud.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${yandex.cloud.region}")
|
||||
private String region;
|
||||
|
||||
@Value("${yandex.cloud.s3-endpoint}")
|
||||
private String s3endpoint;
|
||||
|
||||
@Value("${yandex.cloud.bucket}")
|
||||
private String bucket;
|
||||
|
||||
public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) {
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String mimeTypeFromFile = mimeType.equals("document") ? "application" : mimeType;
|
||||
String contentType = mimeTypeFromFile + "/" + extension;
|
||||
String filePath = "files" + sendToCloudFilePath;
|
||||
|
||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key(filePath)
|
||||
.contentType(contentType)
|
||||
.build();
|
||||
|
||||
try {
|
||||
s3Client.putObject(putObjectRequest,
|
||||
RequestBody.fromBytes(Files.readAllBytes(Paths.get(sendToCloudFilePath))));
|
||||
Files.deleteIfExists(Paths.get(sendToCloudFilePath));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read file: {}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public File readFileFromStorageByPath(String filePath) throws IOException {
|
||||
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key("files" + filePath)
|
||||
.build();
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String tempDir = System.getProperty("java.io.tmpdir");
|
||||
String fileName = filePath.replace("/", "_");
|
||||
File tempFile = new File(tempDir, fileName);
|
||||
|
||||
s3Client.getObject(objectRequest, tempFile.toPath());
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public void deleteFileFromStorage(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
log.warn("Attempted to delete file with empty path");
|
||||
return;
|
||||
}
|
||||
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String cloudKey = "files" + filePath;
|
||||
|
||||
try {
|
||||
try {
|
||||
s3Client.headObject(builder -> builder.bucket(bucket).key(cloudKey).build());
|
||||
} catch (Exception e) {
|
||||
log.warn("File does not exist in cloud storage: {}", cloudKey);
|
||||
return;
|
||||
}
|
||||
|
||||
s3Client.deleteObject(builder -> builder.bucket(bucket).key(cloudKey).build());
|
||||
|
||||
log.info("File successfully deleted from cloud storage: {}", cloudKey);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to delete file from cloud storage: {}", cloudKey, e);
|
||||
throw new RuntimeException("Failed to delete file from cloud storage: " + cloudKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteFromStorageDisk(FileEntity fileEntity) {
|
||||
deleteFileFromStorage(fileEntity.getFilePath());
|
||||
|
||||
if (fileEntity.getProtectedFilePath() != null) {
|
||||
deleteFileFromStorage(fileEntity.getProtectedFilePath());
|
||||
}
|
||||
|
||||
fileEntityRepository.delete(fileEntity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.soune.nocopy.service.file.impl;
|
||||
|
||||
import com.vrt.fileprotection.FileProtector;
|
||||
import com.vrt.fileprotection.documents.DocumentLocalSearch;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DocumentLocalSearchImpl implements DocumentLocalSearch {
|
||||
|
||||
private final FileUtil fileUtil;
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
@Override
|
||||
public @Nullable FileProtector.FileInfo findByIds(@NotNull String ownerId, @NotNull String fileId) {
|
||||
FileEntity fileEntity = fileEntityRepository.findByIdAndUserId(fileId, Long.valueOf(ownerId));
|
||||
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package ru.soune.nocopy.service.file.impl;
|
||||
import com.vrt.NoCopyFileService;
|
||||
import com.vrt.fileprotection.FileProtector;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,6 +25,7 @@ import ru.soune.nocopy.service.FileSimilarityService;
|
||||
import ru.soune.nocopy.service.ImageHashService;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileUploadService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.service.tariff.TariffConstants;
|
||||
import ru.soune.nocopy.service.tariff.TariffInfoService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
@@ -83,6 +85,8 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
|
||||
private final TariffInfoService tariffInfoService;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
@@ -177,7 +181,6 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber,
|
||||
MultipartFile chunkFile, Integer findSimilar) {
|
||||
FileUploadSession session = sessionRepository.findById(uploadId)
|
||||
@@ -219,7 +222,8 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
@Async("fileUploadTaskExecutor")
|
||||
@Transactional
|
||||
@Override
|
||||
public void completeFileProcessingAsync(FileUploadSession session, FileStatus status) {
|
||||
public FileEntity completeFileProcessingAsync(FileUploadSession session, FileStatus status) {
|
||||
FileEntity saved = null;
|
||||
try {
|
||||
Path filePath = Paths.get(session.getFilePath());
|
||||
String checksum = calculateChecksum(filePath);
|
||||
@@ -238,7 +242,7 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
.status(status)
|
||||
.build();
|
||||
|
||||
FileEntity saved = fileEntityRepository.save(fileEntity);
|
||||
saved = fileEntityRepository.save(fileEntity);
|
||||
|
||||
if (session.getFileType().equals("image")) {
|
||||
Map<String, Long> hash = imageHashService.calculateHash(filePath);
|
||||
@@ -248,16 +252,13 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
|
||||
cleanupSessionFiles(session);
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
noCopyFileService.addFile(fileUtil.createFileInfo(fileEntity));
|
||||
}
|
||||
|
||||
log.info("File processing completed for session: {}", session.getUploadId());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to complete file processing for session {}: {}",
|
||||
session.getUploadId(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
private void validateSession(FileUploadSession session) {
|
||||
@@ -289,6 +290,9 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
private UploadProgressResponse processChunk(FileUploadSession session, Integer chunkNumber, MultipartFile chunkFile,
|
||||
Integer findSimilar) {
|
||||
String chunkPath = null;
|
||||
@@ -329,8 +333,20 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
fileEntityService.clearTempFiles(session.getUserId());
|
||||
}
|
||||
|
||||
completeFileProcessingAsync(session, status);
|
||||
FileEntity fileEntity = completeFileProcessingAsync(session, status);
|
||||
|
||||
fileEntityRepository.flush();
|
||||
|
||||
entityManager.clear();
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getFilePath());
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
|
||||
|
||||
noCopyFileService.addFile(fileInfo);
|
||||
}
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
String fileType = session.getFileType();
|
||||
@@ -377,13 +393,13 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
|
||||
switch (fileType) {
|
||||
case "video":
|
||||
cost = TariffConstants.VIDEO_TOKEN_VALUE;
|
||||
return TariffConstants.VIDEO_TOKEN_VALUE;
|
||||
case "image":
|
||||
cost = TariffConstants.IMAGE_TOKEN_VALUE;
|
||||
return TariffConstants.IMAGE_TOKEN_VALUE;
|
||||
case "audio":
|
||||
cost = TariffConstants.AUDIO_TOKEN_VALUE;
|
||||
case "pdf":
|
||||
cost = TariffConstants.PDF_TOKEN_VALUE;
|
||||
return TariffConstants.AUDIO_TOKEN_VALUE;
|
||||
case "document":
|
||||
return TariffConstants.PDF_TOKEN_VALUE;
|
||||
}
|
||||
|
||||
return cost;
|
||||
|
||||
@@ -7,7 +7,10 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -24,22 +27,55 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
|
||||
private final FileEntityService fileEntityService;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getImageFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getVideoFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getAudioFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable File getDocument(@NotNull String id) {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +110,11 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeAudioFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
@@ -86,7 +126,11 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeVideoFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
@@ -98,7 +142,25 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeImageFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
return OperationResult.Companion.failure("Failed to create protected file");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OperationResult writeDocumentFile(@NotNull String id, @NotNull byte[] data) {
|
||||
try {
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, null);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package ru.soune.nocopy.service.referral;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.ReferralLevel;
|
||||
import ru.soune.ReferralLevelProvider;
|
||||
import ru.soune.nocopy.entity.referral.ReferralLevelEntity;
|
||||
import ru.soune.nocopy.repository.ReferralLevelRepository;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReferralLevelProviderImpl implements ReferralLevelProvider {
|
||||
|
||||
private final ReferralLevelRepository referralLevelRepository;
|
||||
|
||||
@Override
|
||||
public List<ReferralLevel> getAvailableReferralLevels() {
|
||||
List<ReferralLevelEntity> entities = referralLevelRepository.findAll();
|
||||
|
||||
return entities.stream()
|
||||
.map(this::createReferralLevel)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitialReferralLevelId() {
|
||||
return "bronze";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReferralLevel getReferralLevelById(String levelId) {
|
||||
return createReferralLevel(referralLevelRepository.findById(levelId));
|
||||
}
|
||||
|
||||
private ReferralLevel createReferralLevel(ReferralLevelEntity entity) {
|
||||
return new ReferralLevel() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return entity.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShare() {
|
||||
return entity.getRewardPercentage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinInvitee() {
|
||||
return entity.getMinInvitees();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInvitee() {
|
||||
switch (entity.getId()) {
|
||||
case "bronze": return 5;
|
||||
case "silver": return 15;
|
||||
case "gold": return 50;
|
||||
case "platinum": return Integer.MAX_VALUE;
|
||||
default: return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNext() {
|
||||
switch (entity.getId()) {
|
||||
case "bronze": return "silver";
|
||||
case "silver": return "gold";
|
||||
case "gold": return "platinum";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package ru.soune.nocopy.service.referral;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.soune.Referral;
|
||||
import ru.soune.ReferralInvitee;
|
||||
import ru.soune.ReferralRepo;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.repository.ReferralJpaRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class ReferralRepoImpl implements ReferralRepo {
|
||||
|
||||
private final ReferralJpaRepository referralRepository;
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public @NotNull String getUserReferralLink(long userId) {
|
||||
ru.soune.nocopy.entity.referral.Referral referral =
|
||||
referralRepository.findByUserId(userId);
|
||||
|
||||
if (referral != null && referral.getReferralLink() != null &&
|
||||
!referral.getReferralLink().isEmpty()) {
|
||||
return referral.getReferralLink();
|
||||
}
|
||||
|
||||
String newLink = "ref-" + userId + "-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
if (referral == null) {
|
||||
referral = new ru.soune.nocopy.entity.referral.Referral();
|
||||
referral.setUserId(userId);
|
||||
referral.setLevelId("bronze");
|
||||
referral.setActive(false);
|
||||
}
|
||||
|
||||
referral.setReferralLink(newLink);
|
||||
|
||||
referralRepository.save(referral);
|
||||
|
||||
return newLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Long getUserIdByLink(@NotNull String link) {
|
||||
ru.soune.nocopy.entity.referral.Referral referral = referralRepository.findByReferralLink(link);
|
||||
return referral != null ? referral.getUserId() : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Long getInviterIdForUser(long userId) {
|
||||
ru.soune.nocopy.entity.referral.Referral referral = referralRepository.findByUserId(userId);
|
||||
return referral != null ? referral.getInviterId() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getReferralLevelForUser(long userId) {
|
||||
ru.soune.nocopy.entity.referral.Referral referral = referralRepository.findByUserId(userId);
|
||||
return referral != null ? referral.getLevelId() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Referral getReferralByUserId(long userId) {
|
||||
ru.soune.nocopy.entity.referral.Referral entity = referralRepository.findByUserId(userId);
|
||||
|
||||
return new Referral() {
|
||||
@Override
|
||||
public long getUserId() { return entity.getUserId(); }
|
||||
|
||||
@Override
|
||||
public String getReferralLink() { return entity.getReferralLink(); }
|
||||
|
||||
@Override
|
||||
public Long getInviter() {
|
||||
return entity.getInviterId() != null ? entity.getInviterId() : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentLevel() { return entity.getLevelId(); }
|
||||
|
||||
@Override
|
||||
public int getTotalIncome() { return entity.getTotalIncome(); }
|
||||
|
||||
@Override
|
||||
public int getAvailableIncome() {
|
||||
Integer holdBalance = entity.getHoldBalance();
|
||||
return entity.getTotalIncome() - (holdBalance != null ? holdBalance : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHoldBalance() {
|
||||
Integer holdBalance = entity.getHoldBalance();
|
||||
return holdBalance != null ? holdBalance : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getActive() { return entity.isActive(); }
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ReferralInvitee> getReferralInvitees(long userId, int pageSize, int pageNumber) {
|
||||
Pageable pageable = PageRequest.of(pageNumber, pageSize);
|
||||
List<ru.soune.nocopy.entity.referral.Referral> referrals = referralRepository.findByInviterId(userId, pageable);
|
||||
|
||||
List<ReferralInvitee> result = new ArrayList<>();
|
||||
|
||||
for (ru.soune.nocopy.entity.referral.Referral referral : referrals) {
|
||||
User user = userRepository.findById(referral.getUserId()).orElse(null);
|
||||
|
||||
String email = user != null ? user.getEmail() : "";
|
||||
boolean isActive = referral.isActive();
|
||||
String regDate = referral.getCreatedAt() != null ?
|
||||
referral.getCreatedAt().toString() : "";
|
||||
|
||||
try {
|
||||
Constructor<ReferralInvitee> constructor = ReferralInvitee.class.getDeclaredConstructor(
|
||||
String.class, boolean.class, String.class
|
||||
);
|
||||
ReferralInvitee invitee = constructor.newInstance(email, isActive, regDate);
|
||||
result.add(invitee);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createReferralEntity(@NotNull Referral entity) {
|
||||
ru.soune.nocopy.entity.referral.Referral referral = new ru.soune.nocopy.entity.referral.Referral();
|
||||
referral.setUserId(entity.getUserId());
|
||||
referral.setReferralLink(entity.getReferralLink());
|
||||
referral.setInviterId(entity.getInviter());
|
||||
referral.setLevelId(entity.getCurrentLevel());
|
||||
referral.setTotalIncome(entity.getTotalIncome());
|
||||
referral.setHoldBalance(entity.getHoldBalance());
|
||||
referral.setActive(entity.getActive());
|
||||
|
||||
referralRepository.save(referral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increaseIncome(long userId, int transferAmount) {
|
||||
referralRepository.increaseIncome(userId, transferAmount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean activateUser(long userId) {
|
||||
try {
|
||||
referralRepository.activateUser(userId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveInviteeForUser(long userId) {
|
||||
return referralRepository.countActiveInvitees(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalInviteeForUser(long userId) {
|
||||
return referralRepository.countTotalInvitees(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgradeReferralLevelForUser(long userId, @NotNull String newLevelId) {
|
||||
referralRepository.updateLevel(userId, newLevelId);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.soune.nocopy.service.register;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -10,11 +11,13 @@ import ru.soune.nocopy.dto.register.LoginAnswer;
|
||||
import ru.soune.nocopy.dto.register.LoginRequest;
|
||||
import ru.soune.nocopy.dto.register.RegRequest;
|
||||
import ru.soune.nocopy.entity.company.Company;
|
||||
import ru.soune.nocopy.entity.tarif.Tariff;
|
||||
import ru.soune.nocopy.entity.tarif.TariffInfo;
|
||||
import ru.soune.nocopy.entity.tarif.TariffType;
|
||||
import ru.soune.nocopy.entity.user.AuthToken;
|
||||
import ru.soune.nocopy.entity.user.Permission;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.exception.MaxUserCountIsOver;
|
||||
import ru.soune.nocopy.exception.NotFoundAuthToken;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
@@ -176,20 +179,31 @@ public class AuthService {
|
||||
private void createIndividualCompanyAccount(RegRequest request, User user) {
|
||||
String authToken = request.getAuthToken();
|
||||
Long userIdByToken = authTokenRepository.findUserIdByToken(authToken);
|
||||
User companyUser = userRepository.findById(userIdByToken).orElseThrow();
|
||||
User companyUser = userRepository.findById(userIdByToken)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Not found token: " + authToken));
|
||||
|
||||
Company company = companyUser.getCompany();
|
||||
TariffInfo tariffInfo = company.getTariffInfo();
|
||||
|
||||
Long maxUsers = tariffInfo.getTariff().getMaxUsers();
|
||||
long currentCountUsers = userRepository.countByCompanyId(company.getId());
|
||||
|
||||
//TODO add exception
|
||||
if (currentCountUsers < maxUsers) {
|
||||
throw new RuntimeException();
|
||||
if (company == null) {
|
||||
throw new IllegalStateException("Not have company");
|
||||
}
|
||||
|
||||
user.setCompany(companyUser.getCompany());
|
||||
TariffInfo tariffInfo = company.getTariffInfo();
|
||||
if (tariffInfo == null) {
|
||||
throw new IllegalStateException("Company not have tariff");
|
||||
}
|
||||
|
||||
Tariff tariff = tariffInfo.getTariff();
|
||||
Long maxUsers = tariff.getMaxUsers();
|
||||
long currentCountUsers = userRepository.countByCompanyId(company.getId());
|
||||
|
||||
if (currentCountUsers >= maxUsers) {
|
||||
throw new NotValidFieldException("Max user is over.", new BaseResponse(20002,
|
||||
MessageCode.USER_LIMIT_IS_OVER.getCode(),
|
||||
MessageCode.USER_LIMIT_IS_OVER.getDescription(), Map.of(
|
||||
"limit", maxUsers, "countUser", currentCountUsers)));
|
||||
}
|
||||
|
||||
user.setCompany(company);
|
||||
user.grantPermission(Permission.DEFAULT_USER_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package ru.soune.nocopy.service.search;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchApiToYandexResponseMapper {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public YandexSearchResponse mapToYandexResponse(String searchApiJson) throws Exception {
|
||||
JsonNode root = objectMapper.readTree(searchApiJson);
|
||||
|
||||
YandexSearchResponse response = new YandexSearchResponse();
|
||||
List<YandexSearchResponse.ImageResult> imageResults = new ArrayList<>();
|
||||
|
||||
JsonNode visualMatches = root.path("visual_matches");
|
||||
|
||||
if (visualMatches.isArray()) {
|
||||
for (JsonNode match : visualMatches) {
|
||||
YandexSearchResponse.ImageResult result = new YandexSearchResponse.ImageResult();
|
||||
|
||||
|
||||
JsonNode imageNode = match.path("image");
|
||||
if (!imageNode.isMissingNode()) {
|
||||
result.setUrl(imageNode.path("link").asText());
|
||||
result.setWidth(imageNode.path("width").asInt());
|
||||
result.setHeight(imageNode.path("height").asInt());
|
||||
}
|
||||
|
||||
result.setPageUrl(match.path("link").asText());
|
||||
|
||||
result.setPageTitle(match.path("title").asText());
|
||||
|
||||
String source = match.path("source").asText();
|
||||
result.setHost(extractHostFromSource(source));
|
||||
|
||||
if (result.getUrl() != null && !result.getUrl().isEmpty()) {
|
||||
imageResults.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.setImages(imageResults);
|
||||
return response;
|
||||
}
|
||||
|
||||
private String extractHostFromSource(String source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
source = source.replaceFirst("^(https?://)?(www\\.)?", "");
|
||||
|
||||
int slashIndex = source.indexOf('/');
|
||||
if (slashIndex > 0) {
|
||||
return source.substring(0, slashIndex);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package ru.soune.nocopy.service.search;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SearchImageService {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
public SearchImageService() {
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.callTimeout(20, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Value("${yandex.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${yandex.folder-id}")
|
||||
private String folderId;
|
||||
|
||||
@Value("${yandex.search-url}")
|
||||
private String searchUrl;
|
||||
|
||||
@Value("${searchapi.api-key:}")
|
||||
private String searchApiKey;
|
||||
|
||||
@Value("${server.baseurl}")
|
||||
private String appBaseUrl;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
}
|
||||
|
||||
public String searchReverseByPublicUrl(FileEntity fileEntity, String engine, String searchType)
|
||||
throws IOException, TimeoutException {
|
||||
|
||||
if (!isImageFile(fileEntity)) {
|
||||
throw new NotValidFieldException(
|
||||
"File not image",
|
||||
new BaseResponse(
|
||||
20007,
|
||||
MessageCode.INVALID_FIELD.getCode(),
|
||||
MessageCode.INVALID_FIELD.getDescription(),
|
||||
Map.of("file_type", fileEntity.getMimeType())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
String publicUrl = String.format("%s/api/files/public/%s", appBaseUrl, fileEntity.getId());
|
||||
|
||||
log.info("Searching reverse for image: {}", publicUrl);
|
||||
|
||||
try {
|
||||
return callReverseImageApiByUrl(publicUrl, engine, searchType);
|
||||
} catch (SocketTimeoutException e) {
|
||||
log.error("Yandex search timeout after {}", fileEntity.getId());
|
||||
throw new TimeoutException("Search timeout");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isImageFile(FileEntity fileEntity) {
|
||||
String mimeType = fileEntity.getMimeType();
|
||||
return mimeType != null && mimeType.startsWith("image");
|
||||
}
|
||||
|
||||
private String callReverseImageApiByUrl(String imageUrl, String engine, String searchType) throws IOException {
|
||||
|
||||
if (searchApiKey == null || searchApiKey.isBlank()) {
|
||||
throw new IllegalStateException("SearchAPI key not configured");
|
||||
}
|
||||
|
||||
HttpUrl url = HttpUrl.parse("https://searchapi.io/api/v1/search")
|
||||
.newBuilder()
|
||||
.addQueryParameter("engine", engine)
|
||||
.addQueryParameter("api_key", searchApiKey)
|
||||
.addQueryParameter("url", imageUrl)
|
||||
.addQueryParameter("search_type", searchType)
|
||||
.addQueryParameter("wait", "true")
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("Accept", "application/json")
|
||||
.header("User-Agent", "Mozilla/5.0")
|
||||
.build();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
log.info("Yandex response code={}, duration={}ms", response.code(), duration);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("API error: " + response.code());
|
||||
}
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
throw new IOException("Empty response body");
|
||||
}
|
||||
|
||||
return body.string();
|
||||
}
|
||||
}
|
||||
|
||||
public List<YandexSearchResponse.ImageResult> getAllImagesWithoutPagination(String searchApiJson, String findType)
|
||||
throws IOException {
|
||||
JsonNode root = objectMapper.readTree(searchApiJson);
|
||||
JsonNode matches = root.path(findType);
|
||||
|
||||
List<YandexSearchResponse.ImageResult> allImages = new ArrayList<>();
|
||||
|
||||
if (matches.isArray()) {
|
||||
for (JsonNode match : matches) {
|
||||
YandexSearchResponse.ImageResult result = mapImageResult(match);
|
||||
|
||||
if ("exact_matches".equals(findType)) {
|
||||
JsonNode thumbnail = match.path("thumbnail");
|
||||
if (!thumbnail.isMissingNode() && thumbnail.asText().startsWith("data:image")) {
|
||||
result.setUrl(thumbnail.asText());
|
||||
}
|
||||
|
||||
JsonNode imageNode = match.path("image");
|
||||
if (!imageNode.isMissingNode()) {
|
||||
String directUrl = imageNode.path("link").asText();
|
||||
if (directUrl != null && !directUrl.isBlank()) {
|
||||
result.setUrl(directUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.getUrl() != null && !result.getUrl().isBlank()) {
|
||||
allImages.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allImages;
|
||||
}
|
||||
|
||||
public List<YandexSearchResponse.ImageResult> removeDuplicateUrls(
|
||||
List<YandexSearchResponse.ImageResult> yandexImages,
|
||||
List<YandexSearchResponse.ImageResult> googleImages) {
|
||||
|
||||
Map<String, YandexSearchResponse.ImageResult> uniqueByUrl = new LinkedHashMap<>();
|
||||
|
||||
for (YandexSearchResponse.ImageResult image : googleImages) {
|
||||
String normalizedUrl = normalizeUrl(image.getUrl());
|
||||
uniqueByUrl.putIfAbsent(normalizedUrl, image);
|
||||
}
|
||||
|
||||
for (YandexSearchResponse.ImageResult image : yandexImages) {
|
||||
String normalizedUrl = normalizeUrl(image.getUrl());
|
||||
uniqueByUrl.putIfAbsent(normalizedUrl, image);
|
||||
}
|
||||
|
||||
return new ArrayList<>(uniqueByUrl.values());
|
||||
}
|
||||
|
||||
public List<YandexSearchResponse.ImageResult> paginateResults(
|
||||
List<YandexSearchResponse.ImageResult> allResults,
|
||||
int page,
|
||||
int pageSize) {
|
||||
|
||||
if (allResults.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
int totalPages = (int) Math.ceil((double) allResults.size() / pageSize);
|
||||
|
||||
if (page > totalPages) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
int startIndex = (page - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, allResults.size());
|
||||
|
||||
return new ArrayList<>(allResults.subList(startIndex, endIndex));
|
||||
}
|
||||
|
||||
private String normalizeUrl(String url) {
|
||||
if (url == null) return null;
|
||||
|
||||
try {
|
||||
if (url.startsWith("data:image")) {
|
||||
return url.length() > 100 ? url.substring(0, 100) : url;
|
||||
}
|
||||
|
||||
java.net.URI uri = new java.net.URI(url);
|
||||
String host = uri.getHost();
|
||||
if (host != null) {
|
||||
host = host.toLowerCase().replaceFirst("^www\\.", "");
|
||||
}
|
||||
|
||||
String path = uri.getPath();
|
||||
if (path != null && path.length() > 1 && path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return (host != null ? host : "") + (path != null ? path : "");
|
||||
} catch (Exception e) {
|
||||
return url.toLowerCase()
|
||||
.replaceFirst("^(https?://)?(www\\.)?", "")
|
||||
.replaceFirst("/$", "");
|
||||
}
|
||||
}
|
||||
|
||||
private YandexSearchResponse.ImageResult mapImageResult(JsonNode match) {
|
||||
|
||||
YandexSearchResponse.ImageResult result =
|
||||
new YandexSearchResponse.ImageResult();
|
||||
|
||||
JsonNode imageNode = match.path("image");
|
||||
if (imageNode.isObject()) {
|
||||
result.setUrl(imageNode.path("link").asText());
|
||||
result.setWidth(imageNode.path("width").asInt(0));
|
||||
result.setHeight(imageNode.path("height").asInt(0));
|
||||
}
|
||||
|
||||
result.setPageUrl(match.path("link").asText());
|
||||
result.setPageTitle(match.path("title").asText());
|
||||
|
||||
String source = match.path("source").asText();
|
||||
result.setHost(extractHostFromSource(source));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String extractHostFromSource(String source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
source = source.replaceFirst("^(https?://)?(www\\.)?", "");
|
||||
|
||||
int slashIndex = source.indexOf('/');
|
||||
if (slashIndex > 0) {
|
||||
return source.substring(0, slashIndex);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package ru.soune.nocopy.service.search;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class YandexSearchService {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
public YandexSearchService() {
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.callTimeout(180, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Value("${yandex.api-key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${yandex.folder-id}")
|
||||
private String folderId;
|
||||
|
||||
@Value("${yandex.search-url}")
|
||||
private String searchUrl;
|
||||
|
||||
@Value("${searchapi.api-key:}")
|
||||
private String searchApiKey;
|
||||
|
||||
@Value("${server.baseurl}")
|
||||
private String appBaseUrl;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
}
|
||||
|
||||
public YandexSearchResponse searchByFileEntity(FileEntity fileEntity) throws IOException {
|
||||
byte[] fileBytes;
|
||||
|
||||
if (!isImageFile(fileEntity)) {
|
||||
log.error("File not image: {}", fileEntity.getMimeType());
|
||||
throw new NotValidFieldException("File not image", new BaseResponse(20007,
|
||||
MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(),
|
||||
Map.of("file_type", fileEntity.getMimeType())));
|
||||
}
|
||||
|
||||
try {
|
||||
fileBytes = readFileFromDisk(fileEntity);
|
||||
} catch (IOException e) {
|
||||
throw new NotValidFieldException("File not found or cannot read file", new BaseResponse(20007,
|
||||
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
|
||||
Map.of("fileId", fileEntity.getId(),
|
||||
"filePath", fileEntity.getFilePath())));
|
||||
}
|
||||
|
||||
return callYandexApi(fileBytes);
|
||||
}
|
||||
|
||||
|
||||
public String searchReverseByPublicUrl(FileEntity fileEntity) throws IOException {
|
||||
if (!isImageFile(fileEntity)) {
|
||||
log.error("File not image: {}", fileEntity.getMimeType());
|
||||
throw new NotValidFieldException("File not image", new BaseResponse(20007,
|
||||
MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(),
|
||||
Map.of("file_type", fileEntity.getMimeType())));
|
||||
}
|
||||
|
||||
String publicUrl = String.format("%s/api/files/public/%s", appBaseUrl, fileEntity.getId());
|
||||
|
||||
log.info("Searching reverse for image: {}", publicUrl);
|
||||
|
||||
return callReverseImageApiByUrl(publicUrl);
|
||||
}
|
||||
|
||||
|
||||
private String callReverseImageApiByUrl(String imageUrl) throws IOException {
|
||||
if (searchApiKey == null || searchApiKey.isEmpty()) {
|
||||
throw new IllegalStateException("SearchAPI key not configured. Set searchapi.api-key property");
|
||||
}
|
||||
|
||||
HttpUrl url = HttpUrl.parse("https://searchapi.io/api/v1/search")
|
||||
.newBuilder()
|
||||
.addQueryParameter("engine", "yandex_reverse_image")
|
||||
.addQueryParameter("api_key", searchApiKey)
|
||||
.addQueryParameter("url", imageUrl)
|
||||
.addQueryParameter("wait", "true")
|
||||
.addQueryParameter("timeout", "30000")
|
||||
.build();
|
||||
|
||||
log.info("Calling Yandex Reverse Image API: {}", url);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("Accept", "application/json")
|
||||
.header("User-Agent", "Mozilla/5.0")
|
||||
.build();
|
||||
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
log.info("Response code: {}", response.code());
|
||||
log.info("Response headers: {}", response.headers());
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
String errorBody = response.body() != null ? response.body().string() : "No error body";
|
||||
log.error("API request failed: {} - {}", response.code(), errorBody);
|
||||
throw new IOException("API request failed: " + response.code() + " - " + response.message());
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
log.info("Response body length: {} characters", responseBody.length());
|
||||
|
||||
return responseBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isImageFile(FileEntity fileEntity) {
|
||||
String mimeType = fileEntity.getMimeType();
|
||||
return mimeType != null && mimeType.startsWith("image");
|
||||
}
|
||||
|
||||
private byte[] readFileFromDisk(FileEntity fileEntity) throws IOException {
|
||||
Path filePath = Path.of(fileEntity.getFilePath());
|
||||
|
||||
if (!Files.exists(filePath)) {
|
||||
throw new IOException("File not found: " + fileEntity.getFilePath());
|
||||
}
|
||||
|
||||
if (!Files.isReadable(filePath)) {
|
||||
throw new IOException("Cannot read file: " + fileEntity.getFilePath());
|
||||
}
|
||||
|
||||
return Files.readAllBytes(filePath);
|
||||
}
|
||||
|
||||
private YandexSearchResponse callYandexApi(byte[] imageBytes) throws IOException {
|
||||
String imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
|
||||
String jsonRequest = String.format("{\"folderId\":\"%s\",\"data\":\"%s\",\"page\":0,\"showSimilarImages\":true}",
|
||||
folderId, imageBase64);
|
||||
|
||||
URL url = new URL(searchUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
try {
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Authorization", "Api-Key " + apiKey);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setConnectTimeout(30000);
|
||||
connection.setReadTimeout(30000);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonRequest.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
os.flush();
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
String responseBody;
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), "utf-8"))) {
|
||||
responseBody = readAll(br);
|
||||
}
|
||||
} else {
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(connection.getErrorStream(), "utf-8"))) {
|
||||
responseBody = readAll(br);
|
||||
}
|
||||
throw new IOException("Error Yandex API: " + responseCode + " - " + responseBody);
|
||||
}
|
||||
|
||||
return parseJsonResponse(responseBody);
|
||||
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private String readAll(BufferedReader reader) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private YandexSearchResponse parseJsonResponse(String json) {
|
||||
YandexSearchResponse response = null;
|
||||
try {
|
||||
response = objectMapper.readValue(json, YandexSearchResponse.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public class TariffInfoService {
|
||||
tariffInfo.setStatus(tariffStatus);
|
||||
tariffInfo.setEndTariff(endTariff);
|
||||
tariffInfo.setTokens(tokens);
|
||||
tariffInfo.setBoughtTokens(0);
|
||||
|
||||
return tariffInfoRepository.save(tariffInfo);
|
||||
}
|
||||
@@ -52,10 +53,15 @@ public class TariffInfoService {
|
||||
}
|
||||
|
||||
Integer tokens = activeTariffInfo.getTokens();
|
||||
Integer boughtTokens = activeTariffInfo.getBoughtTokens();
|
||||
|
||||
if (tokens - value >= 0) {
|
||||
activeTariffInfo.setTokens(activeTariffInfo.getTokens() - value);
|
||||
|
||||
tariffInfoRepository.save(activeTariffInfo);
|
||||
} else if (boughtTokens - value >= 0) {
|
||||
activeTariffInfo.setTokens(activeTariffInfo.getBoughtTokens() - value);
|
||||
|
||||
tariffInfoRepository.save(activeTariffInfo);
|
||||
} else {
|
||||
throw new TariffNotFoundException("User not have tokens for protect");
|
||||
@@ -118,7 +124,8 @@ public class TariffInfoService {
|
||||
tariffInfo.getStartTariff(),
|
||||
tariffInfo.getEndTariff(),
|
||||
tariffInfo.getTariff() != null ? tariffInfo.getTariff().getId() : null,
|
||||
tariffInfo.getTariff() != null ? tariffInfo.getTariff().getName() : null, tariffInfo.getTokens(),
|
||||
tariffInfo.getTariff() != null ? tariffInfo.getTariff().getName() : null,
|
||||
tariffInfo.getTokens() + tariffInfo.getBoughtTokens(),
|
||||
tariffInfo.getTariff().getDiskSize(), 0L, 0,
|
||||
tariffInfo.getTariff().getMaxFilesCount());
|
||||
}
|
||||
|
||||
@@ -101,40 +101,44 @@ public class TariffInitializationService {
|
||||
tariff.setType(type.name());
|
||||
|
||||
switch (type) {
|
||||
case DEMO:
|
||||
tariff.setName("demo");
|
||||
tariff.setPrice(demoPrice);
|
||||
tariff.setTokens(demoTokens);
|
||||
tariff.setMaxFilesCount(demoMaxFileCount);
|
||||
case FREE:
|
||||
tariff.setName("free");
|
||||
tariff.setPrice(0);
|
||||
tariff.setTokens(30);
|
||||
tariff.setMaxFilesCount(10);
|
||||
tariff.setDiskSize(1024L * 1024 * 100);
|
||||
tariff.setMaxUsers(demoUserCount);
|
||||
tariff.setMaxUsers(1L);
|
||||
tariff.setTariffAccountType("b2c");
|
||||
break;
|
||||
|
||||
case START:
|
||||
tariff.setName("start");
|
||||
tariff.setPrice(startPrice);
|
||||
tariff.setTokens(startTokens);
|
||||
tariff.setMaxFilesCount(startMaxFileCount);
|
||||
tariff.setPrice(1200);
|
||||
tariff.setTokens(300);
|
||||
tariff.setMaxFilesCount(80);
|
||||
tariff.setDiskSize(1024L * 1024 * 500);
|
||||
tariff.setMaxUsers(startUserCount);
|
||||
tariff.setMaxUsers(1L);
|
||||
tariff.setTariffAccountType("b2c");
|
||||
break;
|
||||
|
||||
case PRO:
|
||||
tariff.setName("pro");
|
||||
tariff.setPrice(proPrice);
|
||||
tariff.setTokens(proTokens);
|
||||
tariff.setMaxFilesCount(proMaxFileCount);
|
||||
tariff.setPrice(4250);
|
||||
tariff.setTokens(900);
|
||||
tariff.setMaxFilesCount(300);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 10L);
|
||||
tariff.setMaxUsers(proUserCount);
|
||||
tariff.setMaxUsers(1L);
|
||||
tariff.setTariffAccountType("b2c");
|
||||
break;
|
||||
|
||||
case ENTERPRISE:
|
||||
tariff.setName("premium");
|
||||
tariff.setPrice(enterpricePrice);
|
||||
tariff.setTokens(enterpriceTokens);
|
||||
tariff.setMaxFilesCount(enterpriceMaxFileCount);
|
||||
case STUDIO:
|
||||
tariff.setName("studio");
|
||||
tariff.setPrice(9750);
|
||||
tariff.setTokens(3000);
|
||||
tariff.setMaxFilesCount(1000);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 50L);
|
||||
tariff.setMaxUsers(enterpriceUserCount);
|
||||
tariff.setMaxUsers(2L);
|
||||
tariff.setTariffAccountType("b2c");
|
||||
break;
|
||||
|
||||
case BASIC:
|
||||
@@ -144,6 +148,37 @@ public class TariffInitializationService {
|
||||
tariff.setMaxFilesCount(enterpriceMaxFileCount);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 50L);
|
||||
tariff.setMaxUsers(basicUserCount);
|
||||
tariff.setTariffAccountType("b2c");
|
||||
break;
|
||||
|
||||
case ENTERPRISE:
|
||||
tariff.setName("enterprise");
|
||||
tariff.setPrice(200000);
|
||||
tariff.setTokens(30000);
|
||||
tariff.setMaxFilesCount(100000);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 10L);
|
||||
tariff.setMaxUsers(10L);
|
||||
tariff.setTariffAccountType("b2b");
|
||||
break;
|
||||
|
||||
case BUISNES:
|
||||
tariff.setName("buisnes");
|
||||
tariff.setPrice(29000);
|
||||
tariff.setTokens(5000);
|
||||
tariff.setMaxFilesCount(5000);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 50L);
|
||||
tariff.setMaxUsers(5L);
|
||||
tariff.setTariffAccountType("b2b");
|
||||
break;
|
||||
|
||||
case CORPORAT:
|
||||
tariff.setName("corporat");
|
||||
tariff.setPrice(99000);
|
||||
tariff.setTokens(20000);
|
||||
tariff.setMaxFilesCount(50000);
|
||||
tariff.setDiskSize(1024L * 1024 * 1024 * 50L);
|
||||
tariff.setMaxUsers(10L);
|
||||
tariff.setTariffAccountType("b2b");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.soune.nocopy.dto.tarriff.TariffDTO;
|
||||
import ru.soune.nocopy.entity.tarif.Tariff;
|
||||
import ru.soune.nocopy.entity.tarif.TariffType;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.exception.TariffNotFoundException;
|
||||
import ru.soune.nocopy.repository.TariffRepository;
|
||||
|
||||
@@ -32,6 +33,15 @@ public class TariffService {
|
||||
tariffRepository.save(tariff);
|
||||
}
|
||||
|
||||
public List<TariffDTO> getTariffByAccountType(User user) {
|
||||
String typeAccount = user.canManageCompanySettings() ? "b2b": "b2c";
|
||||
|
||||
return tariffRepository.findByTariffAccountType(typeAccount)
|
||||
.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<TariffDTO> getAllTariffs() {
|
||||
return tariffRepository.findAllByOrderByPriceAsc()
|
||||
.stream()
|
||||
|
||||
@@ -41,6 +41,8 @@ public class FileUtil {
|
||||
return FileProtector.Type.VIDEO;
|
||||
} else if (mimeType.startsWith("audio")) {
|
||||
return FileProtector.Type.AUDIO;
|
||||
} else if (mimeType.startsWith("document")) {
|
||||
return FileProtector.Type.DOC;
|
||||
} else {
|
||||
return FileProtector.Type.IMAGE;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ spring:
|
||||
enabled: true
|
||||
resolve-lazily: false
|
||||
|
||||
dadata:
|
||||
api-key: 0109fcc9d5a0a68c08f3b12ba7e2a620ea438ef7
|
||||
url: "https://suggestions.dadata.ru/suggestions/api/4_1/rs/findById/party"
|
||||
|
||||
file:
|
||||
storage:
|
||||
base-path: ${FILE_STORAGE_PATH:/data/uploads}
|
||||
@@ -72,6 +76,15 @@ security:
|
||||
server:
|
||||
port: ${SERVER_PORT:8080}
|
||||
baseurl: "http://92.242.61.23"
|
||||
tomcat:
|
||||
uri-encoding: UTF-8
|
||||
servlet:
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
force: true
|
||||
force-request: true
|
||||
force-response: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
@@ -87,9 +100,16 @@ yandex:
|
||||
api-key: ${YANDEX_API_KEY:AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q}
|
||||
folder-id: ${YANDEX_FOLDER_ID:b1gokpdbm6qfpsou8pcd}
|
||||
search-url: ${YANDEX_SEARCH_URL:https://searchapi.api.cloud.yandex.net/v2/image/search_by_image}
|
||||
cloud:
|
||||
key: "YCAJEmHZcWxzf4oGWvETG3mms"
|
||||
secret-key: "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG"
|
||||
region: "ru-central1"
|
||||
s3-endpoint: "https://storage.yandexcloud.net"
|
||||
bucket: "no-copy-storage"
|
||||
|
||||
|
||||
searchapi:
|
||||
api-key: ${SEARCHAPI_API_KEY:2xBChafVmojL7G1ZXzzUuA6o}
|
||||
api-key: ${SEARCHAPI_API_KEY:rdwNzCr2eybYVbU1KrGTJAgm}
|
||||
reverse-image-url: "https://searchapi.io/api/v1/search"
|
||||
|
||||
app:
|
||||
@@ -129,3 +149,4 @@ tarif:
|
||||
enterprice-max-files-count: 50
|
||||
enterprice-disk-size: 500
|
||||
enterprice-user-count: 80
|
||||
|
||||
|
||||
Reference in New Issue
Block a user