From b15529008b4138eb265d0a7c81f93573dcd84a89 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 7 Feb 2024 23:51:58 +0100 Subject: [PATCH 01/13] Added file upload UI --- .../app/format-file-size-pipe.pipe.spec.ts | 8 +++ .../src/app/format-file-size-pipe.pipe.ts | 19 +++++ frontend/src/app/upload/upload.component.html | 49 ++++++++++--- frontend/src/app/upload/upload.component.ts | 70 ++++++++++++++++++- frontend/src/assets/circle-check-solid.svg | 1 + frontend/src/assets/file-solid.svg | 1 + 6 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/format-file-size-pipe.pipe.spec.ts create mode 100644 frontend/src/app/format-file-size-pipe.pipe.ts create mode 100644 frontend/src/assets/circle-check-solid.svg create mode 100644 frontend/src/assets/file-solid.svg diff --git a/frontend/src/app/format-file-size-pipe.pipe.spec.ts b/frontend/src/app/format-file-size-pipe.pipe.spec.ts new file mode 100644 index 0000000..0d1235e --- /dev/null +++ b/frontend/src/app/format-file-size-pipe.pipe.spec.ts @@ -0,0 +1,8 @@ +import { FormatFileSizePipePipe } from './format-file-size-pipe.pipe'; + +describe('FormatFileSizePipePipe', () => { + it('create an instance', () => { + const pipe = new FormatFileSizePipePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/format-file-size-pipe.pipe.ts b/frontend/src/app/format-file-size-pipe.pipe.ts new file mode 100644 index 0000000..b7c1040 --- /dev/null +++ b/frontend/src/app/format-file-size-pipe.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'formatFileSizePipe', + standalone: true +}) +export class FormatFileSizePipePipe implements PipeTransform { + + transform(value: number, ...args: unknown[]): string { + if (value === null || value === 0) return '0 Bytes'; + + const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1); + const filesize = Number((value / Math.pow(1024, index)).toFixed(2)); + + return `${filesize.toLocaleString()} ${units[index]}`; + } + +} diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index 2be1da3..f905d7d 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -1,34 +1,63 @@

Upload Your File

-
+
-
+

Drag and drop your files here or browse

- + +
+ +
+ +

{{ fileToUpload.name }}

+

{{ fileToUpload.size | formatFileSizePipe }}

- - Store for only one hour + +
- - Allow only one download + +
- - Protect download with password + +
- + +
+
+
{{uploadProgress}}%
+

Uploading {{ fileToUpload.name }}...

+
+
+ +

File uploaded successfully!

+

Drag and drop files, or click to select the files you want to share.

+ + diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index 0324250..770c0d9 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -1,12 +1,78 @@ -import { Component } from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {NgIf} from "@angular/common"; +import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe"; +import {FormsModule} from "@angular/forms"; @Component({ selector: 'app-upload', standalone: true, - imports: [], + imports: [ + NgIf, + FormatFileSizePipePipe, + FormsModule + ], templateUrl: './upload.component.html', styleUrl: './upload.component.scss' }) export class UploadComponent { + @ViewChild('fileInput') fileInput!: ElementRef; + fileToUpload: File | null = null; + + shortStorage: boolean = false; + singleDownload: boolean = false; + passwordProtected: boolean = false; + password: string = ''; + + uploadStarted: boolean = false; + uploadProgress = 0; + uploadFinished = false; + + /** + * Manages the file input click or drag and drop + */ + onFileSelected(event: any): void { + const files = event.target.files; + if (files && files.length > 0) { + this.handleFiles(files); + } + } + onDragOver(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + // Optionally add visual feedback + } + onDrop(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + const files = event.dataTransfer?.files; + if (files && files.length > 0) { + this.handleFiles(files); + } + // Remove visual feedback + } + handleFiles(files: FileList): void { + for(let i = 0; i < files.length; i++) { + const file = files[i]; + this.fileToUpload = file; + } + } + + startUpload(): void { + if (this.fileToUpload) { + console.log(`Uploading file: ${this.fileToUpload.name}, Short storage: ${this.shortStorage}, Single download: ${this.singleDownload}, Password protected: ${this.passwordProtected}`); + this.uploadStarted = true; + + + const interval = setInterval(() => { + if (this.uploadProgress < 100) { + this.uploadProgress++; + } else { + clearInterval(interval); // Stop the interval when progress reaches 100% + this.uploadFinished = true; + } + }, 10); + + } + } } diff --git a/frontend/src/assets/circle-check-solid.svg b/frontend/src/assets/circle-check-solid.svg new file mode 100644 index 0000000..504bf80 --- /dev/null +++ b/frontend/src/assets/circle-check-solid.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/file-solid.svg b/frontend/src/assets/file-solid.svg new file mode 100644 index 0000000..f953184 --- /dev/null +++ b/frontend/src/assets/file-solid.svg @@ -0,0 +1 @@ + From a864d81befc7895e4d1e28ba260d13f6166b8993 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 7 Feb 2024 23:53:11 +0100 Subject: [PATCH 02/13] Update upload.component.html - Added circle icon caching --- frontend/src/app/upload/upload.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index f905d7d..71f0485 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -60,4 +60,5 @@ From b52128c35c26fb99cb7b79fbf48f4711ece854ec Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Feb 2024 00:02:11 +0100 Subject: [PATCH 03/13] Added deployment modes - CORS only apply in dev mode --- .../de/w665/sharepulse/config/CorsConfig.java | 22 +++++++++++++++++++ src/main/resources/application.properties | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 src/main/java/de/w665/sharepulse/config/CorsConfig.java diff --git a/src/main/java/de/w665/sharepulse/config/CorsConfig.java b/src/main/java/de/w665/sharepulse/config/CorsConfig.java new file mode 100644 index 0000000..e9f9fb6 --- /dev/null +++ b/src/main/java/de/w665/sharepulse/config/CorsConfig.java @@ -0,0 +1,22 @@ +package de.w665.sharepulse.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@Profile("development") +public class CorsConfig implements WebMvcConfigurer { + + // NEVER LEAVE THIS CONFIGURATION IN PRODUCTION + @Override + public void addCorsMappings(CorsRegistry registry) { + System.out.println("CORS CONFIGURATION ENABLED | DEVELOPMENT PROFILE"); + System.out.println("IF YOU SEE THIS MESSAGE IN PRODUCTION, PLEASE DISABLE DEVELOPEMENT MODE IN APPLICATION.PROPERTIES"); + registry.addMapping("/api/v1/speed-test") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*"); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 05b0892..ce36a92 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,3 +22,6 @@ rethinkdb.database=sharepulse # Miscellaneous server.port=80 spring.application.name=sharepulse + +# Spring profiles (Options: development, production) (Controls cors) +spring.profiles.active=development \ No newline at end of file From fb1cbce8c7dd1f08677527be555ac44e27d53abe Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Feb 2024 00:08:47 +0100 Subject: [PATCH 04/13] Updated readme --- README.md | 12 +++++++++++- build.gradle | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 66412f3..6596347 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,14 @@ Upload a file. Share the URL/link. Download the file. -Simple as that. \ No newline at end of file +Simple as that. + +## Setup development environment + +Requirements: +- Java JDK 21 +- Gradle 8.5 + +1. Update `spring.profiles.active` in `application.properties` to `development` +2. Start external Backend and Database +3. Run `./gradlew bootRun ` in the root directory \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6a61ff9..078791f 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ version = '0.0.2' java { sourceCompatibility = '21' + targetCompatibility = '21' } configurations { From bfcce1ec3bbfb5ef949cf34b5d17501e64b423fc Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Feb 2024 00:09:39 +0100 Subject: [PATCH 05/13] Update build.gradle --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 078791f..f89eaee 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,9 @@ version = '0.0.2' java { sourceCompatibility = '21' targetCompatibility = '21' + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } configurations { From ab16fd1c5d910cf86ec1d671cc7aa6870c1c9ac6 Mon Sep 17 00:00:00 2001 From: Max W <66736561+Walzen665@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:14:04 +0100 Subject: [PATCH 06/13] Added upload speedtest - Updated CORS to apply for all API endpoints - Added database docker compose --- db/docker-compose.yml | 13 +++++ frontend/src/app/upload/upload.component.ts | 56 +++++++++++++++++++ .../de/w665/sharepulse/config/CorsConfig.java | 2 +- .../sharepulse/rest/mappings/SpeedTest.java | 23 ++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 db/docker-compose.yml diff --git a/db/docker-compose.yml b/db/docker-compose.yml new file mode 100644 index 0000000..d6407f1 --- /dev/null +++ b/db/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' +services: + rethinkdb: + image: rethinkdb:latest + restart: unless-stopped + volumes: + - rethinkdb_data:/data + ports: + - "28015:28015" # Client driver port + - "8080:8080" # Admin UI port + +volumes: + rethinkdb_data: \ No newline at end of file diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index 770c0d9..fb8f4e1 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -2,6 +2,7 @@ import {Component, ElementRef, ViewChild} from '@angular/core'; import {NgIf} from "@angular/common"; import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe"; import {FormsModule} from "@angular/forms"; +import axios from "axios"; @Component({ selector: 'app-upload', @@ -28,6 +29,10 @@ export class UploadComponent { uploadProgress = 0; uploadFinished = false; + constructor() { + this.uploadSpeedTest(); + } + /** * Manages the file input click or drag and drop */ @@ -75,4 +80,55 @@ export class UploadComponent { } } + + private uploadSpeedTest() { + const start = new Date().getTime(); // Start timer + + // Create a 1MB payload for upload + const payload = new ArrayBuffer(1024 * 1024); // 1MB + const uint8View = new Uint8Array(payload); + for (let i = 0; i < uint8View.length; i++) { + uint8View[i] = 0; + } + + axios({ + method: 'post', + url: 'http://localhost/api/v1/upload-speed-test', + data: uint8View, + headers: { + 'Content-Type': 'application/octet-stream', + 'Access-Control-Allow-Origin': '*', // Allow CORS + }, + onUploadProgress: function(progressEvent) { + // Optional: handle progress events for feedback + } + }) + .then(response => { + const end = new Date().getTime(); // End timer + const duration = (end - start) / 1000; // Convert ms to seconds + const bitsUploaded = payload.byteLength * 8; + const speedBps = bitsUploaded / duration; + const speedKbps = speedBps / 1024; + const speedMbps = speedKbps / 1024; + console.log(`Upload speed: ${speedMbps.toFixed(2)} Mbps`); + }) + .catch(error => { + console.error('Error during upload test:', error); + }); + } + + /** + * Calculates the estimated upload time. + * @param {number} fileSize - The size of the file in bytes. + * @param {number} uploadSpeed - The upload speed in bytes per second. + * @returns {number} The estimated upload time in seconds. + */ + private calculateEstimatedUploadTime(fileSize: number, uploadSpeed: number) { + if (uploadSpeed <= 0) { + console.error("Upload speed must be greater than 0."); + return NaN; + } + const estimatedTimeInSeconds = fileSize / uploadSpeed; + return estimatedTimeInSeconds; + } } diff --git a/src/main/java/de/w665/sharepulse/config/CorsConfig.java b/src/main/java/de/w665/sharepulse/config/CorsConfig.java index e9f9fb6..37f5971 100644 --- a/src/main/java/de/w665/sharepulse/config/CorsConfig.java +++ b/src/main/java/de/w665/sharepulse/config/CorsConfig.java @@ -14,7 +14,7 @@ public class CorsConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { System.out.println("CORS CONFIGURATION ENABLED | DEVELOPMENT PROFILE"); System.out.println("IF YOU SEE THIS MESSAGE IN PRODUCTION, PLEASE DISABLE DEVELOPEMENT MODE IN APPLICATION.PROPERTIES"); - registry.addMapping("/api/v1/speed-test") + registry.addMapping("/api/v1/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*"); diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/SpeedTest.java b/src/main/java/de/w665/sharepulse/rest/mappings/SpeedTest.java index 8a3a71c..35345d8 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/SpeedTest.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/SpeedTest.java @@ -1,12 +1,16 @@ package de.w665.sharepulse.rest.mappings; import de.w665.sharepulse.rest.ApiRestController; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.io.InputStream; @Slf4j @RestController @@ -30,4 +34,23 @@ public class SpeedTest extends ApiRestController { return; } } + + @PostMapping("/upload-speed-test") + public void uploadSpeedTest(HttpServletRequest request, HttpServletResponse response) { + final int maxBytes = 1024 * 1024; // Limit to 1MB + byte[] buffer = new byte[1024]; // Buffer to read data + int bytesRead; + int totalBytesRead = 0; + + try (InputStream inputStream = request.getInputStream()) { + while ((bytesRead = inputStream.read(buffer)) != -1 && totalBytesRead < maxBytes) { + totalBytesRead += bytesRead; + } + response.setStatus(HttpServletResponse.SC_OK); + } catch (IOException e) { + log.error("Upload speed test failed.", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + } From 7ae97b1aa3e50e369d699bcfbe900e8a62bce2c4 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 11:01:10 +0100 Subject: [PATCH 07/13] Finished estimated upload time calculator --- frontend/src/app/upload/upload.component.ts | 28 +++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index fb8f4e1..0f62ca4 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -29,6 +29,8 @@ export class UploadComponent { uploadProgress = 0; uploadFinished = false; + uploadSpeedBps: number = 0; + constructor() { this.uploadSpeedTest(); } @@ -68,6 +70,8 @@ export class UploadComponent { console.log(`Uploading file: ${this.fileToUpload.name}, Short storage: ${this.shortStorage}, Single download: ${this.singleDownload}, Password protected: ${this.passwordProtected}`); this.uploadStarted = true; + this.calculateEstimatedUploadTime(); + const interval = setInterval(() => { if (this.uploadProgress < 100) { @@ -107,28 +111,20 @@ export class UploadComponent { const end = new Date().getTime(); // End timer const duration = (end - start) / 1000; // Convert ms to seconds const bitsUploaded = payload.byteLength * 8; - const speedBps = bitsUploaded / duration; - const speedKbps = speedBps / 1024; - const speedMbps = speedKbps / 1024; - console.log(`Upload speed: ${speedMbps.toFixed(2)} Mbps`); + this.uploadSpeedBps = bitsUploaded / duration; + console.log("Speed test ping finished."); }) .catch(error => { console.error('Error during upload test:', error); }); } - /** - * Calculates the estimated upload time. - * @param {number} fileSize - The size of the file in bytes. - * @param {number} uploadSpeed - The upload speed in bytes per second. - * @returns {number} The estimated upload time in seconds. - */ - private calculateEstimatedUploadTime(fileSize: number, uploadSpeed: number) { - if (uploadSpeed <= 0) { - console.error("Upload speed must be greater than 0."); - return NaN; + private calculateEstimatedUploadTime() : void { + const fileSize = this.fileToUpload?.size; + const uploadSpeed = this.uploadSpeedBps / 8; // Convert bits per second to bytes per second + if (fileSize && uploadSpeed) { + const estimatedTimeInSeconds = fileSize / uploadSpeed; + console.log(`Estimated upload time: ${estimatedTimeInSeconds} seconds`); } - const estimatedTimeInSeconds = fileSize / uploadSpeed; - return estimatedTimeInSeconds; } } From 7d5c5fc1faf6adbfefd06cb7fa0b22853de4a1e3 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 11:20:15 +0100 Subject: [PATCH 08/13] Create DevelopmentStore.ts - Added DevStore for easy baseURL change for developement --- frontend/src/store/DevelopmentStore.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 frontend/src/store/DevelopmentStore.ts diff --git a/frontend/src/store/DevelopmentStore.ts b/frontend/src/store/DevelopmentStore.ts new file mode 100644 index 0000000..48d11de --- /dev/null +++ b/frontend/src/store/DevelopmentStore.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; + +interface DevelopmentStoreState { + baseUrl: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class DevelopmentStore { + private state: BehaviorSubject = new BehaviorSubject({ + baseUrl: 'http://localhost/', + }); + + + get baseUrl() { + return this.state.asObservable().pipe(map(state => state.baseUrl)); + } +} From 93a3ab41bdd72454610a55439a5700bc67c738dd Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 13:40:27 +0100 Subject: [PATCH 09/13] Updated DevStore to work automatically --- frontend/README.md | 28 ++------------------- frontend/src/store/DevelopmentStore.ts | 34 ++++++++++++++++---------- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 1831a52..01e6a2f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,27 +1,3 @@ -# Frontend +# Befor production deployment -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.3. - -## Development server - -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. +Note that the baseURL is automatically defined by the `DevelopementStore.ts` store. diff --git a/frontend/src/store/DevelopmentStore.ts b/frontend/src/store/DevelopmentStore.ts index 48d11de..2bd5244 100644 --- a/frontend/src/store/DevelopmentStore.ts +++ b/frontend/src/store/DevelopmentStore.ts @@ -1,21 +1,29 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; -import { map } from 'rxjs/operators'; - -interface DevelopmentStoreState { - baseUrl: string; -} @Injectable({ - providedIn: 'root' + providedIn: 'root', }) -export class DevelopmentStore { - private state: BehaviorSubject = new BehaviorSubject({ - baseUrl: 'http://localhost/', - }); +/** + * THIS IS NOT A OBSERVABLE STORE. + * This store automatically identifies if the env is production or development. + * If this app is running on development mode, it will add port 80 to the base url. + */ +export class EnvironmentService { + private isDevelopment: boolean; + constructor() { + this.isDevelopment = location.port === '4200'; + } - get baseUrl() { - return this.state.asObservable().pipe(map(state => state.baseUrl)); + getIsDevelopment(): boolean { + return this.isDevelopment; + } + + getBaseUrl(): string { + if (this.isDevelopment) { + return 'http://localhost:80/'; + } else { + return window.location.origin; + } } } From 1bce2e1d2e2c5ff4257c386b9d1fad8cd62c2c3e Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 13:40:36 +0100 Subject: [PATCH 10/13] Update Upload.java --- src/main/java/de/w665/sharepulse/rest/mappings/Upload.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java index ee14611..e887ec5 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java @@ -30,6 +30,9 @@ public class Upload extends ApiRestController { @RequestParam(value = "password", required = false) String password, @RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload, @RequestParam(value = "fileDescription", required = false) String fileDescription) { + + // TODO: Handle shortStorage0 + if (file.isEmpty()) { log.debug("User tried to upload an empty file. IP: " + request.getRemoteAddr()); return new ResponseEntity<>("Please select a file to upload.", HttpStatus.NOT_ACCEPTABLE); From a4b6069789b0d8b949486347cd7e97d8f7ad47a8 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 17:52:58 +0100 Subject: [PATCH 11/13] Added FileUpload logic - Added smoother for progress indicator --- frontend/src/app/upload/upload.component.html | 2 +- frontend/src/app/upload/upload.component.ts | 64 ++++++++++++++++--- frontend/src/store/DevelopmentStore.ts | 2 +- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index 71f0485..4f51bde 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -46,7 +46,7 @@
-
{{uploadProgress}}%
+
{{ uploadProgress | number:'1.0-0' }}%

