diff --git a/README.md b/README.md index 1e47bce..67aaff9 100644 --- a/README.md +++ b/README.md @@ -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): diff --git a/pom.xml b/pom.xml index 8306494..2748451 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,16 @@ test + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter + 4.1.3 + + diff --git a/src/main/java/org/rostislav/quickdrop/QuickdropApplication.java b/src/main/java/org/rostislav/quickdrop/QuickdropApplication.java index 4a95011..7616e21 100644 --- a/src/main/java/org/rostislav/quickdrop/QuickdropApplication.java +++ b/src/main/java/org/rostislav/quickdrop/QuickdropApplication.java @@ -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 { diff --git a/src/main/java/org/rostislav/quickdrop/config/MultipartConfig.java b/src/main/java/org/rostislav/quickdrop/config/MultipartConfig.java index 6a61137..b075980 100644 --- a/src/main/java/org/rostislav/quickdrop/config/MultipartConfig.java +++ b/src/main/java/org/rostislav/quickdrop/config/MultipartConfig.java @@ -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); diff --git a/src/main/java/org/rostislav/quickdrop/config/MultipartProperties.java b/src/main/java/org/rostislav/quickdrop/config/MultipartProperties.java new file mode 100644 index 0000000..4aa4b4b --- /dev/null +++ b/src/main/java/org/rostislav/quickdrop/config/MultipartProperties.java @@ -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(); + } +} diff --git a/src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java b/src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java index 6d5c073..49bac78 100644 --- a/src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java +++ b/src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java @@ -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"; } diff --git a/src/main/java/org/rostislav/quickdrop/controller/FileViewController.java b/src/main/java/org/rostislav/quickdrop/controller/FileViewController.java index fdde2c5..7e86a65 100644 --- a/src/main/java/org/rostislav/quickdrop/controller/FileViewController.java +++ b/src/main/java/org/rostislav/quickdrop/controller/FileViewController.java @@ -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"; } diff --git a/src/main/java/org/rostislav/quickdrop/model/ApplicationSettingsEntity.java b/src/main/java/org/rostislav/quickdrop/model/ApplicationSettingsEntity.java index 319f47e..9d997c2 100644 --- a/src/main/java/org/rostislav/quickdrop/model/ApplicationSettingsEntity.java +++ b/src/main/java/org/rostislav/quickdrop/model/ApplicationSettingsEntity.java @@ -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; } diff --git a/src/main/java/org/rostislav/quickdrop/model/FileEntity.java b/src/main/java/org/rostislav/quickdrop/model/FileEntity.java index c11cef3..ab95497 100644 --- a/src/main/java/org/rostislav/quickdrop/model/FileEntity.java +++ b/src/main/java/org/rostislav/quickdrop/model/FileEntity.java @@ -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 { diff --git a/src/main/java/org/rostislav/quickdrop/repository/FileRepository.java b/src/main/java/org/rostislav/quickdrop/repository/FileRepository.java index 053a1fc..bd089d8 100644 --- a/src/main/java/org/rostislav/quickdrop/repository/FileRepository.java +++ b/src/main/java/org/rostislav/quickdrop/repository/FileRepository.java @@ -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 { @Query("SELECT f FROM FileEntity f WHERE f.uuid = :uuid") Optional findByUUID(@Param("uuid") String uuid); diff --git a/src/main/java/org/rostislav/quickdrop/service/ApplicationSettingsService.java b/src/main/java/org/rostislav/quickdrop/service/ApplicationSettingsService.java index 6ab80cd..81842ef 100644 --- a/src/main/java/org/rostislav/quickdrop/service/ApplicationSettingsService.java +++ b/src/main/java/org/rostislav/quickdrop/service/ApplicationSettingsService.java @@ -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(); } diff --git a/src/main/java/org/rostislav/quickdrop/service/FileService.java b/src/main/java/org/rostislav/quickdrop/service/FileService.java index 95c2e6c..48c5791 100644 --- a/src/main/java/org/rostislav/quickdrop/service/FileService.java +++ b/src/main/java/org/rostislav/quickdrop/service/FileService.java @@ -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()); diff --git a/src/main/java/org/rostislav/quickdrop/service/ScheduleService.java b/src/main/java/org/rostislav/quickdrop/service/ScheduleService.java index 14b1d2d..94c9352 100644 --- a/src/main/java/org/rostislav/quickdrop/service/ScheduleService.java +++ b/src/main/java/org/rostislav/quickdrop/service/ScheduleService.java @@ -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); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ade4f5b..3f0f268 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/src/test/java/org/rostislav/quickdrop/FileServiceTests.java b/src/test/java/org/rostislav/quickdrop/FileServiceTests.java index 21fa544..43065da 100644 --- a/src/test/java/org/rostislav/quickdrop/FileServiceTests.java +++ b/src/test/java/org/rostislav/quickdrop/FileServiceTests.java @@ -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(); diff --git a/src/test/java/org/rostislav/quickdrop/TestDataContainer.java b/src/test/java/org/rostislav/quickdrop/TestDataContainer.java index cbdffda..5ed08a2 100644 --- a/src/test/java/org/rostislav/quickdrop/TestDataContainer.java +++ b/src/test/java/org/rostislav/quickdrop/TestDataContainer.java @@ -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() { diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index b1e6fc2..77b3b37 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -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