diff --git a/frontend/src/app/download/download.component.ts b/frontend/src/app/download/download.component.ts index 5d66e8f..1cd06e8 100644 --- a/frontend/src/app/download/download.component.ts +++ b/frontend/src/app/download/download.component.ts @@ -24,7 +24,7 @@ export class DownloadComponent { @ViewChild('download_not_possible') download_not_possible: ElementRef | undefined; @ViewChild('file_not_found_modal') file_not_found_modal: ElementRef | undefined; - inputFileId: string = "2402183"; + inputFileId: string = ""; fileId: string = ""; filePassword: string = ""; fileName: string = ""; diff --git a/frontend/src/service/legalService.ts b/frontend/src/service/legalService.ts index 21f428f..5990b04 100644 --- a/frontend/src/service/legalService.ts +++ b/frontend/src/service/legalService.ts @@ -4,6 +4,10 @@ import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root', }) +/** + * Service to open the privacy policy and terms of use modals + * The modals are controlled by the navbar component + */ export class LegalService { private openModalSource = new Subject(); diff --git a/src/main/java/de/w665/sharepulse/config/MvcConfig.java b/src/main/java/de/w665/sharepulse/config/MvcConfig.java index c1ef35c..ecfc941 100644 --- a/src/main/java/de/w665/sharepulse/config/MvcConfig.java +++ b/src/main/java/de/w665/sharepulse/config/MvcConfig.java @@ -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 { diff --git a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java index 91390d9..3e44e4e 100644 --- a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java +++ b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java @@ -35,22 +35,6 @@ public class RethinkDBService { @PostConstruct public void initialize() { - //rethinkdb check if database exists - - /*Result result = r.dbList().run(connection); - List databases = new ArrayList<>(); - - Object db = result.first(); - ArrayList dbList = (ArrayList) 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(); @@ -79,6 +63,14 @@ public class RethinkDBService { } catch (ReqlOpFailedError e) { log.debug("Table 'id_store' already exists. No action needed."); } + + // 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. No action needed."); + } log.info("Database ready for operation!"); } diff --git a/src/main/java/de/w665/sharepulse/db/repo/ExpiredFileUploadRepository.java b/src/main/java/de/w665/sharepulse/db/repo/ExpiredFileUploadRepository.java new file mode 100644 index 0000000..dae75ce --- /dev/null +++ b/src/main/java/de/w665/sharepulse/db/repo/ExpiredFileUploadRepository.java @@ -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>(){}.getType(); + Map 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); + } +} diff --git a/src/main/java/de/w665/sharepulse/db/repo/FileUploadRepository.java b/src/main/java/de/w665/sharepulse/db/repo/FileUploadRepository.java index 24278ae..f894648 100644 --- a/src/main/java/de/w665/sharepulse/db/repo/FileUploadRepository.java +++ b/src/main/java/de/w665/sharepulse/db/repo/FileUploadRepository.java @@ -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>(){}.getType(); Map 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); } @@ -72,4 +68,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 getAllExpiredFileUploads() { + + long timestamp = getOneMinuteAgoTimestamp(); + + List 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) + } } diff --git a/src/main/java/de/w665/sharepulse/model/FileUpload.java b/src/main/java/de/w665/sharepulse/model/FileUpload.java index b6ef691..6afde28 100644 --- a/src/main/java/de/w665/sharepulse/model/FileUpload.java +++ b/src/main/java/de/w665/sharepulse/model/FileUpload.java @@ -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; diff --git a/src/main/java/de/w665/sharepulse/service/FileCleanupService.java b/src/main/java/de/w665/sharepulse/service/FileCleanupService.java new file mode 100644 index 0000000..e3a3b6d --- /dev/null +++ b/src/main/java/de/w665/sharepulse/service/FileCleanupService.java @@ -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 expFileUploads = fileUploadRepository.getAllExpiredFileUploads(); + for (FileUpload fileUpload : expFileUploads) { + fileService.deleteFile(fileUpload); + expiredFileUploadRepository.insertExpiredFileUpload(fileUpload); + log.debug("Moved file " + fileUpload.getFileId() + " to old_file_uploads table."); + } + } +} diff --git a/src/main/java/de/w665/sharepulse/service/FileService.java b/src/main/java/de/w665/sharepulse/service/FileService.java index 9b8fea0..fb9b50a 100644 --- a/src/main/java/de/w665/sharepulse/service/FileService.java +++ b/src/main/java/de/w665/sharepulse/service/FileService.java @@ -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; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a2b8bc0..5e0484c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ 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 # Static path spring.web.resources.static-locations=classpath:/static/browser/