mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2026-01-04 05:29:53 -06:00
added a checkbox to hide the file from the file list view
This commit is contained in:
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user