Uploading {{ fileToUpload.name }}...

diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index 0f62ca4..ef48bd8 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -1,8 +1,9 @@ import {Component, ElementRef, ViewChild} from '@angular/core'; -import {NgIf} from "@angular/common"; +import {DecimalPipe, NgIf} from "@angular/common"; import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe"; import {FormsModule} from "@angular/forms"; -import axios from "axios"; +import axios, {AxiosProgressEvent} from "axios"; +import { DevelopmentStore } from '../../store/DevelopmentStore'; @Component({ selector: 'app-upload', @@ -10,7 +11,8 @@ import axios from "axios"; imports: [ NgIf, FormatFileSizePipePipe, - FormsModule + FormsModule, + DecimalPipe ], templateUrl: './upload.component.html', styleUrl: './upload.component.scss' @@ -22,16 +24,21 @@ export class UploadComponent { shortStorage: boolean = false; singleDownload: boolean = false; + fileDescription: string = ''; passwordProtected: boolean = false; - password: string = ''; + password: string = 'nigga123'; uploadStarted: boolean = false; - uploadProgress = 0; + uploadProgress = 0; // Real progress + targetUploadProgress = 0; // New target progress to reach + uploadFinished = false; uploadSpeedBps: number = 0; - constructor() { + + + constructor(private developmentStore: DevelopmentStore) { this.uploadSpeedTest(); } @@ -63,16 +70,37 @@ export class UploadComponent { const file = files[i]; this.fileToUpload = file; } + this.calculateEstimatedUploadTime(); } startUpload(): void { if (this.fileToUpload) { console.log(`Uploading file: ${this.fileToUpload.name}, Short storage: ${this.shortStorage}, Single download: ${this.singleDownload}, Password protected: ${this.passwordProtected}`); this.uploadStarted = true; + const formData = this.buildFormDataObject(); - this.calculateEstimatedUploadTime(); + const config = { + headers: { + 'Content-Type': 'multipart/form-data', + 'Access-Control-Allow-Origin': '*', // Allow CORS + }, + onUploadProgress: (progressEvent: AxiosProgressEvent) => { + // @ts-ignore + this.targetUploadProgress = Math.round((progressEvent.loaded / progressEvent.total) * 100); + this.smoothProgressUpdate(); + } + }; + + axios.post(this.developmentStore.getBaseUrl() + 'api/v1/upload', formData, config) + .then(response => { + console.log('Upload completed successfully:', response.data); + }) + .catch(error => { + console.error('Upload failed:', error.response ? error.response.data : error.message); + }); + /*// Old simulation of upload progress const interval = setInterval(() => { if (this.uploadProgress < 100) { this.uploadProgress++; @@ -80,11 +108,21 @@ export class UploadComponent { clearInterval(interval); // Stop the interval when progress reaches 100% this.uploadFinished = true; } - }, 10); + }, 10);*/ } } + buildFormDataObject(): FormData { + const formData = new FormData(); + formData.append('file', this.fileToUpload as Blob); + formData.append('password', this.passwordProtected ? this.password : ''); + formData.append('singleDownload', this.singleDownload.toString()); + formData.append('shortStorage', this.shortStorage.toString()); + formData.append('fileDescription', this.fileDescription); + return formData; + } + private uploadSpeedTest() { const start = new Date().getTime(); // Start timer @@ -127,4 +165,14 @@ export class UploadComponent { console.log(`Estimated upload time: ${estimatedTimeInSeconds} seconds`); } } + + smoothProgressUpdate() { + if (this.uploadProgress < this.targetUploadProgress) { + this.uploadProgress += 0.01 * (this.targetUploadProgress - this.uploadProgress); + requestAnimationFrame(this.smoothProgressUpdate.bind(this)); + } else if (this.uploadProgress > this.targetUploadProgress) { + // Handle overshoot + this.uploadProgress = this.targetUploadProgress; + } + } } diff --git a/frontend/src/store/DevelopmentStore.ts b/frontend/src/store/DevelopmentStore.ts index 2bd5244..4ae2945 100644 --- a/frontend/src/store/DevelopmentStore.ts +++ b/frontend/src/store/DevelopmentStore.ts @@ -8,7 +8,7 @@ import { Injectable } from '@angular/core'; * This store automatically identifies if the env is production or development. * If this app is running on development mode, it will add port 80 to the base url. */ -export class EnvironmentService { +export class DevelopmentStore { private isDevelopment: boolean; constructor() { From a0a096b9e7b9b6448f441eafe3e8e5691d2f0d53 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 19:08:27 +0100 Subject: [PATCH 12/13] Fixed ts-ignore --- frontend/src/app/upload/upload.component.html | 2 ++ frontend/src/app/upload/upload.component.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index 4f51bde..02540d4 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -45,10 +45,12 @@
+
{{ uploadProgress | number:'1.0-0' }}%

