Merge branch 'main' of https://git.walzen665.de/Walzen665/fileshare-service
This commit is contained in:
commit
91a3507710
@ -1,21 +1,40 @@
|
||||
<p>credits works!</p>
|
||||
<div class="flex flex-col items-center justify-center min-h-fit p-4 lg:p-28">
|
||||
<div class="flex items-center justify-center mb-3">
|
||||
<a href="/" class="flex items-center justify-center">
|
||||
<img src="./assets/cloud-solid.svg" class="mr-3 w-24 animate-bounce ease-out" alt="SharePulse Logo" />
|
||||
<span class="self-center text-4xl font-semibold whitespace-nowrap">SharePulse</span>
|
||||
</a>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-10 text-center">
|
||||
Upload a file. Share the URL/link. Download the file. Simple as that.
|
||||
</h2>
|
||||
<div class="space-y-4 text-xl mb-20">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-4">
|
||||
<h3 class="font-semibold text-gray-800 sm:col-span-2 md:text-right">Developer:</h3>
|
||||
<p class="text-gray-800 sm:col-span-2 sm:text-left">Walzen665</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-4">
|
||||
<h3 class="font-semibold text-gray-800 sm:col-span-2 md:text-right">Version:</h3>
|
||||
<p class="text-gray-800 sm:col-span-2 sm:text-left">1.0.0</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-4">
|
||||
<h3 class="font-semibold text-gray-800 sm:col-span-2 md:text-right">API-Status:</h3>
|
||||
<p class="sm:col-span-2 sm:text-left text-green-600">online</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-4 items-center gap-4">
|
||||
<h3 class=" font-semibold text-gray-800 sm:col-span-2 md:text-right">Libraries:</h3>
|
||||
<p class="text-gray-800 sm:col-span-2 sm:text-left">
|
||||
<a href="https://fontawesome.com/" class="text-indigo-600 hover:text-indigo-800">Fontawesome</a>,
|
||||
<a href="https://tailwindcss.com/" class="text-indigo-600 hover:text-indigo-800">TailwindCSS</a>,
|
||||
<a href="https://axios-http.com/" class="text-indigo-600 hover:text-indigo-800">AXIOS http</a>,
|
||||
<a href="https://www.npmjs.com/package/tailwindcss-animate" class="text-indigo-600 hover:text-indigo-800">TailwindCSS Animations</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-4">
|
||||
Used media
|
||||
</h2>
|
||||
<ul class="list-disc pl-5 space-y-2">
|
||||
<li>
|
||||
<p class="font-medium text-gray-700">Icons used:</p>
|
||||
<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>
|
||||
</ul>
|
||||
<p>
|
||||
<span class="text-indigo-600 cursor-pointer" (click)="openPrivacyPolicyModal()">Privacy Policy</span> |
|
||||
<span class="text-indigo-600 cursor-pointer" (click)="openTermsOfUseModal()">Terms of Use</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
@ -24,7 +24,7 @@ export class DownloadComponent {
|
||||
@ViewChild('download_not_possible') download_not_possible: ElementRef<HTMLDialogElement> | undefined;
|
||||
@ViewChild('file_not_found_modal') file_not_found_modal: ElementRef<HTMLDialogElement> | undefined;
|
||||
|
||||
inputFileId: string = "2402183";
|
||||
inputFileId: string = "";
|
||||
fileId: string = "";
|
||||
filePassword: string = "";
|
||||
fileName: string = "";
|
||||
@ -34,14 +34,28 @@ export class DownloadComponent {
|
||||
fileDownloadFinished: boolean = false;
|
||||
waitingForPassword: boolean = false;
|
||||
downloadProgress: number = 0;
|
||||
targetUploadProgress: number = 0;
|
||||
downloadDuration: string = "";
|
||||
passwordWrong: boolean = false;
|
||||
|
||||
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 +84,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");
|
||||
}
|
||||
@ -137,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();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -183,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;
|
||||
|
@ -25,72 +25,11 @@
|
||||
<p class="text-gray-600 text-center mb-4">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
|
||||
<span class="text-indigo-600 cursor-pointer" (click)="openPrivacyPolicy()">Privacy Policy</span> note.
|
||||
By using SharePulse, you agree to the <span class="text-indigo-600 cursor-pointer" (click)="openTermsOfUse()">Terms of Use</span>.</p>
|
||||
<span class="text-indigo-600 cursor-pointer" (click)="openPrivacyPolicyModal()">Privacy Policy</span> note.
|
||||
By using SharePulse, you agree to the <span class="text-indigo-600 cursor-pointer" (click)="openTermsOfUseModal()">Terms of Use</span>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog #privacy_policy_modal class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg">SharePulse™ Privacy Policy</h3>
|
||||
<div>
|
||||
<div class="mx-auto my-6">
|
||||
<ol class="list-decimal">
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Uploaded files are stored on a secure server.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Files are stored for a maximum of 24 hours.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse tracks your IP address when uploading files.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse may track your IP address when downloading files.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse does not track you when accessing the website.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Personal information collected by Sharepulse is used solely for data protection and is not processed.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse will not sell, rent, or share personal information with third parties without user consent, except as required by law.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users have the right to request the deletion of their data from Sharepulse's servers at any time.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse does not store any cookies on your device.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Operators do not view or manipulate uploaded files.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users are encouraged to review the privacy policy periodically for any changes or updates.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn">Okay</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog #terms_of_use_modal class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg">SharePulse™ Terms of Use</h3>
|
||||
<div>
|
||||
<div class="mx-auto my-6">
|
||||
<ol class="list-decimal">
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users must not upload files that contain malware, illegal content, or violate copyright laws.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse is not responsible for the content of the files shared through its service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">By using Sharepulse, users agree not to attempt to breach the security of the service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Sharepulse reserves the right to remove any files or suspend users that violate these terms without notice.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">The service is provided "as is" without warranty of any kind, express or implied.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users agree to indemnify and hold harmless Sharepulse from any claims resulting from the use of the service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">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.</li>
|
||||
|
||||
</ol>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn">Okay</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- Invisible SVGs to prevent lazy loading -->
|
||||
<div class="invisible h-0 w-0">
|
||||
<img src="./assets/cloud-arrow-up-solid.svg">
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {LegalService} from "../../service/legalService";
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@ -13,15 +14,14 @@ import {FormsModule} from "@angular/forms";
|
||||
styleUrl: './home.component.scss'
|
||||
})
|
||||
export class HomeComponent {
|
||||
@ViewChild('privacy_policy_modal') privacy_policy_modal: ElementRef<HTMLDialogElement> | undefined;
|
||||
@ViewChild('terms_of_use_modal') terms_of_use_modal: ElementRef<HTMLDialogElement> | undefined;
|
||||
|
||||
|
||||
openPrivacyPolicy() {
|
||||
this.privacy_policy_modal?.nativeElement.showModal();
|
||||
constructor(private legalService: LegalService) {
|
||||
}
|
||||
|
||||
openTermsOfUse() {
|
||||
this.terms_of_use_modal?.nativeElement.showModal();
|
||||
openPrivacyPolicyModal() {
|
||||
this.legalService.openPrivacyPolicy();
|
||||
}
|
||||
|
||||
openTermsOfUseModal() {
|
||||
this.legalService.openTermsOfUse();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<span class="self-center text-xl font-semibold whitespace-nowrap">SharePulse</span>
|
||||
</a>
|
||||
<div class="flex items-center lg:order-2">
|
||||
<a routerLink="/credits" class="text-gray-800 hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2">Administration</a>
|
||||
<a routerLink="/credits" class="text-gray-800 hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2"><!--Administration-->About</a>
|
||||
<button (click)="toggleMenu()" type="button" class="inline-flex items-center p-2 ml-1 text-sm text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 " aria-controls="mobile-menu-2" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
|
||||
@ -26,3 +26,65 @@
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<dialog #privacy_policy_modal class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg">SharePulse™ Privacy Policy</h3>
|
||||
<div>
|
||||
<div class="mx-auto my-6">
|
||||
<ol class="list-decimal">
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Uploaded files are stored on a secure server.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Files are stored for a maximum of 24 hours.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse tracks your IP address when uploading files. This serves as a method of identification, allowing you to check the status of your upload.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse may track your IP address when downloading files.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse does not track you when accessing the website.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Personal information collected by SharePulse is used solely for data protection and is not processed.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse will not sell, rent, or share personal information with third parties without user consent, except as required by law.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users have the right to request the deletion of their data from SharePulse's servers at any time.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse does not store any cookies on your device.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Operators do not view or manipulate uploaded files.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users are encouraged to review the privacy policy periodically for any changes or updates.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn">Okay</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<dialog #terms_of_use_modal class="modal">
|
||||
<div class="modal-box w-11/12 max-w-5xl">
|
||||
<h3 class="font-bold text-lg">SharePulse™ Terms of Use</h3>
|
||||
<div>
|
||||
<div class="mx-auto my-6">
|
||||
<ol class="list-decimal">
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users must not upload files that contain malware, illegal content, or violate copyright laws.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse is not responsible for the content of the files shared through its service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">By using SharePulse, users agree not to attempt to breach the security of the service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">SharePulse reserves the right to remove any files or suspend users that violate these terms without notice.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">The service is provided "as is" without warranty of any kind, express or implied.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">Users agree to indemnify and hold harmless SharePulse from any claims resulting from the use of the service.</li>
|
||||
<li class="ml-4 mb-2 text-gray-800 font-semibold">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.</li>
|
||||
|
||||
</ol>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn">Okay</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
@ -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<HTMLDialogElement> | undefined;
|
||||
@ViewChild('terms_of_use_modal') terms_of_use_modal: ElementRef<HTMLDialogElement> | 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();
|
||||
}
|
||||
}
|
||||
|
@ -28,17 +28,18 @@
|
||||
<div class="w-full mb-10">
|
||||
<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="flex items-center">
|
||||
<input id="shortStorage" [(ngModel)]="shortStorage" type="checkbox" class="toggle" checked />
|
||||
<label for="shortStorage" class="ml-2 text-gray-700">Store for only one hour</label>
|
||||
<div class="flex items-center" >
|
||||
<input id="shortStorage" [(ngModel)]="shortStorage" type="checkbox" class="toggle" [disabled]="true"/>
|
||||
<label for="shortStorage" class="ml-2 text-gray-700 tooltip" data-tip="This feature will be available with the next update!">Store for only one hour</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="singleDownload" [(ngModel)]="singleDownload" type="checkbox" class="toggle" />
|
||||
<label for="singleDownload" class="ml-2 text-gray-700">Allow only one download</label>
|
||||
<label for="singleDownload" class="ml-2 text-gray-700 tooltip" data-tip="If enabled, the uploaded file can only be downloaded once.">Allow only one download</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input id="passwordProtected" [(ngModel)]="passwordProtected" type="checkbox" class="toggle" />
|
||||
<label for="passwordProtected" class="ml-2 text-gray-700">Protect download with password</label>
|
||||
<label for="passwordProtected" class="ml-2 text-gray-700 tooltip"
|
||||
data-tip="The password will be generated by the server and is displayed after the upload is finished.">Protect download with password</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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();
|
||||
|
23
frontend/src/service/legalService.ts
Normal file
23
frontend/src/service/legalService.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
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<string>();
|
||||
|
||||
openModal$ = this.openModalSource.asObservable();
|
||||
|
||||
openPrivacyPolicy() {
|
||||
this.openModalSource.next('privacyPolicy');
|
||||
}
|
||||
|
||||
openTermsOfUse() {
|
||||
this.openModalSource.next('termsOfUse');
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package de.w665.sharepulse.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@EnableScheduling
|
||||
@Configuration
|
||||
public class MvcConfig implements WebMvcConfigurer {
|
||||
|
||||
|
@ -35,22 +35,6 @@ public class RethinkDBService {
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
|
||||
//rethinkdb check if database exists
|
||||
|
||||
/*Result<Object> result = r.dbList().run(connection);
|
||||
List<String> databases = new ArrayList<>();
|
||||
|
||||
Object db = result.first();
|
||||
ArrayList<String> dbList = (ArrayList<String>) db;
|
||||
databases.addAll(dbList);
|
||||
|
||||
if (!databases.contains("sharepulse")) {
|
||||
r.dbCreate("sharepulse").run(connection);
|
||||
log.info("Database 'sharepulse' created successfully.");
|
||||
} else {
|
||||
log.info("Database 'sharepulse' already exists. No action needed.");
|
||||
}*/
|
||||
|
||||
// rethinkdb check if database exists
|
||||
try {
|
||||
r.dbCreate(config.getDatabase()).run(connection).stream();
|
||||
@ -77,7 +61,25 @@ public class RethinkDBService {
|
||||
r.db(config.getDatabase()).tableCreate("id_store").run(connection).stream();
|
||||
log.debug("Table 'id_store' created successfully.");
|
||||
} catch (ReqlOpFailedError e) {
|
||||
log.debug("Table 'id_store' already exists. No action needed.");
|
||||
log.debug("Table 'id_store' already exists.");
|
||||
if(autoResetOnStartup) {
|
||||
log.debug("Clearing content...");
|
||||
r.db(config.getDatabase()).table("id_store").delete().run(connection);
|
||||
log.debug("Table 'id_store' cleared successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
// rethinkdb check if table expired_file_uploads exists
|
||||
try {
|
||||
r.db(config.getDatabase()).tableCreate("expired_file_uploads").run(connection).stream();
|
||||
log.debug("Table 'expired_file_uploads' created successfully.");
|
||||
} catch (ReqlOpFailedError e) {
|
||||
log.debug("Table 'expired_file_uploads' already exists.");
|
||||
if(autoResetOnStartup) {
|
||||
log.debug("Clearing content...");
|
||||
r.db(config.getDatabase()).table("expired_file_uploads").delete().run(connection);
|
||||
log.debug("Table 'expired_file_uploads' cleared successfully.");
|
||||
}
|
||||
}
|
||||
log.info("Database ready for operation!");
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package de.w665.sharepulse.db.repo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
public class ExpiredFileUploadRepository {
|
||||
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public ExpiredFileUploadRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertExpiredFileUpload(FileUpload fileUpload) {
|
||||
String json = gson.toJson(fileUpload);
|
||||
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> map = gson.fromJson(json, type);
|
||||
|
||||
long uploadDateTimestamp = fileUpload.getUploadDate().getTime() / 1000;
|
||||
map.put("uploadDate", uploadDateTimestamp);
|
||||
|
||||
r.db("sharepulse").table("expired_file_uploads").insert(map).run(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package de.w665.sharepulse.db.repo;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import com.rethinkdb.net.Result;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class FileIdRepository {
|
||||
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public FileIdRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertFileId(String fileId) {
|
||||
r.db("sharepulse").table("id_store").insert(r.hashMap("fileId", fileId)).run(connection);
|
||||
}
|
||||
|
||||
public boolean fileIdExists(String fileId) {
|
||||
Result<Object> result = r.db("sharepulse").table("id_store")
|
||||
.filter(r.hashMap("fileId", fileId))
|
||||
.run(connection);
|
||||
|
||||
return result.hasNext();
|
||||
}
|
||||
}
|
@ -6,35 +6,28 @@ import com.google.gson.reflect.TypeToken;
|
||||
import com.rethinkdb.RethinkDB;
|
||||
import com.rethinkdb.net.Connection;
|
||||
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||
import de.w665.sharepulse.db.RethinkDBService;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class FileUploadRepository {
|
||||
|
||||
private final RethinkDBService rethinkDBService;
|
||||
private final RethinkDB r;
|
||||
private final Connection connection;
|
||||
private final Gson gson;
|
||||
|
||||
@Autowired
|
||||
public FileUploadRepository(RethinkDBService rethinkDBService, RethinkDBConnector connector) {
|
||||
this.rethinkDBService = rethinkDBService;
|
||||
public FileUploadRepository(RethinkDBConnector connector) {
|
||||
this.r = connector.getR();
|
||||
this.connection = connector.getConnection();
|
||||
|
||||
this.gson = new GsonBuilder()
|
||||
.setDateFormat("dd-MM-yyyy HH:mm:ss") // date field formatting
|
||||
.create();
|
||||
this.gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public void insertFileUpload(FileUpload fileUpload) {
|
||||
@ -43,6 +36,9 @@ public class FileUploadRepository {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> map = gson.fromJson(json, type);
|
||||
|
||||
long uploadDateTimestamp = fileUpload.getUploadDate().getTime() / 1000;
|
||||
map.put("uploadDate", uploadDateTimestamp);
|
||||
|
||||
r.db("sharepulse").table("file_uploads").insert(map).run(connection);
|
||||
}
|
||||
|
||||
@ -75,4 +71,34 @@ public class FileUploadRepository {
|
||||
.update(map)
|
||||
.run(connection);
|
||||
}
|
||||
|
||||
public void deleteFileUploadByFileId(String fileId) {
|
||||
r.db("sharepulse").table("file_uploads")
|
||||
.filter(r.hashMap("fileId", fileId))
|
||||
.delete()
|
||||
.run(connection);
|
||||
}
|
||||
|
||||
public List<FileUpload> getAllExpiredFileUploads() {
|
||||
|
||||
long timestamp = getOneMinuteAgoTimestamp();
|
||||
|
||||
List<FileUpload> olderFiles = r.db("sharepulse").table("file_uploads")
|
||||
.filter(row -> row.g("uploadDate").lt(timestamp))
|
||||
.run(connection, FileUpload.class)
|
||||
.toList();
|
||||
return olderFiles;
|
||||
}
|
||||
|
||||
private long get24HoursAgoTimestamp() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.HOUR, -24);
|
||||
return calendar.getTimeInMillis() / 1000;
|
||||
}
|
||||
|
||||
private long getOneMinuteAgoTimestamp() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MINUTE, -1); // Subtract 1 minute
|
||||
return calendar.getTimeInMillis() / 1000; // Convert milliseconds to seconds (Unix timestamp)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public class FileUpload {
|
||||
private String fileName;
|
||||
private long fileSize;
|
||||
private boolean singleDownload;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "CET")
|
||||
@JsonFormat(timezone = "ETC")
|
||||
private Date uploadDate;
|
||||
private String uploadedByIpAddress;
|
||||
private long downloadCount;
|
||||
|
@ -82,7 +82,7 @@ public class Download extends ApiRestController {
|
||||
Optional<FileUpload> optionalFileUpload = fileService.getFileUploadByFileId(fileId);
|
||||
FileUpload fileUpload = optionalFileUpload.orElse(null);
|
||||
if(optionalFileUpload.isEmpty()) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
boolean downloadable = !fileUpload.isSingleDownload() || fileUpload.getDownloadCount() == 0;
|
||||
|
@ -0,0 +1,36 @@
|
||||
package de.w665.sharepulse.service;
|
||||
|
||||
import de.w665.sharepulse.db.repo.FileUploadRepository;
|
||||
import de.w665.sharepulse.db.repo.ExpiredFileUploadRepository;
|
||||
import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileCleanupService {
|
||||
|
||||
private final FileUploadRepository fileUploadRepository;
|
||||
private final ExpiredFileUploadRepository expiredFileUploadRepository;
|
||||
private final FileService fileService;
|
||||
|
||||
public FileCleanupService(FileUploadRepository fileUploadRepository, ExpiredFileUploadRepository expiredFileUploadRepository, FileService fileService) {
|
||||
this.fileUploadRepository = fileUploadRepository;
|
||||
this.expiredFileUploadRepository = expiredFileUploadRepository;
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 * * * *")
|
||||
public void cleanup() {
|
||||
log.debug("Running cleanup...");
|
||||
List<FileUpload> expFileUploads = fileUploadRepository.getAllExpiredFileUploads();
|
||||
for (FileUpload fileUpload : expFileUploads) {
|
||||
fileService.deleteFile(fileUpload);
|
||||
expiredFileUploadRepository.insertExpiredFileUpload(fileUpload);
|
||||
log.debug("Moved file " + fileUpload.getFileId() + " to old_file_uploads table.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +1,51 @@
|
||||
package de.w665.sharepulse.service;
|
||||
|
||||
import de.w665.sharepulse.db.repo.FileIdRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class FileIdService {
|
||||
|
||||
private static int dailyCounter = 0;
|
||||
private static String lastDate = "";
|
||||
@Value("${sharepulse.fileid.length}")
|
||||
private int fileIdLength;
|
||||
|
||||
// Not safe to use
|
||||
public static synchronized String generateId() {
|
||||
String today = new SimpleDateFormat("yyMMdd").format(new Date());
|
||||
@Value("${sharepulse.fileid.charset}")
|
||||
private String CHARSET;
|
||||
|
||||
if (!today.equals(lastDate)) {
|
||||
dailyCounter = 0;
|
||||
lastDate = today;
|
||||
private final FileIdRepository fileIdRepository;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
@Autowired
|
||||
public FileIdService(FileIdRepository fileIdRepository) {
|
||||
this.fileIdRepository = fileIdRepository;
|
||||
}
|
||||
|
||||
private String generateId() {
|
||||
String uniqueId;
|
||||
do {
|
||||
uniqueId = generateRandomCode(fileIdLength);
|
||||
} while (fileIdRepository.fileIdExists(uniqueId));
|
||||
fileIdRepository.insertFileId(uniqueId);
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
private String generateRandomCode(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = RANDOM.nextInt(CHARSET.length());
|
||||
sb.append(CHARSET.charAt(index));
|
||||
}
|
||||
|
||||
String counterEncoded = Integer.toString(++dailyCounter, 36).toUpperCase();
|
||||
|
||||
return today + counterEncoded;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String generateNewUniqueId() {
|
||||
return generateId();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,17 +5,19 @@ import de.w665.sharepulse.model.FileUpload;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileSecurityService {
|
||||
|
||||
@Value("${sharepulse.filepassword-length}")
|
||||
@Value("${sharepulse.filepassword.length}")
|
||||
private int passwordLength;
|
||||
@Value("${sharepulse.filepassword-charset}")
|
||||
@Value("${sharepulse.filepassword.charset}")
|
||||
private String passwordCharset;
|
||||
private final Random random = new Random();
|
||||
private final Random random = new SecureRandom();
|
||||
|
||||
public boolean verifyDownloadPermission(FileUpload file, String password) throws NoDownloadPermissionException {
|
||||
|
||||
|
@ -96,7 +96,6 @@ public class FileService {
|
||||
fileUploadRepository.insertFileUpload(fileUpload);
|
||||
|
||||
|
||||
// TODO: rename file to fileID
|
||||
Path path = Paths.get(getTempDirPath() + File.separator + fileId);
|
||||
try {
|
||||
Files.write(path, file.getBytes());
|
||||
@ -122,6 +121,16 @@ public class FileService {
|
||||
return fileUpload.getDownloadCount();
|
||||
}
|
||||
|
||||
public void deleteFile(FileUpload fileUpload) {
|
||||
fileUploadRepository.deleteFileUploadByFileId(fileUpload.getFileId());
|
||||
Path path = Paths.get(getTempDirPath() + File.separator + fileUpload.getFileId());
|
||||
try {
|
||||
Files.delete(path);
|
||||
log.debug("File " + fileUpload.getFileId() + " deleted from disk.");
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting file " + fileUpload.getFileId() + " from disk: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getTempDirPath() {
|
||||
return System.getProperty("user.dir") + tempDirPath;
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Application config
|
||||
sharepulse.temp-filestore-path=/temp-filestore
|
||||
sharepulse.filepassword-length=6
|
||||
sharepulse.filepassword-charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
sharepulse.auto-reset-on-startup=false
|
||||
sharepulse.auto-reset-on-startup=true
|
||||
sharepulse.fileid.length=6
|
||||
sharepulse.fileid.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
sharepulse.filepassword.length=6
|
||||
sharepulse.filepassword.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
|
||||
# Static path
|
||||
spring.web.resources.static-locations=classpath:/static/browser/
|
||||
|
Loading…
x
Reference in New Issue
Block a user