a working max file size setting in the admin panel

This commit is contained in:
Rostislav Raykov
2024-12-01 18:16:46 +02:00
parent 889108f9fa
commit b99e77d6e5
17 changed files with 116 additions and 62 deletions

View File

@@ -88,8 +88,9 @@ java -jar target/quickdrop-0.0.1-SNAPSHOT.jar
```
Using an external application.properties file:
- Create an **application.properties** file in the same directory as the JAR file or specify its location in the
start command.
- Create an **application.properties** file in the same directory as the JAR file or specify its location in the
start command.
- Add your custom settings, for example (Listed below are the default values):

10
pom.xml
View File

@@ -185,6 +185,16 @@
test
</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>4.1.3</version>
</dependency>
</dependencies>
<build>

View File

@@ -1,8 +1,5 @@
package org.rostislav.quickdrop;
import java.nio.file.Files;
import java.nio.file.Path;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,6 +8,9 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.nio.file.Files;
import java.nio.file.Path;
@SpringBootApplication
@EnableScheduling
public class QuickdropApplication {

View File

@@ -1,8 +1,9 @@
package org.rostislav.quickdrop.config;
import jakarta.servlet.MultipartConfigElement;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
@@ -10,16 +11,18 @@ import org.springframework.util.unit.DataSize;
@Configuration
public class MultipartConfig {
private final long ADDITIONAL_REQUEST_SIZE = 1024L * 1024L * 10L; // 10 MB
@Value("${max-upload-file-size}")
private String maxUploadFileSize;
@Autowired
private MultipartProperties multipartProperties;
@Bean
@RefreshScope
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.parse(maxUploadFileSize));
factory.setMaxFileSize(DataSize.parse(multipartProperties.getMaxFileSize()));
DataSize maxRequestSize = DataSize.parse(maxUploadFileSize);
DataSize maxRequestSize = DataSize.parse(multipartProperties.getMaxFileSize());
maxRequestSize = DataSize.ofBytes(maxRequestSize.toBytes() + ADDITIONAL_REQUEST_SIZE);
factory.setMaxRequestSize(maxRequestSize);

View File

@@ -0,0 +1,19 @@
package org.rostislav.quickdrop.config;
import org.rostislav.quickdrop.repository.ApplicationSettingsRepository;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@RefreshScope
@Component
public class MultipartProperties {
private final ApplicationSettingsRepository applicationSettingsRepository;
public MultipartProperties(ApplicationSettingsRepository applicationSettingsRepository) {
this.applicationSettingsRepository = applicationSettingsRepository;
}
public String getMaxFileSize() {
return "" + applicationSettingsRepository.findById(1L).orElseThrow().getMaxFileSize();
}
}

View File

@@ -28,9 +28,11 @@ public class AdminViewController {
@GetMapping("/settings")
public String getSettingsPage(Model model) {
ApplicationSettingsEntity settings = applicationSettingsService.getApplicationSettings();
settings.setMaxFileSize(bytesToMegabytes(settings.getMaxFileSize()));
model.addAttribute("settings", settings);
ApplicationSettingsEntity applicationSettingsEntity = new ApplicationSettingsEntity(settings);
applicationSettingsEntity.setMaxFileSize(bytesToMegabytes(settings.getMaxFileSize()));
model.addAttribute("settings", applicationSettingsEntity);
return "admin/settings";
}

View File

@@ -2,6 +2,7 @@ package org.rostislav.quickdrop.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.rostislav.quickdrop.model.FileEntity;
import org.rostislav.quickdrop.service.ApplicationSettingsService;
import org.rostislav.quickdrop.service.FileService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
@@ -22,18 +23,18 @@ import static org.rostislav.quickdrop.util.FileUtils.populateModelAttributes;
@RequestMapping("/file")
public class FileViewController {
private final FileService fileService;
@Value("${max-upload-file-size}")
private String maxFileSize;
private final ApplicationSettingsService applicationSettingsService;
@Value("${file.max.age}")
private String maxFileLifeTime;
public FileViewController(FileService fileService) {
public FileViewController(FileService fileService, ApplicationSettingsService applicationSettingsService) {
this.fileService = fileService;
this.applicationSettingsService = applicationSettingsService;
}
@GetMapping("/upload")
public String showUploadFile(Model model) {
model.addAttribute("maxFileSize", maxFileSize);
model.addAttribute("maxFileSize", applicationSettingsService.getFormattedMaxFileSize());
model.addAttribute("maxFileLifeTime", maxFileLifeTime);
return "upload";
}

View File

@@ -20,6 +20,21 @@ public class ApplicationSettingsEntity {
private String appPasswordHash;
private String adminPasswordHash;
public ApplicationSettingsEntity() {
}
public ApplicationSettingsEntity(ApplicationSettingsEntity settings) {
this.id = settings.id;
this.maxFileSize = settings.maxFileSize;
this.maxFileLifeTime = settings.maxFileLifeTime;
this.fileStoragePath = settings.fileStoragePath;
this.logStoragePath = settings.logStoragePath;
this.fileDeletionCron = settings.fileDeletionCron;
this.appPasswordEnabled = settings.appPasswordEnabled;
this.appPasswordHash = settings.appPasswordHash;
this.adminPasswordHash = settings.adminPasswordHash;
}
public long getMaxFileSize() {
return maxFileSize;
}

View File

@@ -1,12 +1,8 @@
package org.rostislav.quickdrop.model;
import java.time.LocalDate;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import java.time.LocalDate;
@Entity
public class FileEntity {

View File

@@ -1,14 +1,14 @@
package org.rostislav.quickdrop.repository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.rostislav.quickdrop.model.FileEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
public interface FileRepository extends JpaRepository<FileEntity, Long> {
@Query("SELECT f FROM FileEntity f WHERE f.uuid = :uuid")
Optional<FileEntity> findByUUID(@Param("uuid") String uuid);

View File

@@ -2,17 +2,23 @@ package org.rostislav.quickdrop.service;
import org.rostislav.quickdrop.model.ApplicationSettingsEntity;
import org.rostislav.quickdrop.repository.ApplicationSettingsRepository;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;
import static org.rostislav.quickdrop.util.FileUtils.formatFileSize;
@Service
public class ApplicationSettingsService {
private final ConfigurableApplicationContext applicationContext;
private final ApplicationSettingsRepository applicationSettingsRepository;
private final ContextRefresher contextRefresher;
private ApplicationSettingsEntity applicationSettings;
public ApplicationSettingsService(ApplicationSettingsRepository applicationSettingsRepository, ApplicationContext applicationContext) {
public ApplicationSettingsService(ApplicationSettingsRepository applicationSettingsRepository, ApplicationContext applicationContext, @Qualifier("configDataContextRefresher") ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
this.applicationSettingsRepository = applicationSettingsRepository;
@@ -27,7 +33,6 @@ public class ApplicationSettingsService {
settings.setAppPasswordHash("");
settings.setAdminPasswordHash("");
settings = applicationSettingsRepository.save(settings);
this.applicationContext.refresh();
return settings;
});
}
@@ -47,12 +52,18 @@ public class ApplicationSettingsService {
applicationSettingsRepository.save(applicationSettingsEntity);
this.applicationSettings = applicationSettingsEntity;
contextRefresher.refresh();
}
public long getMaxFileSize() {
return applicationSettings.getMaxFileSize();
}
public String getFormattedMaxFileSize() {
return formatFileSize(applicationSettings.getMaxFileSize());
}
public long getMaxFileLifeTime() {
return applicationSettings.getMaxFileLifeTime();
}

View File

@@ -33,11 +33,11 @@ import static org.rostislav.quickdrop.util.FileEncryptionUtils.encryptFile;
@Service
public class FileService {
@Value("${file.save.path}")
private String fileSavePath;
private static final Logger logger = LoggerFactory.getLogger(FileService.class);
private final FileRepository fileRepository;
private final PasswordEncoder passwordEncoder;
@Value("${file.save.path}")
private String fileSavePath;
public FileService(FileRepository fileRepository, PasswordEncoder passwordEncoder) {
this.fileRepository = fileRepository;
@@ -184,10 +184,10 @@ public class FileService {
Resource resource = new UrlResource(outputFile.toUri());
logger.info("Sending file: {}", fileEntity);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileEntity.name, StandardCharsets.UTF_8) + "\"")
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength()))
.body(responseBody);
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileEntity.name, StandardCharsets.UTF_8) + "\"")
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength()))
.body(responseBody);
} catch (
Exception e) {
logger.error("Error reading file: {}", e.getMessage());

View File

@@ -1,8 +1,5 @@
package org.rostislav.quickdrop.service;
import java.time.LocalDate;
import java.util.List;
import org.rostislav.quickdrop.model.FileEntity;
import org.rostislav.quickdrop.repository.FileRepository;
import org.slf4j.Logger;
@@ -11,6 +8,9 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
@Service
public class ScheduleService {
private static final Logger logger = LoggerFactory.getLogger(ScheduleService.class);

View File

@@ -15,6 +15,5 @@ logging.file.name=log/quickdrop.log
file.deletion.cron=0 0 2 * * *
app.basic.password=test
app.enable.password=false
max-upload-file-size=1GB
#logging.level.org.springframework=DEBUG
#logging.level.org.hibernate=DEBUG

View File

@@ -1,9 +1,5 @@
package org.rostislav.quickdrop;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -20,46 +16,45 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.multipart.MultipartFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.rostislav.quickdrop.TestDataContainer.getEmptyFileUploadRequest;
import static org.rostislav.quickdrop.TestDataContainer.getFileEntity;
import static org.rostislav.quickdrop.TestDataContainer.getFileUploadRequest;
import static org.rostislav.quickdrop.TestDataContainer.*;
@SpringBootTest
@ExtendWith(MockitoExtension.class)
public class FileServiceTests {
@Nested
class SaveFileTests {
@Value("${file.save.path}")
private String fileSavePath;
@Autowired
FileService fileService;
@MockBean
FileRepository fileRepository;
@MockBean
PasswordEncoder passwordEncoder;
@Value("${file.save.path}")
private String fileSavePath;
@AfterEach
void tearDown() {
//Delete the all files in the fileSavePath
try {
Files.walk(Path.of(fileSavePath))
.filter(Files::isRegularFile)
.forEach(file -> {
try {
Files.delete(file);
} catch (
IOException e) {
e.printStackTrace();
}
});
.filter(Files::isRegularFile)
.forEach(file -> {
try {
Files.delete(file);
} catch (
IOException e) {
e.printStackTrace();
}
});
} catch (
IOException e) {
e.printStackTrace();

View File

@@ -1,10 +1,10 @@
package org.rostislav.quickdrop;
import java.util.UUID;
import org.rostislav.quickdrop.model.FileEntity;
import org.rostislav.quickdrop.model.FileUploadRequest;
import java.util.UUID;
public class TestDataContainer {
public static FileEntity getFileEntity() {

View File

@@ -9,6 +9,8 @@ spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
server.tomcat.connection-timeout=60000
management.endpoint.refresh.enabled=true
management.endpoints.web.exposure.include=refresh
file.save.path=files
file.max.age=30
logging.file.name=log/quickdrop.log