Merge branch 'main' of https://git.walzen665.de/Walzen665/fileshare-service
This commit is contained in:
@ -1,9 +1,11 @@
|
||||
package de.w665.sharepulse.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@EnableScheduling
|
||||
@Configuration
|
||||
public class MvcConfig implements WebMvcConfigurer {
|
||||
|
||||
|
@ -35,22 +35,6 @@ public class RethinkDBService {
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
|
||||
//rethinkdb check if database exists
|
||||
|
||||
/*Result<Object> result = r.dbList().run(connection);
|
||||
List<String> databases = new ArrayList<>();
|
||||
|
||||
Object db = result.first();
|
||||
ArrayList<String> dbList = (ArrayList<String>) db;
|
||||
databases.addAll(dbList);
|
||||
|
||||
if (!databases.contains("sharepulse")) {
|
||||
r.dbCreate("sharepulse").run(connection);
|
||||
log.info("Database 'sharepulse' created successfully.");
|
||||
} else {
|
||||
log.info("Database 'sharepulse' already exists. No action needed.");
|
||||
}*/
|
||||
|
||||
// rethinkdb check if database exists
|
||||
try {
|
||||
r.dbCreate(config.getDatabase()).run(connection).stream();
|
||||
@ -77,7 +61,25 @@ public class RethinkDBService {
|
||||
r.db(config.getDatabase()).tableCreate("id_store").run(connection).stream();
|
||||
log.debug("Table 'id_store' created successfully.");
|
||||
} catch (ReqlOpFailedError e) {
|
||||
log.debug("Table 'id_store' already exists. No action needed.");
|
||||
log.debug("Table 'id_store' already exists.");
|
||||
if(autoResetOnStartup) {
|
||||
log.debug("Clearing content...");
|
||||
r.db(config.getDatabase()).table("id_store").delete().run(connection);
|
||||
log.debug("Table 'id_store' cleared successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
// rethinkdb check if table expired_file_uploads exists
|
||||
try {
|
||||
r.db(config.getDatabase()).tableCreate("expired_file_uploads").run(connection).stream();
|
||||
log.debug("Table 'expired_file_uploads' created successfully.");
|
||||
} catch (ReqlOpFailedError e) {
|
||||
log.debug("Table 'expired_file_uploads' already exists.");
|
||||
if(autoResetOnStartup) {
|
||||
log.debug("Clearing content...");
|
||||
r.db(config.getDatabase()).table("expired_file_uploads").delete().run(connection);
|
||||
log.debug("Table 'expired_file_uploads' cleared successfully.");
|
||||
}
|
||||
}
|
||||
log.info("Database ready for operation!");
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package de.w665.sharepulse.db.repo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
public class ExpiredFileUploadRepository {
|
||||
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public ExpiredFileUploadRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertExpiredFileUpload(FileUpload fileUpload) {
|
||||
String json = gson.toJson(fileUpload);
|
||||
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> map = gson.fromJson(json, type);
|
||||
|
||||
long uploadDateTimestamp = fileUpload.getUploadDate().getTime() / 1000;
|
||||
map.put("uploadDate", uploadDateTimestamp);
|
||||
|
||||
r.db("sharepulse").table("expired_file_uploads").insert(map).run(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package de.w665.sharepulse.db.repo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import com.rethinkdb.net.Result;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class FileIdRepository {
|
||||
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public FileIdRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertFileId(String fileId) {
|
||||
r.db("sharepulse").table("id_store").insert(r.hashMap("fileId", fileId)).run(connection);
|
||||
}
|
||||
|
||||
public boolean fileIdExists(String fileId) {
|
||||
Result<Object> result = r.db("sharepulse").table("id_store")
|
||||
.filter(r.hashMap("fileId", fileId))
|
||||
.run(connection);
|
||||
|
||||
return result.hasNext();
|
||||
}
|
||||
}
|
@ -6,35 +6,28 @@ import com.google.gson.reflect.TypeToken;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import de.w665.sharepulse.db.RethinkDBService;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class FileUploadRepository {
|
||||
|
||||
private final RethinkDBService rethinkDBService;
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public FileUploadRepository(RethinkDBService rethinkDBService, RethinkDBConnector connector) {
|
||||
this.rethinkDBService = rethinkDBService;
|
||||
public FileUploadRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
|
||||
this.gson = new GsonBuilder()
|
||||
.setDateFormat("dd-MM-yyyy HH:mm:ss") // date field formatting
|
||||
.create();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertFileUpload(FileUpload fileUpload) {
|
||||
@ -43,6 +36,9 @@ public class FileUploadRepository {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> map = gson.fromJson(json, type);
|
||||
|
||||
long uploadDateTimestamp = fileUpload.getUploadDate().getTime() / 1000;
|
||||
map.put("uploadDate", uploadDateTimestamp);
|
||||
|
||||
r.db("sharepulse").table("file_uploads").insert(map).run(connection);
|
||||
}
|
||||
|
||||
@ -75,4 +71,34 @@ public class FileUploadRepository {
|
||||
.update(map)
|
||||
.run(connection);
|
||||
}
|
||||
|
||||
public void deleteFileUploadByFileId(String fileId) {
|
||||
r.db("sharepulse").table("file_uploads")
|
||||
.filter(r.hashMap("fileId", fileId))
|
||||
.delete()
|
||||
.run(connection);
|
||||
}
|
||||
|
||||
public List<FileUpload> getAllExpiredFileUploads() {
|
||||
|
||||
long timestamp = getOneMinuteAgoTimestamp();
|
||||
|
||||
List<FileUpload> olderFiles = r.db("sharepulse").table("file_uploads")
|
||||
.filter(row -> row.g("uploadDate").lt(timestamp))
|
||||
.run(connection, FileUpload.class)
|
||||
.toList();
|
||||
return olderFiles;
|
||||
}
|
||||
|
||||
private long get24HoursAgoTimestamp() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.HOUR, -24);
|
||||
return calendar.getTimeInMillis() / 1000;
|
||||
}
|
||||
|
||||
private long getOneMinuteAgoTimestamp() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MINUTE, -1); // Subtract 1 minute
|
||||
return calendar.getTimeInMillis() / 1000; // Convert milliseconds to seconds (Unix timestamp)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public class FileUpload {
|
||||
private String fileName;
|
||||
private long fileSize;
|
||||
private boolean singleDownload;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "CET")
|
||||
@JsonFormat(timezone = "ETC")
|
||||
private Date uploadDate;
|
||||
private String uploadedByIpAddress;
|
||||
private long downloadCount;
|
||||
|
@ -82,7 +82,7 @@ public class Download extends ApiRestController {
|
||||
Optional<FileUpload> optionalFileUpload = fileService.getFileUploadByFileId(fileId);
|
||||
FileUpload fileUpload = optionalFileUpload.orElse(null);
|
||||
if(optionalFileUpload.isEmpty()) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
boolean downloadable = !fileUpload.isSingleDownload() || fileUpload.getDownloadCount() == 0;
|
||||
|
@ -0,0 +1,36 @@
|
||||
package de.w665.sharepulse.service;
|
||||
|
||||
import de.w665.sharepulse.db.repo.FileUploadRepository;
|
||||
import de.w665.sharepulse.db.repo.ExpiredFileUploadRepository;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileCleanupService {
|
||||
|
||||
private final FileUploadRepository fileUploadRepository;
|
||||
private final ExpiredFileUploadRepository expiredFileUploadRepository;
|
||||
private final FileService fileService;
|
||||
|
||||
public FileCleanupService(FileUploadRepository fileUploadRepository, ExpiredFileUploadRepository expiredFileUploadRepository, FileService fileService) {
|
||||
this.fileUploadRepository = fileUploadRepository;
|
||||
this.expiredFileUploadRepository = expiredFileUploadRepository;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 * * * *")
|
||||
public void cleanup() {
|
||||
log.debug("Running cleanup...");
|
||||
List<FileUpload> expFileUploads = fileUploadRepository.getAllExpiredFileUploads();
|
||||
for (FileUpload fileUpload : expFileUploads) {
|
||||
fileService.deleteFile(fileUpload);
|
||||
expiredFileUploadRepository.insertExpiredFileUpload(fileUpload);
|
||||
log.debug("Moved file " + fileUpload.getFileId() + " to old_file_uploads table.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +1,51 @@
|
||||
package de.w665.sharepulse.service;
|
||||
|
||||
import de.w665.sharepulse.db.repo.FileIdRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class FileIdService {
|
||||
|
||||
private static int dailyCounter = 0;
|
||||
private static String lastDate = "";
|
||||
@Value("${sharepulse.fileid.length}")
|
||||
private int fileIdLength;
|
||||
|
||||
// Not safe to use
|
||||
public static synchronized String generateId() {
|
||||
String today = new SimpleDateFormat("yyMMdd").format(new Date());
|
||||
@Value("${sharepulse.fileid.charset}")
|
||||
private String CHARSET;
|
||||
|
||||
if (!today.equals(lastDate)) {
|
||||
dailyCounter = 0;
|
||||
lastDate = today;
|
||||
private final FileIdRepository fileIdRepository;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
@Autowired
|
||||
public FileIdService(FileIdRepository fileIdRepository) {
|
||||
this.fileIdRepository = fileIdRepository;
|
||||
}
|
||||
|
||||
private String generateId() {
|
||||
String uniqueId;
|
||||
do {
|
||||
uniqueId = generateRandomCode(fileIdLength);
|
||||
} while (fileIdRepository.fileIdExists(uniqueId));
|
||||
fileIdRepository.insertFileId(uniqueId);
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
private String generateRandomCode(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = RANDOM.nextInt(CHARSET.length());
|
||||
sb.append(CHARSET.charAt(index));
|
||||
}
|
||||
|
||||
String counterEncoded = Integer.toString(++dailyCounter, 36).toUpperCase();
|
||||
|
||||
return today + counterEncoded;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String generateNewUniqueId() {
|
||||
return generateId();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,17 +5,19 @@ import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileSecurityService {
|
||||
|
||||
@Value("${sharepulse.filepassword-length}")
|
||||
@Value("${sharepulse.filepassword.length}")
|
||||
private int passwordLength;
|
||||
@Value("${sharepulse.filepassword-charset}")
|
||||
@Value("${sharepulse.filepassword.charset}")
|
||||
private String passwordCharset;
|
||||
private final Random random = new Random();
|
||||
private final Random random = new SecureRandom();
|
||||
|
||||
public boolean verifyDownloadPermission(FileUpload file, String password) throws NoDownloadPermissionException {
|
||||
|
||||
|
@ -96,7 +96,6 @@ public class FileService {
|
||||
fileUploadRepository.insertFileUpload(fileUpload);
|
||||
|
||||
|
||||
// TODO: rename file to fileID
|
||||
Path path = Paths.get(getTempDirPath() + File.separator + fileId);
|
||||
try {
|
||||
Files.write(path, file.getBytes());
|
||||
@ -122,6 +121,16 @@ public class FileService {
|
||||
return fileUpload.getDownloadCount();
|
||||
}
|
||||
|
||||
public void deleteFile(FileUpload fileUpload) {
|
||||
fileUploadRepository.deleteFileUploadByFileId(fileUpload.getFileId());
|
||||
Path path = Paths.get(getTempDirPath() + File.separator + fileUpload.getFileId());
|
||||
try {
|
||||
Files.delete(path);
|
||||
log.debug("File " + fileUpload.getFileId() + " deleted from disk.");
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting file " + fileUpload.getFileId() + " from disk: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getTempDirPath() {
|
||||
return System.getProperty("user.dir") + tempDirPath;
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Application config
|
||||
sharepulse.temp-filestore-path=/temp-filestore
|
||||
sharepulse.filepassword-length=6
|
||||
sharepulse.filepassword-charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
sharepulse.auto-reset-on-startup=false
|
||||
sharepulse.auto-reset-on-startup=true
|
||||
sharepulse.fileid.length=6
|
||||
sharepulse.fileid.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
sharepulse.filepassword.length=6
|
||||
sharepulse.filepassword.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
|
||||
# Static path
|
||||
spring.web.resources.static-locations=classpath:/static/browser/
|
||||
|
Reference in New Issue
Block a user