Uploading {{ fileToUpload.name }}...

+

File uploaded successfully!

diff --git a/frontend/src/app/upload/upload.component.ts b/frontend/src/app/upload/upload.component.ts index ef48bd8..5661c04 100644 --- a/frontend/src/app/upload/upload.component.ts +++ b/frontend/src/app/upload/upload.component.ts @@ -85,8 +85,12 @@ export class UploadComponent { 'Access-Control-Allow-Origin': '*', // Allow CORS }, onUploadProgress: (progressEvent: AxiosProgressEvent) => { - // @ts-ignore - this.targetUploadProgress = Math.round((progressEvent.loaded / progressEvent.total) * 100); + if (progressEvent.total) { + this.targetUploadProgress = Math.round((progressEvent.loaded / progressEvent.total) * 100); + } else { + this.uploadProgress = 99; // Unable to compute progress since total size is unknown + console.error('Unable to compute progress since total size is unknown'); + } this.smoothProgressUpdate(); } }; @@ -94,6 +98,7 @@ export class UploadComponent { axios.post(this.developmentStore.getBaseUrl() + 'api/v1/upload', formData, config) .then(response => { console.log('Upload completed successfully:', response.data); + this.uploadFinished = true; }) .catch(error => { console.error('Upload failed:', error.response ? error.response.data : error.message); From fea0db7b9f40a156d787de8a44b2ea5bb20c586a Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 15 Feb 2024 20:42:27 +0100 Subject: [PATCH 13/13] Styled uploadUI - Updated privacy disclaimer - Updated libs used --- .../src/app/credits/credits.component.html | 19 +++++++++++++++---- frontend/src/app/home/home.component.html | 10 ++++++++-- frontend/src/app/upload/upload.component.html | 6 +++--- frontend/src/app/upload/upload.component.ts | 14 -------------- .../sharepulse/rest/mappings/SpeedTest.java | 4 ++-- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/credits/credits.component.html b/frontend/src/app/credits/credits.component.html index 1472cb1..11be4c4 100644 --- a/frontend/src/app/credits/credits.component.html +++ b/frontend/src/app/credits/credits.component.html @@ -1,10 +1,21 @@

credits works!

-

+ +

Used media

-
    + diff --git a/frontend/src/app/home/home.component.html b/frontend/src/app/home/home.component.html index 8f25ad0..79d80ec 100644 --- a/frontend/src/app/home/home.component.html +++ b/frontend/src/app/home/home.component.html @@ -23,7 +23,13 @@

    Data Privacy/Security

    We prioritize data security and privacy consequently. - Any files uploaded by our clients are securely stored on our servers for a maximum duration of one day, - after which they are permanently and irretrievably wiped to ensure the utmost confidentiality and data protection.

    + 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.

+ + + diff --git a/frontend/src/app/upload/upload.component.html b/frontend/src/app/upload/upload.component.html index 02540d4..f3616be 100644 --- a/frontend/src/app/upload/upload.component.html +++ b/frontend/src/app/upload/upload.component.html @@ -1,7 +1,7 @@

Upload Your File

-
+
+

Drag and drop files, or click to select the files you want to share.

@@ -57,10 +58,9 @@
-

Drag and drop files, or click to select the files you want to share.

-