Merge pull request 'frontend' (#3) from frontend into main
Reviewed-on: https://git.walzen665.de/Walzen665/fileshare-service/pulls/3
This commit is contained in:
commit
9f830c4e48
12
README.md
12
README.md
@ -2,4 +2,14 @@
|
|||||||
|
|
||||||
Upload a file. Share the URL/link. Download the file.
|
Upload a file. Share the URL/link. Download the file.
|
||||||
|
|
||||||
Simple as that.
|
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
|
@ -9,6 +9,10 @@ version = '0.0.2'
|
|||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = '21'
|
sourceCompatibility = '21'
|
||||||
|
targetCompatibility = '21'
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
13
db/docker-compose.yml
Normal file
13
db/docker-compose.yml
Normal file
@ -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:
|
@ -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.
|
Note that the baseURL is automatically defined by the `DevelopementStore.ts` store.
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
<p>credits works!</p>
|
<p>credits works!</p>
|
||||||
<h2>
|
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-800 mb-4">
|
||||||
Used media
|
Used media
|
||||||
</h2>
|
</h2>
|
||||||
<ul>
|
<ul class="list-disc pl-5 space-y-2">
|
||||||
<li>
|
<li>
|
||||||
<p>App Icon by flaticon.com</p>
|
<p class="font-medium text-gray-700">Icons used:</p>
|
||||||
<a href="https://www.flaticon.com/de/autoren/najmunnahar">NajmunNahar</a>
|
<a href="https://fontawesome.com/" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out">Fontawesome</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p class="font-medium text-gray-700">HTML, CSS, JS Libraries used:</p>
|
||||||
|
<a href="https://tailwindcss.com/" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out">TailwindCSS</a>,
|
||||||
|
<a href="https://axios-http.com/" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out">AXIOS http</a>,
|
||||||
|
<a href="https://www.npmjs.com/package/tailwindcss-animate" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out">TailwindCSS Animations</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p class="font-medium text-gray-700">App Icon by flaticon.com</p>
|
||||||
|
<a href="https://www.flaticon.com/de/autoren/najmunnahar" class="text-blue-500 hover:text-blue-600 transition duration-300 ease-in-out">NajmunNahar</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
8
frontend/src/app/format-file-size-pipe.pipe.spec.ts
Normal file
8
frontend/src/app/format-file-size-pipe.pipe.spec.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
19
frontend/src/app/format-file-size-pipe.pipe.ts
Normal file
19
frontend/src/app/format-file-size-pipe.pipe.ts
Normal file
@ -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]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,7 +23,13 @@
|
|||||||
<div class="bg-white shadow-lg rounded-lg p-6 mt-10 animate-in fade-in slide-in-from-bottom duration-500">
|
<div class="bg-white shadow-lg rounded-lg p-6 mt-10 animate-in fade-in slide-in-from-bottom duration-500">
|
||||||
<h2 class="text-2xl font-bold text-gray-700 mb-4 text-center">Data Privacy/Security</h2>
|
<h2 class="text-2xl font-bold text-gray-700 mb-4 text-center">Data Privacy/Security</h2>
|
||||||
<p class="text-gray-600 text-center mb-4">We prioritize data security and privacy consequently.
|
<p class="text-gray-600 text-center mb-4">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,
|
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.</p>
|
after which they are permanently deleted to guarantee the highest level of confidentiality and data protection.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invisible SVGs to prevent lazy loading -->
|
||||||
|
<div class="invisible h-0 w-0">
|
||||||
|
<img src="./assets/cloud-arrow-up-solid.svg">
|
||||||
|
<img src="./assets/cloud-arrow-down-solid.svg">
|
||||||
|
</div>
|
||||||
|
@ -1,34 +1,66 @@
|
|||||||
<div class="container mx-auto p-4 mt-4">
|
<div class="container mx-auto p-4 mt-4">
|
||||||
<h1 class="text-4xl font-bold text-center text-gray-800 mb-10">Upload Your File</h1>
|
<h1 class="text-4xl font-bold text-center text-gray-800 mb-10">Upload Your File</h1>
|
||||||
<div class="bg-white shadow-lg rounded-lg p-6">
|
<div class="bg-white shadow-lg rounded-lg p-6">
|
||||||
<div class="flex flex-col items-center justify-center">
|
<div *ngIf="!uploadStarted" class="flex flex-col items-center justify-center lg:p-5">
|
||||||
<!-- File Drop Area -->
|
<!-- File Drop Area -->
|
||||||
<div class="border-2 border-dashed border-gray-300 rounded-lg w-full p-20 flex flex-col items-center justify-center mb-6">
|
<div
|
||||||
|
*ngIf="!fileToUpload && !uploadStarted"
|
||||||
|
(drop)="onDrop($event)"
|
||||||
|
(dragover)="onDragOver($event)"
|
||||||
|
(click)="fileInput.click()"
|
||||||
|
class="border-2 border-dashed border-gray-300 rounded-lg w-full p-20 flex flex-col items-center justify-center mb-6 animate-in fade-in duration-500">
|
||||||
<img class="w-16 mb-3" src="./assets/cloud-arrow-up-solid.svg">
|
<img class="w-16 mb-3" src="./assets/cloud-arrow-up-solid.svg">
|
||||||
<p class="text-gray-700 mb-3">Drag and drop your files here or <span class="text-blue-600 cursor-pointer">browse</span></p>
|
<p class="text-gray-700 mb-3">Drag and drop your files here or <span class="text-blue-600 cursor-pointer">browse</span></p>
|
||||||
<input type="file" class="hidden" />
|
<input
|
||||||
|
#fileInput
|
||||||
|
(change)="onFileSelected($event)"
|
||||||
|
type="file" class="hidden" />
|
||||||
|
</div>
|
||||||
|
<!-- File Details -->
|
||||||
|
<div
|
||||||
|
*ngIf="fileToUpload && !uploadStarted"
|
||||||
|
class="w-full p-10 flex flex-col items-center justify-center mb-3 animate-in fade-in duration-500">
|
||||||
|
<img class="w-14 mb-3" src="./assets/file-solid.svg">
|
||||||
|
<p class="text-gray-700 mb-3"> {{ fileToUpload.name }} </p>
|
||||||
|
<p class="text-gray-700 mb-3"> {{ fileToUpload.size | formatFileSizePipe }} </p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<div class="w-full mb-10">
|
<div class="w-full mb-10">
|
||||||
<label class="block text-gray-700 text-l font-bold mb-4">Options</label>
|
<label class="block text-gray-700 text-l font-bold mb-4">Options</label>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input type="checkbox" class="toggle" checked />
|
<input id="shortStorage" [(ngModel)]="shortStorage" type="checkbox" class="toggle" checked />
|
||||||
<span class="ml-2 text-gray-700">Store for only one hour</span>
|
<label for="shortStorage" class="ml-2 text-gray-700">Store for only one hour</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input type="checkbox" class="toggle" />
|
<input id="singleDownload" [(ngModel)]="singleDownload" type="checkbox" class="toggle" />
|
||||||
<span class="ml-2 text-gray-700">Allow only one download</span>
|
<label for="singleDownload" class="ml-2 text-gray-700">Allow only one download</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input type="checkbox" class="toggle" />
|
<input id="passwordProtected" [(ngModel)]="passwordProtected" type="checkbox" class="toggle" />
|
||||||
<span class="ml-2 text-gray-700">Protect download with password</span>
|
<label for="passwordProtected" class="ml-2 text-gray-700">Protect download with password</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Upload Button -->
|
<!-- Upload Button -->
|
||||||
<button class="btn btn-primary w-full max-w-xs">Upload Files</button>
|
<button class="btn btn-primary w-full max-w-xs" (click)="startUpload()">Upload File</button>
|
||||||
|
<p class="text-gray-600 text-center mt-6">Drag and drop files, or click to select the files you want to share.</p>
|
||||||
|
</div>
|
||||||
|
<!-- Upload Progress -->
|
||||||
|
<div *ngIf="uploadStarted && fileToUpload && !uploadFinished" class="flex flex-col items-center justify-center animate-in fade-in duration-500">
|
||||||
|
<div class="radial-progress" [style.--value]="uploadProgress" style="--size:12rem; --thickness: 2rem;" role="progressbar">{{ uploadProgress | number:'1.0-0' }}%</div>
|
||||||
|
<p class="text-gray-700 mt-4">Uploading {{ fileToUpload.name }}...</p>
|
||||||
|
</div>
|
||||||
|
<!-- Upload Finished -->
|
||||||
|
<div *ngIf="uploadFinished" class="flex flex-col items-center justify-center animate-in fade-in duration-1000">
|
||||||
|
<img class="w-16 mb-3" src="./assets/circle-check-solid.svg">
|
||||||
|
<p class="text-gray-700 mb-3">File uploaded successfully!</p>
|
||||||
|
<button class="btn btn-primary w-full max-w-xs">Upload Another File</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-600 text-center mt-6">Drag and drop files, or click to select the files you want to share.</p>
|
</div>
|
||||||
|
<!-- Invisible SVGs to prevent lazy loading -->
|
||||||
|
<div class="invisible h-0 w-0">
|
||||||
|
<img src="./assets/file-solid.svg">
|
||||||
|
<img src="./assets/circle-check-solid.svg">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,169 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||||
|
import {DecimalPipe, NgIf} from "@angular/common";
|
||||||
|
import {FormatFileSizePipePipe} from "../format-file-size-pipe.pipe";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import axios, {AxiosProgressEvent} from "axios";
|
||||||
|
import { DevelopmentStore } from '../../store/DevelopmentStore';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-upload',
|
selector: 'app-upload',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [
|
||||||
|
NgIf,
|
||||||
|
FormatFileSizePipePipe,
|
||||||
|
FormsModule,
|
||||||
|
DecimalPipe
|
||||||
|
],
|
||||||
templateUrl: './upload.component.html',
|
templateUrl: './upload.component.html',
|
||||||
styleUrl: './upload.component.scss'
|
styleUrl: './upload.component.scss'
|
||||||
})
|
})
|
||||||
export class UploadComponent {
|
export class UploadComponent {
|
||||||
|
@ViewChild('fileInput') fileInput!: ElementRef;
|
||||||
|
|
||||||
|
fileToUpload: File | null = null;
|
||||||
|
|
||||||
|
shortStorage: boolean = false;
|
||||||
|
singleDownload: boolean = false;
|
||||||
|
fileDescription: string = '';
|
||||||
|
passwordProtected: boolean = false;
|
||||||
|
password: string = 'nigga123';
|
||||||
|
|
||||||
|
uploadStarted: boolean = false;
|
||||||
|
uploadProgress = 0; // Real progress
|
||||||
|
targetUploadProgress = 0; // New target progress to reach
|
||||||
|
uploadFinished = false;
|
||||||
|
uploadSpeedBps: number = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private developmentStore: DevelopmentStore) {
|
||||||
|
this.uploadSpeedTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
'Access-Control-Allow-Origin': '*', // Allow CORS
|
||||||
|
},
|
||||||
|
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
this.uploadSpeedBps = bitsUploaded / duration;
|
||||||
|
console.log("Speed test ping finished.");
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during upload test:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
frontend/src/assets/circle-check-solid.svg
Normal file
1
frontend/src/assets/circle-check-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#2b3440" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>
|
After Width: | Height: | Size: 440 B |
1
frontend/src/assets/file-solid.svg
Normal file
1
frontend/src/assets/file-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#2b3440" d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128z"/></svg>
|
After Width: | Height: | Size: 380 B |
29
frontend/src/store/DevelopmentStore.ts
Normal file
29
frontend/src/store/DevelopmentStore.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 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 DevelopmentStore {
|
||||||
|
private isDevelopment: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.isDevelopment = location.port === '4200';
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsDevelopment(): boolean {
|
||||||
|
return this.isDevelopment;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseUrl(): string {
|
||||||
|
if (this.isDevelopment) {
|
||||||
|
return 'http://localhost:80/';
|
||||||
|
} else {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/de/w665/sharepulse/config/CorsConfig.java
Normal file
22
src/main/java/de/w665/sharepulse/config/CorsConfig.java
Normal file
@ -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/**")
|
||||||
|
.allowedOrigins("*")
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*");
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,16 @@
|
|||||||
package de.w665.sharepulse.rest.mappings;
|
package de.w665.sharepulse.rest.mappings;
|
||||||
|
|
||||||
import de.w665.sharepulse.rest.ApiRestController;
|
import de.w665.sharepulse.rest.ApiRestController;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
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.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@ -18,7 +22,7 @@ public class SpeedTest extends ApiRestController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/speed-test")
|
@GetMapping("/speed-test")
|
||||||
public void speedTest(HttpServletResponse response) {
|
public void speedTest(HttpServletResponse response) {
|
||||||
byte[] dummyData = new byte[1024 * 1024];
|
byte[] dummyData = new byte[1024 * 1024 / 2];
|
||||||
response.setContentType("application/octet-stream");
|
response.setContentType("application/octet-stream");
|
||||||
response.setContentLength(dummyData.length);
|
response.setContentLength(dummyData.length);
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
@ -30,4 +34,23 @@ public class SpeedTest extends ApiRestController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload-speed-test")
|
||||||
|
public void uploadSpeedTest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
final int maxBytes = 1024 * 1024 / 2; // Limit to 0.5MB
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ public class Upload extends ApiRestController {
|
|||||||
@RequestParam(value = "password", required = false) String password,
|
@RequestParam(value = "password", required = false) String password,
|
||||||
@RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload,
|
@RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload,
|
||||||
@RequestParam(value = "fileDescription", required = false) String fileDescription) {
|
@RequestParam(value = "fileDescription", required = false) String fileDescription) {
|
||||||
|
|
||||||
|
// TODO: Handle shortStorage0
|
||||||
|
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
log.debug("User tried to upload an empty file. IP: " + request.getRemoteAddr());
|
log.debug("User tried to upload an empty file. IP: " + request.getRemoteAddr());
|
||||||
return new ResponseEntity<>("Please select a file to upload.", HttpStatus.NOT_ACCEPTABLE);
|
return new ResponseEntity<>("Please select a file to upload.", HttpStatus.NOT_ACCEPTABLE);
|
||||||
|
@ -22,3 +22,6 @@ rethinkdb.database=sharepulse
|
|||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
server.port=80
|
server.port=80
|
||||||
spring.application.name=sharepulse
|
spring.application.name=sharepulse
|
||||||
|
|
||||||
|
# Spring profiles (Options: development, production) (Controls cors)
|
||||||
|
spring.profiles.active=development
|
Loading…
x
Reference in New Issue
Block a user