From d9e621165d8108682a18bcaecb81a7b96bc02997 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 18 Feb 2024 14:38:34 +0100 Subject: [PATCH 1/5] Added automatic download mechanism - Added tooltips to options - Updated privacy policy --- .../src/app/download/download.component.ts | 22 +++++++++++++++-- frontend/src/app/home/home.component.html | 24 +++++++++---------- frontend/src/app/upload/upload.component.html | 11 +++++---- frontend/src/app/upload/upload.component.ts | 13 ++++++---- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/download/download.component.ts b/frontend/src/app/download/download.component.ts index 8d889a5..5d66e8f 100644 --- a/frontend/src/app/download/download.component.ts +++ b/frontend/src/app/download/download.component.ts @@ -4,7 +4,7 @@ import {DevelopmentStore} from "../../store/DevelopmentStore"; import {FormsModule} from "@angular/forms"; import {DecimalPipe, NgClass, NgIf} from "@angular/common"; import funfacts from "../../assets/funfacts"; -import {RouterLink} from "@angular/router"; +import {ActivatedRoute, RouterLink} from "@angular/router"; @Component({ selector: 'app-download', @@ -40,8 +40,21 @@ export class DownloadComponent { funfact: string = ""; - constructor(private developmentStore: DevelopmentStore) { + constructor(private developmentStore: DevelopmentStore, private activatedRoute: ActivatedRoute) { this.funfact = funfacts[Math.floor(Math.random() * funfacts.length)]; + + this.activatedRoute.queryParams.subscribe(params => { + const fileId = params['fileId']; + const password = params['password']; + if(password) { + this.filePassword = password; + } + if(fileId) { + this.inputFileId = fileId; + this.requestDownload(); + } + }); + this.speedTest(); } @@ -70,6 +83,11 @@ export class DownloadComponent { return; } else if(this.downloadInfo?.passwordProtected) { + if(this.filePassword) { + console.log("Requesting download with password"); + this.downloadFile(); + return; + } this.waitingForPassword = true; console.log("Password protected"); } diff --git a/frontend/src/app/home/home.component.html b/frontend/src/app/home/home.component.html index 2bdf784..4c30bf4 100644 --- a/frontend/src/app/home/home.component.html +++ b/frontend/src/app/home/home.component.html @@ -38,13 +38,13 @@
  1. Uploaded files are stored on a secure server.
  2. Files are stored for a maximum of 24 hours.
  3. -
  4. Sharepulse tracks your IP address when uploading files.
  5. -
  6. Sharepulse may track your IP address when downloading files.
  7. -
  8. Sharepulse does not track you when accessing the website.
  9. -
  10. Personal information collected by Sharepulse is used solely for data protection and is not processed.
  11. -
  12. Sharepulse will not sell, rent, or share personal information with third parties without user consent, except as required by law.
  13. -
  14. Users have the right to request the deletion of their data from Sharepulse's servers at any time.
  15. -
  16. Sharepulse does not store any cookies on your device.
  17. +
  18. SharePulse tracks your IP address when uploading files. This serves as a method of identification, allowing you to check the status of your upload.
  19. +
  20. SharePulse may track your IP address when downloading files.
  21. +
  22. SharePulse does not track you when accessing the website.
  23. +
  24. Personal information collected by SharePulse is used solely for data protection and is not processed.
  25. +
  26. SharePulse will not sell, rent, or share personal information with third parties without user consent, except as required by law.
  27. +
  28. Users have the right to request the deletion of their data from SharePulse's servers at any time.
  29. +
  30. SharePulse does not store any cookies on your device.
  31. Operators do not view or manipulate uploaded files.
  32. Users are encouraged to review the privacy policy periodically for any changes or updates.
@@ -69,12 +69,12 @@
  1. Users must not upload files that contain malware, illegal content, or violate copyright laws.
  2. -
  3. Sharepulse is not responsible for the content of the files shared through its service.
  4. -
  5. By using Sharepulse, users agree not to attempt to breach the security of the service.
  6. -
  7. Sharepulse reserves the right to remove any files or suspend users that violate these terms without notice.
  8. +
  9. SharePulse is not responsible for the content of the files shared through its service.
  10. +
  11. By using SharePulse, users agree not to attempt to breach the security of the service.
  12. +
  13. SharePulse reserves the right to remove any files or suspend users that violate these terms without notice.
  14. The service is provided "as is" without warranty of any kind, express or implied.
  15. -
  16. Users agree to indemnify and hold harmless Sharepulse from any claims resulting from the use of the service.
  17. -
  18. Sharepulse reserves the right to modify these terms at any time. Continued use of the service after such changes constitutes acceptance of the new terms.
  19. +
  20. Users agree to indemnify and hold harmless SharePulse from any claims resulting from the use of the service.
  21. +
  22. SharePulse reserves the right to modify these terms at any time. Continued use of the service after such changes constitutes acceptance of the new terms.
diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index a862c5b..482a7d7 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -28,17 +28,18 @@
-
- - +
+ +
- +
- +
diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index 8251840..783101a 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -115,14 +115,19 @@ export class UploadComponent { buildFileUrls(fileDetails: FileDetails) { const baseUrl = this.developmentStore.getBaseUrl(); const fileId = fileDetails.fileId; - const downloadUrl = `${baseUrl}download?fileId=${fileId}`; - const deleteUrl = `${baseUrl}api/v1/deletefile?fileId=${fileId}`; - const statusUrl = `${baseUrl}status?fileId=${fileId}`; + let passwordUrlPart = ''; + if(fileDetails.passwordProtected) { + passwordUrlPart = `&password=${fileDetails.password}`; + } + const downloadUrl = `${baseUrl}download?fileId=${fileId}${passwordUrlPart}`; + const deleteUrl = `${baseUrl}api/v1/deletefile?fileId=${fileId}${passwordUrlPart}`; + const statusUrl = `${baseUrl}status?fileId=${fileId}${passwordUrlPart}`; return { downloadUrl, statusUrl, deleteUrl, - }; } + }; + } buildFormDataObject(): FormData { const formData = new FormData(); From 7d253675c833e8b566c18a8c3f162a8300fa9a4c Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 18 Feb 2024 16:06:54 +0100 Subject: [PATCH 2/5] Made legal modals global - Redesigned credits page --- .../src/app/credits/credits.component.html | 59 +++++++++++------ frontend/src/app/credits/credits.component.ts | 16 ++++- frontend/src/app/home/home.component.html | 65 +------------------ frontend/src/app/home/home.component.ts | 16 ++--- frontend/src/app/navbar/navbar.component.html | 62 ++++++++++++++++++ frontend/src/app/navbar/navbar.component.ts | 35 ++++++++-- frontend/src/service/legalService.ts | 19 ++++++ 7 files changed, 175 insertions(+), 97 deletions(-) create mode 100644 frontend/src/service/legalService.ts diff --git a/frontend/src/app/credits/credits.component.html b/frontend/src/app/credits/credits.component.html index 11be4c4..d8e8077 100644 --- a/frontend/src/app/credits/credits.component.html +++ b/frontend/src/app/credits/credits.component.html @@ -1,21 +1,40 @@ -

credits works!

+
+ +

+ Upload a file. Share the URL/link. Download the file. Simple as that. +

+
+
+

Developer:

+

Walzen665

+
+
+

Version:

+

1.0.0

+
+
+

API-Status:

+

online

+
+ +
-

- Used media -

- +

+ Privacy Policy | + Terms of Use +

+ +
diff --git a/frontend/src/app/credits/credits.component.ts b/frontend/src/app/credits/credits.component.ts index 38205bc..f23db40 100644 --- a/frontend/src/app/credits/credits.component.ts +++ b/frontend/src/app/credits/credits.component.ts @@ -1,12 +1,26 @@ import { Component } from '@angular/core'; +import {RouterLink} from "@angular/router"; +import {LegalService} from "../../service/legalService"; @Component({ selector: 'app-credits', standalone: true, - imports: [], + imports: [ + RouterLink + ], templateUrl: './credits.component.html', styleUrl: './credits.component.scss' }) export class CreditsComponent { + constructor(private legalService: LegalService) { + } + + openPrivacyPolicyModal() { + this.legalService.openPrivacyPolicy(); + } + + openTermsOfUseModal() { + this.legalService.openTermsOfUse(); + } } diff --git a/frontend/src/app/home/home.component.html b/frontend/src/app/home/home.component.html index 4c30bf4..25d86be 100644 --- a/frontend/src/app/home/home.component.html +++ b/frontend/src/app/home/home.component.html @@ -25,72 +25,11 @@

We prioritize data security and privacy consequently. Files uploaded by our clients are securely stored on our servers for a maximum duration of one day, after which they are permanently deleted to guarantee the highest level of confidentiality and data protection. Read more in our - Privacy Policy note. - By using SharePulse, you agree to the Terms of Use.

+ Privacy Policy note. + By using SharePulse, you agree to the Terms of Use.

- - - - - - - - - - + + + + + + + + + + + diff --git a/frontend/src/app/navbar/navbar.component.ts b/frontend/src/app/navbar/navbar.component.ts index e995551..11f884a 100644 --- a/frontend/src/app/navbar/navbar.component.ts +++ b/frontend/src/app/navbar/navbar.component.ts @@ -1,21 +1,46 @@ -import { Component } from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {RouterLink} from "@angular/router"; import {NgClass} from "@angular/common"; +import {FormsModule} from "@angular/forms"; +import {LegalService} from "../../service/legalService"; @Component({ selector: 'app-navbar', standalone: true, - imports: [ - RouterLink, - NgClass - ], + imports: [ + RouterLink, + NgClass, + FormsModule + ], templateUrl: './navbar.component.html', styleUrl: './navbar.component.scss' }) export class NavbarComponent { + + @ViewChild('privacy_policy_modal') privacy_policy_modal: ElementRef | undefined; + @ViewChild('terms_of_use_modal') terms_of_use_modal: ElementRef | undefined; + isMenuOpen = false; + constructor(private legalService: LegalService) { + this.legalService.openModal$.subscribe((modalId) => { + if (modalId === 'privacyPolicy') { + this.openPrivacyPolicy(); + } else if (modalId === 'termsOfUse') { + this.openTermsOfUse(); + } + }); + } + toggleMenu(): void { this.isMenuOpen = !this.isMenuOpen; } + + openPrivacyPolicy() { + this.privacy_policy_modal?.nativeElement.showModal(); + } + + openTermsOfUse() { + this.terms_of_use_modal?.nativeElement.showModal(); + } } diff --git a/frontend/src/service/legalService.ts b/frontend/src/service/legalService.ts new file mode 100644 index 0000000..21f428f --- /dev/null +++ b/frontend/src/service/legalService.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class LegalService { + private openModalSource = new Subject(); + + openModal$ = this.openModalSource.asObservable(); + + openPrivacyPolicy() { + this.openModalSource.next('privacyPolicy'); + } + + openTermsOfUse() { + this.openModalSource.next('termsOfUse'); + } +} From e179cdf2dc980d2488d780ebc4520d9d2e0653fd Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 18 Feb 2024 22:56:26 +0100 Subject: [PATCH 3/5] Added cleanup job --- .../src/app/download/download.component.ts | 2 +- frontend/src/service/legalService.ts | 4 ++ .../de/w665/sharepulse/config/MvcConfig.java | 2 + .../w665/sharepulse/db/RethinkDBService.java | 24 ++++------ .../db/repo/ExpiredFileUploadRepository.java | 41 +++++++++++++++++ .../db/repo/FileUploadRepository.java | 46 +++++++++++++++---- .../de/w665/sharepulse/model/FileUpload.java | 2 +- .../service/FileCleanupService.java | 36 +++++++++++++++ .../w665/sharepulse/service/FileService.java | 11 ++++- src/main/resources/application.properties | 2 +- 10 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 src/main/java/de/w665/sharepulse/db/repo/ExpiredFileUploadRepository.java create mode 100644 src/main/java/de/w665/sharepulse/service/FileCleanupService.java 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/ From 6a1e91f5d8e280e11c54b39e623681155b78e134 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 19 Feb 2024 17:24:59 +0100 Subject: [PATCH 4/5] Added unique ID generator - Updated data cleaning feature - Added fileID database logic --- .../src/app/download/download.component.ts | 15 ++++++- .../w665/sharepulse/db/RethinkDBService.java | 14 +++++- .../sharepulse/db/repo/FileIdRepository.java | 37 +++++++++++++++ .../sharepulse/rest/mappings/Download.java | 2 +- .../sharepulse/service/FileIdService.java | 45 +++++++++++++------ .../service/FileSecurityService.java | 8 ++-- src/main/resources/application.properties | 6 ++- 7 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 src/main/java/de/w665/sharepulse/db/repo/FileIdRepository.java diff --git a/frontend/src/app/download/download.component.ts b/frontend/src/app/download/download.component.ts index 1cd06e8..bc9e3b5 100644 --- a/frontend/src/app/download/download.component.ts +++ b/frontend/src/app/download/download.component.ts @@ -34,6 +34,7 @@ export class DownloadComponent { fileDownloadFinished: boolean = false; waitingForPassword: boolean = false; downloadProgress: number = 0; + targetUploadProgress: number = 0; downloadDuration: string = ""; passwordWrong: boolean = false; @@ -155,8 +156,8 @@ export class DownloadComponent { // Calculate the percentage of download completed if(progressEvent.total) { const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); - this.downloadProgress = percentCompleted; - console.log(percentCompleted + '%'); // Log the percentage or update any progress UI component + this.targetUploadProgress = percentCompleted; + this.smoothProgressUpdate(); } } }) @@ -201,6 +202,16 @@ export class DownloadComponent { private wrongPassword() { this.passwordWrong = true; } + + smoothProgressUpdate() { + if (this.downloadProgress < this.targetUploadProgress) { + this.downloadProgress += 0.01 * (this.targetUploadProgress - this.downloadProgress); + requestAnimationFrame(this.smoothProgressUpdate.bind(this)); + } else if (this.downloadProgress > this.targetUploadProgress) { + // Handle overshoot + this.downloadProgress = this.targetUploadProgress; + } + } } interface DownloadInfo { downloadable: boolean; diff --git a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java index 3e44e4e..da1a8d0 100644 --- a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java +++ b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java @@ -61,7 +61,12 @@ 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 @@ -69,7 +74,12 @@ public class RethinkDBService { 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.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!"); } diff --git a/src/main/java/de/w665/sharepulse/db/repo/FileIdRepository.java b/src/main/java/de/w665/sharepulse/db/repo/FileIdRepository.java new file mode 100644 index 0000000..6c0043d --- /dev/null +++ b/src/main/java/de/w665/sharepulse/db/repo/FileIdRepository.java @@ -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 result = r.db("sharepulse").table("id_store") + .filter(r.hashMap("fileId", fileId)) + .run(connection); + + return result.hasNext(); + } +} diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/Download.java b/src/main/java/de/w665/sharepulse/rest/mappings/Download.java index 494e011..4a7a15a 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/Download.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/Download.java @@ -82,7 +82,7 @@ public class Download extends ApiRestController { Optional 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; diff --git a/src/main/java/de/w665/sharepulse/service/FileIdService.java b/src/main/java/de/w665/sharepulse/service/FileIdService.java index 457de4b..f7e6aa3 100644 --- a/src/main/java/de/w665/sharepulse/service/FileIdService.java +++ b/src/main/java/de/w665/sharepulse/service/FileIdService.java @@ -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(); } - - } diff --git a/src/main/java/de/w665/sharepulse/service/FileSecurityService.java b/src/main/java/de/w665/sharepulse/service/FileSecurityService.java index 4d57dd2..cdb6157 100644 --- a/src/main/java/de/w665/sharepulse/service/FileSecurityService.java +++ b/src/main/java/de/w665/sharepulse/service/FileSecurityService.java @@ -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 { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5e0484c..de1047e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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=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/ From 0c2451610197445cbc1a5e862d3fbf9868712502 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 19 Feb 2024 17:38:50 +0100 Subject: [PATCH 5/5] Update navbar.component.html --- frontend/src/app/navbar/navbar.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/navbar/navbar.component.html b/frontend/src/app/navbar/navbar.component.html index b31d688..910dd9e 100644 --- a/frontend/src/app/navbar/navbar.component.html +++ b/frontend/src/app/navbar/navbar.component.html @@ -6,7 +6,7 @@ SharePulse
- Administration + About