mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2025-12-30 11:09:59 -06:00
Added the link generation and a file uploaded page. Added a little indicator that the upload has started and alerts if it errors
This commit is contained in:
13
README.md
13
README.md
@@ -1,12 +1,17 @@
|
||||
# QuickDrop
|
||||
|
||||
QuickDrop is a secure, easy-to-use file sharing application that allows users to upload files without an account, generate download links, and manage file availability, all with built-in malware scanning and optional password protection.
|
||||
QuickDrop is a secure, easy-to-use file sharing application that allows users to upload files without an account,
|
||||
generate download links, and manage file availability, all with built-in malware scanning and optional password
|
||||
protection.
|
||||
|
||||
## This project is still under development, and all but the most basic features are still missing.
|
||||
|
||||
**Available Features:**
|
||||
- **File Upload**: Users can upload files without needing to create an account.
|
||||
- **File list**: Users can view a list of uploaded files.
|
||||
- **File Download**: Users can download files from the file list.
|
||||
|
||||
- **File Upload**: Users can upload files without needing to create an account.
|
||||
- **File list**: Users can view a list of uploaded files.
|
||||
- **File Download**: Users can download files from the file list.
|
||||
- **Download Links**: Generate download links for easy sharing.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@@ -15,6 +16,9 @@ public class SecurityConfig {
|
||||
http.authorizeHttpRequests(authorizeRequests ->
|
||||
authorizeRequests.anyRequest().anonymous()
|
||||
);
|
||||
http.csrf(csrf ->
|
||||
csrf.csrfTokenRepository(new CookieCsrfTokenRepository())
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ public class FileRestController {
|
||||
}
|
||||
|
||||
@PostMapping("/upload")
|
||||
public ResponseEntity<FileEntity> saveFile(@RequestParam("file") MultipartFile file, FileUploadRequest fileUploadRequest) {
|
||||
public ResponseEntity<FileEntity> saveFile(@RequestParam("file") MultipartFile file,
|
||||
FileUploadRequest fileUploadRequest) {
|
||||
FileEntity fileEntity = fileService.saveFile(file, fileUploadRequest);
|
||||
if (fileEntity != null) {
|
||||
return ResponseEntity.ok(fileEntity);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.rostislav.quickdrop.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.rostislav.quickdrop.model.FileEntity;
|
||||
import org.rostislav.quickdrop.model.FileUploadRequest;
|
||||
import org.rostislav.quickdrop.service.FileService;
|
||||
@@ -30,16 +31,13 @@ public class FileViewController {
|
||||
public String saveFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("description") String description,
|
||||
@RequestParam(value = "keepIndefinitely", defaultValue = "false") boolean keepIndefinitely,
|
||||
Model model) {
|
||||
Model model, HttpServletRequest request) {
|
||||
FileUploadRequest fileUploadRequest = new FileUploadRequest(description, keepIndefinitely);
|
||||
|
||||
FileEntity fileEntity = fileService.saveFile(file, fileUploadRequest);
|
||||
if (fileEntity != null) {
|
||||
model.addAttribute("message", "File uploaded successfully");
|
||||
} else {
|
||||
model.addAttribute("message", "File upload failed");
|
||||
}
|
||||
|
||||
if (fileEntity != null) {
|
||||
return filePage(fileEntity.uuid, model, request);
|
||||
}
|
||||
return "upload";
|
||||
}
|
||||
|
||||
@@ -50,6 +48,16 @@ public class FileViewController {
|
||||
return "listFiles";
|
||||
}
|
||||
|
||||
@GetMapping("/{UUID}")
|
||||
public String filePage(@PathVariable String UUID, Model model, HttpServletRequest request) {
|
||||
FileEntity file = fileService.getFile(UUID);
|
||||
model.addAttribute("file", file);
|
||||
String downloadLink = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/file/" + file.uuid;
|
||||
model.addAttribute("downloadLink", downloadLink);
|
||||
|
||||
return "fileUploaded";
|
||||
}
|
||||
|
||||
@GetMapping("/download/{id}")
|
||||
public ResponseEntity<Resource> downloadFile(@PathVariable Long id) {
|
||||
return fileService.downloadFile(id);
|
||||
|
||||
@@ -11,7 +11,7 @@ public class FileEntity {
|
||||
public Long id;
|
||||
|
||||
public String name;
|
||||
public String UUID;
|
||||
public String uuid;
|
||||
public String description;
|
||||
public long size;
|
||||
public boolean keepIndefinitely;
|
||||
|
||||
@@ -2,7 +2,12 @@ package org.rostislav.quickdrop.repository;
|
||||
|
||||
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.util.Optional;
|
||||
|
||||
public interface FileRepository extends JpaRepository<FileEntity, Long> {
|
||||
|
||||
@Query("SELECT f FROM FileEntity f WHERE f.uuid = :uuid")
|
||||
public Optional<FileEntity> findByUUID(@Param("uuid") String uuid);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.rostislav.quickdrop.service;
|
||||
import org.rostislav.quickdrop.model.FileEntity;
|
||||
import org.rostislav.quickdrop.model.FileUploadRequest;
|
||||
import org.rostislav.quickdrop.repository.FileRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
@@ -20,6 +22,7 @@ import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class FileService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileService.class);
|
||||
private final FileRepository fileRepository;
|
||||
@Value("${file.save.path}")
|
||||
private String fileSavePath;
|
||||
@@ -34,17 +37,19 @@ public class FileService {
|
||||
try {
|
||||
Files.createFile(path);
|
||||
Files.write(path, file.getBytes());
|
||||
logger.info("File saved: {}", path);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FileEntity fileEntity = new FileEntity();
|
||||
fileEntity.name = file.getOriginalFilename();
|
||||
fileEntity.UUID = uuid;
|
||||
fileEntity.uuid = uuid;
|
||||
fileEntity.description = fileUploadRequest.description;
|
||||
fileEntity.size = file.getSize();
|
||||
fileEntity.keepIndefinitely = fileUploadRequest.keepIndefinitely;
|
||||
|
||||
logger.info("FileEntity saved: {}", fileEntity);
|
||||
return fileRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
@@ -62,7 +67,7 @@ public class FileService {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
Path path = Path.of(fileSavePath, referenceById.get().UUID);
|
||||
Path path = Path.of(fileSavePath, referenceById.get().uuid);
|
||||
try {
|
||||
Resource resource = new UrlResource(path.toUri());
|
||||
return ResponseEntity.ok()
|
||||
@@ -76,4 +81,8 @@ public class FileService {
|
||||
public FileEntity getFile(Long id) {
|
||||
return fileRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
public FileEntity getFile(String uuid) {
|
||||
return fileRepository.findByUUID(uuid).orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ spring.thymeleaf.suffix=.html
|
||||
spring.thymeleaf.cache=false
|
||||
spring.servlet.multipart.max-file-size=1024MB
|
||||
spring.servlet.multipart.max-request-size=1024MB
|
||||
server.tomcat.connection-timeout=60000
|
||||
file.save.path=/files
|
||||
31
src/main/resources/templates/fileUploaded.html
Normal file
31
src/main/resources/templates/fileUploaded.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>File has been uploaded</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>File has been uploaded</h1>
|
||||
<div>
|
||||
<h3>File Name</h3>
|
||||
<p th:text="${file.name}"></p>
|
||||
<h3>File description</h3>
|
||||
<p th:text="${file.description}"></p>
|
||||
<h3>Uploaded At</h3>
|
||||
<p th:text="${file.uploadDate}"></p>
|
||||
<h3>Keep Indefinitely</h3>
|
||||
<p th:text="${file.keepIndefinitely} ? 'Yes' : 'No'"></p>
|
||||
<h3>File Size</h3>
|
||||
<p th:text="${file.size}"></p>
|
||||
<h3>Link: </h3>
|
||||
<p th:text="${downloadLink}"></p>
|
||||
<h3>Download</h3>
|
||||
<p>
|
||||
<a th:href="@{/file/download/{id}(id=${file.id})}">Download</a>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<a href="/file/list">View all files</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,7 +23,7 @@
|
||||
<td th:text="${file.uploadDate}"></td>
|
||||
<td th:text="${file.keepIndefinitely} ? 'Yes' : 'No'"></td>
|
||||
<td>
|
||||
<a th:href="@{/file/download/{id}(id=${file.id})}">Download</a>
|
||||
<a th:href="@{/file/{UUID}(UUID=${file.UUID})}">Go to file page</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload a File</h1>
|
||||
<form enctype="multipart/form-data" method="post" th:action="@{/file/upload}">
|
||||
<form enctype="multipart/form-data" id="uploadForm" method="post" th:action="@{/file/upload}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<div>
|
||||
<label for="file">Select a file:</label>
|
||||
@@ -24,5 +24,55 @@
|
||||
<button type="submit">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="uploadIndicator" style="display: none;">Upload started...</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
document.getElementById("uploadForm").addEventListener("submit", function (event) {
|
||||
event.preventDefault(); // Prevent the form from submitting synchronously
|
||||
|
||||
// Display the indicator
|
||||
document.getElementById("uploadIndicator").style.display = "block";
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData(event.target);
|
||||
|
||||
// Create an AJAX request
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/file/upload", true); // Updated to use the new API endpoint
|
||||
|
||||
// Add CSRF token if required
|
||||
const csrfTokenElement = document.querySelector('input[name="_csrf"]');
|
||||
if (csrfTokenElement) {
|
||||
xhr.setRequestHeader("X-CSRF-TOKEN", csrfTokenElement.value);
|
||||
}
|
||||
|
||||
// Handle response
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.uuid) {
|
||||
// Redirect to the view page using the UUID from the JSON response
|
||||
window.location.href = "/file/" + response.uuid;
|
||||
} else {
|
||||
alert("Unexpected response. Please try again.");
|
||||
document.getElementById("uploadIndicator").style.display = "none";
|
||||
}
|
||||
} else {
|
||||
alert("Upload failed. Please try again.");
|
||||
document.getElementById("uploadIndicator").style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network or server errors
|
||||
xhr.onerror = function () {
|
||||
alert("An error occurred during the upload. Please try again.");
|
||||
document.getElementById("uploadIndicator").style.display = "none";
|
||||
};
|
||||
|
||||
// Send the form data
|
||||
xhr.send(formData);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user