added a checkbox to hide the file from the file list view

This commit is contained in:
Rostislav Raykov
2024-12-02 14:41:36 +02:00
parent bde49cfce1
commit 9cfa3df03e
8 changed files with 158 additions and 53 deletions

View File

@@ -44,7 +44,7 @@ public class FileViewController {
@GetMapping("/list")
public String listFiles(Model model) {
List<FileEntity> files = fileService.getFiles();
List<FileEntity> files = fileService.getNotHiddenFiles();
model.addAttribute("files", files);
return "listFiles";
}
@@ -129,32 +129,54 @@ public class FileViewController {
@GetMapping("/search")
public String searchFiles(String query, Model model) {
List<FileEntity> files = fileService.searchFiles(query);
List<FileEntity> files = fileService.searchNotHiddenFiles(query);
model.addAttribute("files", files);
return "listFiles";
}
@PostMapping("/keep-indefinitely/{id}")
public String updateKeepIndefinitely(@PathVariable Long id, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely, HttpServletRequest request, Model model) {
return handlePasswordValidationAndRedirect(id, request, model, () -> fileService.updateKeepIndefinitely(id, keepIndefinitely));
}
@PostMapping("/toggle-hidden/{id}")
public String toggleHidden(@PathVariable Long id, HttpServletRequest request, Model model) {
return handlePasswordValidationAndRedirect(id, request, model, () -> fileService.toggleHidden(id));
}
private String handlePasswordValidationAndRedirect(Long fileId, HttpServletRequest request, Model model, Runnable action) {
String referer = request.getHeader("Referer");
// Check for admin password
if (!applicationSettingsService.checkForAdminPassword(request)) {
// Check for file password
String filePassword = (String) request.getSession().getAttribute("password");
if (filePassword != null) {
FileEntity fileEntity = fileService.getFile(id);
// Check if file password is correct
if (fileEntity.passwordHash != null && !fileService.checkPassword(fileEntity.uuid, filePassword)) {
model.addAttribute("uuid", fileEntity.uuid);
return "file-password";
}
// Redirect to file page
fileService.updateKeepIndefinitely(id, keepIndefinitely);
return "redirect:/file/" + fileEntity.uuid;
}
return "redirect:/admin/password";
if (applicationSettingsService.checkForAdminPassword(request)) {
action.run();
return "redirect:" + referer;
}
fileService.updateKeepIndefinitely(id, keepIndefinitely);
return "redirect:/admin/dashboard";
// Check for file password in the session
String filePassword = (String) request.getSession().getAttribute("password");
if (filePassword != null) {
FileEntity fileEntity = fileService.getFile(fileId);
// Validate file password if the file is password-protected
if (fileEntity.passwordHash != null && !fileService.checkPassword(fileEntity.uuid, filePassword)) {
model.addAttribute("uuid", fileEntity.uuid);
return "file-password"; // Redirect to file password page if the password is incorrect
}
action.run();
return "redirect:" + referer;
}
// No valid password found, determine the redirect destination
if (referer != null && referer.contains("/admin/dashboard")) {
return "redirect:/admin/password"; // Redirect to admin password page
} else {
// Get the file for adding the UUID to the model for the file password page
FileEntity fileEntity = fileService.getFile(fileId);
model.addAttribute("uuid", fileEntity.uuid);
return "file-password";
}
}
}

View File

@@ -17,6 +17,8 @@ public class FileEntity {
public boolean keepIndefinitely;
public LocalDate uploadDate;
public String passwordHash;
@Column(columnDefinition = "boolean default false")
public boolean hidden;
@PrePersist
public void prePersist() {

View File

@@ -13,6 +13,7 @@ public class FileEntityView {
public boolean keepIndefinitely;
public LocalDate uploadDate;
public long totalDownloads;
public boolean hidden;
public FileEntityView() {
}
@@ -26,5 +27,6 @@ public class FileEntityView {
this.keepIndefinitely = fileEntity.keepIndefinitely;
this.uploadDate = fileEntity.uploadDate;
this.totalDownloads = totalDownloads;
this.hidden = fileEntity.hidden;
}
}

View File

@@ -16,9 +16,15 @@ public interface FileRepository extends JpaRepository<FileEntity, Long> {
@Query("SELECT f FROM FileEntity f WHERE f.keepIndefinitely = false AND f.uploadDate < :thresholdDate")
List<FileEntity> getFilesForDeletion(@Param("thresholdDate") LocalDate thresholdDate);
@Query("SELECT f FROM FileEntity f WHERE f.name LIKE %:searchString% OR f.description LIKE %:searchString% OR f.uuid LIKE %:searchString%")
@Query("SELECT f FROM FileEntity f WHERE (LOWER(f.name) LIKE LOWER(CONCAT('%', :searchString, '%')) OR LOWER(f.description) LIKE LOWER(CONCAT('%', :searchString, '%')) OR LOWER(f.uuid) LIKE LOWER(CONCAT('%', :searchString, '%')))")
List<FileEntity> searchFiles(@Param("searchString") String searchString);
@Query("SELECT f FROM FileEntity f WHERE f.hidden = false")
List<FileEntity> findAllNotHiddenFiles();
@Query("SELECT SUM(f.size) FROM FileEntity f")
Long totalFileSizeForAllFiles();
@Query("SELECT f FROM FileEntity f WHERE f.hidden = false AND (LOWER(f.name) LIKE LOWER(CONCAT('%', :searchString, '%')) OR LOWER(f.description) LIKE LOWER(CONCAT('%', :searchString, '%')) OR LOWER(f.uuid) LIKE LOWER(CONCAT('%', :searchString, '%')))")
List<FileEntity> searchNotHiddenFiles(@Param("searchString") String query);
}

View File

@@ -267,6 +267,10 @@ public class FileService {
return fileRepository.searchFiles(query);
}
public List<FileEntity> searchNotHiddenFiles(String query) {
return fileRepository.searchNotHiddenFiles(query);
}
public long calculateTotalSpaceUsed() {
return nullToZero(fileRepository.totalFileSizeForAllFiles());
}
@@ -286,4 +290,20 @@ public class FileService {
logger.info("File keepIndefinitely updated: {}", fileEntity);
fileRepository.save(fileEntity);
}
public void toggleHidden(Long id) {
Optional<FileEntity> referenceById = fileRepository.findById(id);
if (referenceById.isEmpty()) {
return;
}
FileEntity fileEntity = referenceById.get();
fileEntity.hidden = !fileEntity.hidden;
logger.info("File hidden updated: {}", fileEntity);
fileRepository.save(fileEntity);
}
public List<FileEntity> getNotHiddenFiles() {
return fileRepository.findAllNotHiddenFiles();
}
}

View File

@@ -117,4 +117,15 @@ function parseSize(size) {
const value = parseFloat(valueMatch[0]);
return value * (units[unit] || 1);
}
function updateHiddenState(event, checkbox) {
event.preventDefault();
const hiddenField = checkbox.form.querySelector('input[name="hidden"][type="hidden"]');
if (hiddenField) {
hiddenField.value = checkbox.checked;
}
console.log('Submitting hidden state form...');
checkbox.form.submit();
}

View File

@@ -63,53 +63,64 @@
<table class="table table-striped align-middle">
<thead>
<tr>
<th class="px-4">Name</th>
<th class="px-4">Upload Date</th>
<th class="px-4">Size</th>
<th class="px-4">Downloads</th>
<th class="text-end px-4">Actions</th>
<th style="width: 20%;">Name</th>
<th style="width: 15%;">Upload Date</th>
<th style="width: 10%;">Size</th>
<th style="width: 10%;">Downloads</th>
<th style="width: 10%; text-align: center;">Keep Indefinitely</th>
<th style="width: 10%; text-align: center;">Hidden</th>
<th style="width: 25%; text-align: right;">Actions</th>
</tr>
</thead>
<tbody>
<tr class="align-middle" th:each="file : ${files}">
<td class="px-4" th:text="${file.name}"></td>
<td class="px-4" th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></td>
<td class="px-4" th:text="${file.size}"></td>
<td class="px-4" th:text="${file.totalDownloads}"></td>
<td class="px-4 text-center">
<!-- Keep Indefinitely Checkbox -->
<td th:text="${file.name}"></td>
<td th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></td>
<td th:text="${file.size}"></td>
<td th:text="${file.totalDownloads}"></td>
<!-- Keep Indefinitely Checkbox -->
<td class="text-center">
<form class="d-inline" method="post" th:action="@{/file/keep-indefinitely/{id}(id=${file.id})}">
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
<input name="keepIndefinitely" type="hidden" value="false">
<div class="d-flex flex-column align-items-center">
<span>Keep Indefinitely</span>
<div class="form-check form-switch mt-2">
<input class="form-check-input"
name="keepIndefinitely"
onchange="updateCheckboxState(event, this)"
th:checked="${file.keepIndefinitely}"
type="checkbox"
value="true">
</div>
<div class="form-check form-switch">
<input class="form-check-input"
name="keepIndefinitely"
onchange="updateCheckboxState(event, this)"
th:checked="${file.keepIndefinitely}"
type="checkbox"
value="true">
</div>
</form>
</td>
<td class="text-end px-4">
<!-- View File Button -->
<a class="btn btn-sm btn-info me-2" th:href="@{/file/{uuid}(uuid=${file.uuid})}">View File</a>
<!-- View Download History Button -->
<a class="btn btn-sm btn-warning me-2" th:href="@{/file/history/{id}(id=${file.id})}">View
History</a>
<!-- Hidden Checkbox -->
<td class="text-center">
<form class="d-inline" method="post" th:action="@{/file/toggle-hidden/{id}(id=${file.id})}">
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
<input name="hidden" type="hidden" value="false">
<div class="form-check form-switch">
<input class="form-check-input"
name="hidden"
onchange="updateHiddenState(event, this)"
th:checked="${file.hidden}"
type="checkbox"
value="true">
</div>
</form>
</td>
<!-- Download Button -->
<a class="btn btn-sm btn-success me-2"
<!-- Actions -->
<td class="text-end">
<a class="btn btn-sm btn-info me-1" th:href="@{/file/{uuid}(uuid=${file.uuid})}">View File</a>
<a class="btn btn-sm btn-warning me-1"
th:href="@{/file/history/{id}(id=${file.id})}">History</a>
<a class="btn btn-sm btn-success me-1"
th:href="@{/file/download/{id}(id=${file.id})}">Download</a>
<!-- Delete Button with POST -->
<form class="d-inline" method="post" th:action="@{/file/delete/{id}(id=${file.id})}">
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
<button class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
@@ -132,6 +143,20 @@
}
</script>
<script>
function updateHiddenState(event, checkbox) {
event.preventDefault();
const hiddenField = checkbox.form.querySelector('input[name="hidden"][type="hidden"]');
if (hiddenField) {
hiddenField.value = checkbox.checked;
}
console.log('Submitting hidden state form...');
checkbox.form.submit();
}
</script>
<!-- Bootstrap Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>

View File

@@ -96,8 +96,25 @@
</div>
</form>
</div>
<div class="d-flex justify-content-between align-items-center" th:if="${file.passwordHash != null}">
<h5 class="card-title">Hide File From List:</h5>
<form class="d-inline" method="post" th:action="@{/file/toggle-hidden/{id}(id=${file.id})}">
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
<input name="hidden" type="hidden" value="false">
<div class="form-check form-switch">
<input
class="form-check-input"
id="hidden"
name="hidden"
onchange="updateCheckboxState(event, this)"
th:checked="${file.hidden}"
type="checkbox"
value="true"/>
</div>
</form>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex justify-content-between align-items-center pt-3">
<h5 class="card-title">
File
Size:</h5>