7 Commits
Author SHA1 Message Date
vladp 19d8f61e1d NCBACK-26 add docker container with storage
Test Workflow / test (push) Successful in 1s
2025-12-23 16:05:51 +07:00
vladp 8fe4d854a6 add create uploads filedir
Test Workflow / test (push) Successful in 1s
2025-12-23 13:10:36 +07:00
vladp 4c607f5bf9 fix check format
Test Workflow / test (push) Successful in 6s
2025-12-23 12:46:59 +07:00
vladp ce879fea18 add format for all office,os
Test Workflow / test (push) Successful in 1s
2025-12-23 12:03:49 +07:00
vladp 33479ea9af add check extensions
Test Workflow / test (push) Successful in 3s
2025-12-22 17:17:48 +07:00
vladp 76e8f4ab1d add image type
Test Workflow / test (push) Successful in 3s
2025-12-22 10:59:36 +07:00
backdev c03ca2566d Merge pull request 'add bone for file system' (#9) from NCBACK-15 into dev
Test Workflow / test (push) Successful in 9s
Reviewed-on: #9
2025-12-21 17:19:34 +08:00
7 changed files with 111 additions and 35 deletions
+2
View File
@@ -12,6 +12,8 @@ WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
RUN mkdir -p /data/uploads && chmod 755 /data/uploads
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
+13 -2
View File
@@ -1,6 +1,16 @@
version: '3.9'
services:
storage:
image: alpine:latest
container_name: file-storage
networks:
- app-network
volumes:
- uploads_data:/storage:rw
command: tail -f /dev/null
restart: unless-stopped
db:
image: postgres:17
restart: always
@@ -30,6 +40,7 @@ services:
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081
depends_on:
- db
ports:
@@ -41,7 +52,7 @@ services:
- backend
- api
volumes:
- ./uploads:/data/uploads:rw
- uploads_data:/data/uploads:rw
grafana:
image: grafana/grafana:10.3.1
@@ -146,7 +157,7 @@ volumes:
loki_chunks:
loki_index:
loki_rules:
# uploads_volume:
uploads_data:
networks:
app-network:
+1
View File
@@ -114,6 +114,7 @@ EOF
--network app-network \\
--network-alias app \\
-p 80:8080 \\
-v /opt/uploads:/data/uploads:rw \\
-e POSTGRES_DB=no_copy_ \\
-e POSTGRES_USER=postgres \\
-e POSTGRES_PASSWORD=postgres \\
@@ -387,11 +387,11 @@ public class ApiController {
return false;
}
return mimeType.startsWith("image/") ||
mimeType.startsWith("text/") ||
mimeType.equals("application/pdf") ||
mimeType.startsWith("video/") ||
mimeType.startsWith("audio/");
return mimeType.startsWith("image") ||
mimeType.startsWith("text") ||
mimeType.equals("pdf") ||
mimeType.startsWith("video") ||
mimeType.startsWith("audio");
}
private Long getUserIdFromToken(String tokenHeader) {
@@ -2,20 +2,47 @@ package ru.soune.nocopy.entity.file;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
@Getter
public enum FileType {
PHOTO("photo"),
VIDEO("video"),
AUDIO("audio"),
DOCUMENT("document");
PHOTO("photo", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp")),
IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "svg", "tiff", "tif", "ico",
"psd", "ai", "eps", "raw", "heic", "heif")),
VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg",
"3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")),
AUDIO("audio", Arrays.asList("mp3", "wav", "ogg", "aac", "flac", "m4a", "wma", "aiff", "aif", "amr",
"opus", "mka", "ac3", "alac")),
DOCUMENT("document", Arrays.asList(
"pdf", "txt", "rtf",
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pps", "ppsx", "dot", "dotx", "xlt", "xltx", "pot", "potx",
"odt", "ods", "odp", "odg", "odf", "odb", "odc", "odi", "odm", "ott", "ots", "otp", "otg", "oth",
"sxw", "sxc", "sxi", "sxd", "sxg", "stc", "sti", "stw", "sxm",
"pages", "numbers", "key",
"csv", "tsv", "xml", "html", "htm", "tex", "md", "markdown",
"epub", "mobi", "azw", "azw3", "fb2",
"wps", "wpt", "et", "dps", "vsd", "vsdx",
"java", "py", "cpp", "c", "h", "js", "css", "php", "sql", "json", "yaml", "yml", "sh", "bat",
"one", "note"));
private final String code;
private final String displayName;
private final List<String> allowedExtensions;
FileType(String code) {
this.code = code;
FileType(String displayName, List<String> allowedExtensions) {
this.displayName = displayName;
this.allowedExtensions = allowedExtensions;
}
public String getCode() {
return code;
public String getDisplayName() {
return displayName;
}
public List<String> getAllowedExtensions() {
return allowedExtensions;
}
public boolean supportsExtension(String extension) {
return allowedExtensions.contains(extension.toLowerCase());
}
}
@@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.entity.AuthToken;
@@ -17,10 +18,8 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileUploadSessionRepository;
import ru.soune.nocopy.service.file.FileUploadService;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component
@@ -70,18 +69,31 @@ public class FileUploadHandler implements RequestHandler {
}
private BaseResponse handleInitUpload(BaseRequest request, FileUploadRequest fileRequest) {
try {
String token = fileRequest.getToken();
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
if (tokenOptional.isEmpty()) {
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
MessageCode.INVALID_TOKEN.getDescription(), Map.of("token", token));
}
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest");
fileUploadRequestValidator.validate(fileRequest, bindingResult);
if (bindingResult.hasErrors()) {
throw new ValidationException(bindingResult, request.getMsgId());
Map<String, String> fieldErrors = bindingResult.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
fieldError -> fieldError.getDefaultMessage() != null
? fieldError.getDefaultMessage()
: "Validation error"));
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), fieldErrors);
}
FileUploadSession session = fileUploadService.initUpload(
@@ -101,15 +113,6 @@ public class FileUploadHandler implements RequestHandler {
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), fileResponse);
} catch (NotFoundAuthToken e) {
InitFileResponse initFileResponse = InitFileResponse.builder()
.build();
throw new NotValidFieldException("Invalid or expired token: " + fileRequest.getToken(),
new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
MessageCode.INVALID_TOKEN.getDescription(), initFileResponse));
}
}
private BaseResponse handleGetProgress(BaseRequest request, FileUploadRequest fileRequest) {
@@ -7,9 +7,12 @@ import ru.soune.nocopy.dto.file.FileUploadRequest;
import ru.soune.nocopy.entity.file.FileType;
import java.util.Arrays;
import java.util.List;
@Component
public class FileUploadRequestValidator implements Validator {
private final List<FileType> supportedFileTypes = Arrays.asList(FileType.values());
@Override
public boolean supports(Class<?> clazz) {
return FileUploadRequest.class.isAssignableFrom(clazz);
@@ -21,7 +24,7 @@ public class FileUploadRequestValidator implements Validator {
validateType(request.getFileType(), errors);
validateFileName(request.getFileName(), errors);
validateExtension(request.getExtension(), errors);
validateExtension(request.getExtension(), request.getFileType(), errors);
}
private void validateType(String fileType, Errors errors) {
@@ -31,7 +34,12 @@ public class FileUploadRequestValidator implements Validator {
}
try {
FileType.valueOf(fileType.toUpperCase());
FileType parsedType = FileType.valueOf(fileType.toUpperCase());
if (!supportedFileTypes.contains(parsedType)) {
errors.rejectValue("fileType", "fileType.unsupported",
"Unsupported file type. Valid types: " + Arrays.toString(FileType.values()));
}
} catch (IllegalArgumentException e) {
errors.rejectValue("fileType", "fileType.invalid",
"Invalid file type. Valid types: " + Arrays.toString(FileType.values()));
@@ -50,10 +58,34 @@ public class FileUploadRequestValidator implements Validator {
}
}
private void validateExtension(String extension, Errors errors) {
private void validateExtension(String extension, String fileType, Errors errors) {
if (extension == null || extension.isBlank()) {
errors.rejectValue("extension", "extension.required", "Extension is required");
errors.rejectValue("fileType", "fileType.required", "File type is required");
return;
}
if (extension.contains(".")) {
errors.rejectValue("extension", "extension.required", "Extension contains comma");
return;
}
try {
FileType parsedType = FileType.valueOf(fileType.toUpperCase());
if (!parsedType.supportsExtension(extension)) {
errors.rejectValue("fileName", "fileType.extension.mismatch",
String.format("File extension '%s' does not match file type '%s'. Allowed extensions for %s: %s",
extension, parsedType.getDisplayName(), parsedType.getDisplayName(),
parsedType.getAllowedExtensions()));
}
} catch (IllegalArgumentException e) {
errors.rejectValue("fileType", "fileType.invalid",
"Invalid file type. Valid types: " + Arrays.toString(FileType.values()));
}
}
private String getFileExtension(String fileName) {
return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
}
}