Archived
Private
Public Access
1
0
This commit is contained in:
2023-04-30 22:26:48 +02:00
parent 4f6c0a00be
commit 0d83e9d75e
10 changed files with 43 additions and 106 deletions

View File

@@ -4,6 +4,7 @@ using System.Text;
using Aspose.Email.Clients.Imap; using Aspose.Email.Clients.Imap;
using BetterIServ.Backend.Entities; using BetterIServ.Backend.Entities;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace BetterIServ.Backend.Controllers; namespace BetterIServ.Backend.Controllers;
@@ -92,8 +93,11 @@ public class MailController : ControllerBase {
return new SingleResult<ImapFolderInfo[]> { Value = results.ToArray() }; return new SingleResult<ImapFolderInfo[]> { Value = results.ToArray() };
} }
[HttpPost("download/{id}/{attachment}")] [HttpGet("download/{id}/{attachment}")]
public async Task<FileStreamResult> DownloadAttachment([FromBody] Credentials credentials, [FromRoute] int id, [FromRoute] string attachment) { public async Task<FileStreamResult> DownloadAttachment([FromQuery] string credentialString, [FromRoute] int id, [FromRoute] string attachment) {
var credentials = JsonConvert.DeserializeObject<Credentials>(credentialString);
if (credentials == null) return new FileStreamResult(Stream.Null, "");
using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password); using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password);
var data = await client.FetchAttachmentAsync(id, attachment); var data = await client.FetchAttachmentAsync(id, attachment);

View File

@@ -1,6 +1,7 @@
using System.Net; using System.Net;
using BetterIServ.Backend.Entities; using BetterIServ.Backend.Entities;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WebDav; using WebDav;
namespace BetterIServ.Backend.Controllers; namespace BetterIServ.Backend.Controllers;
@@ -40,8 +41,11 @@ public class WebDavController : ControllerBase {
return contents.OrderBy(item => item.Type).ToArray(); return contents.OrderBy(item => item.Type).ToArray();
} }
[HttpPost("download")] [HttpGet("download")]
public async Task<FileStreamResult> DonwloadFile([FromBody] Credentials credentials, [FromQuery] string url) { public async Task<FileStreamResult> DonwloadFile([FromQuery] string url, [FromQuery] string credentialString) {
var credentials = JsonConvert.DeserializeObject<Credentials>(credentialString);
if (credentials == null) return new FileStreamResult(Stream.Null, "");
var baseAddress = new Uri($"https://webdav.{credentials.Domain}"); var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
using var client = new WebDavClient(new WebDavClientParams { using var client = new WebDavClient(new WebDavClientParams {
BaseAddress = baseAddress, BaseAddress = baseAddress,

View File

@@ -14,7 +14,6 @@
"@angular/platform-browser": "^15.0.0", "@angular/platform-browser": "^15.0.0",
"@angular/platform-browser-dynamic": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0",
"@angular/router": "^15.0.0", "@angular/router": "^15.0.0",
"@awesome-cordova-plugins/file": "^6.3.0",
"@capacitor/android": "4.7.3", "@capacitor/android": "4.7.3",
"@capacitor/app": "4.1.1", "@capacitor/app": "4.1.1",
"@capacitor/core": "4.7.3", "@capacitor/core": "4.7.3",
@@ -22,7 +21,6 @@
"@capacitor/keyboard": "4.1.1", "@capacitor/keyboard": "4.1.1",
"@capacitor/status-bar": "4.1.1", "@capacitor/status-bar": "4.1.1",
"@ionic/angular": "^7.0.0", "@ionic/angular": "^7.0.0",
"file-saver": "^2.0.5",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"marked": "^4.3.0", "marked": "^4.3.0",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
@@ -42,7 +40,6 @@
"@angular/language-service": "^15.0.0", "@angular/language-service": "^15.0.0",
"@capacitor/cli": "4.7.3", "@capacitor/cli": "4.7.3",
"@ionic/angular-toolkit": "^9.0.0", "@ionic/angular-toolkit": "^9.0.0",
"@types/file-saver": "^2.0.5",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
@@ -698,30 +695,6 @@
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==",
"dev": true "dev": true
}, },
"node_modules/@awesome-cordova-plugins/core": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-6.3.0.tgz",
"integrity": "sha512-MkcWO8akZLHa2RSJEPf76Y3P9wPqh5oXE8YCzn2vnYYeNyYWYnka2pHFsgUdbXJNiS+YeveUzvw+Isweg+wynA==",
"peer": true,
"dependencies": {
"@types/cordova": "latest"
},
"peerDependencies": {
"rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0"
}
},
"node_modules/@awesome-cordova-plugins/file": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/file/-/file-6.3.0.tgz",
"integrity": "sha512-w2S/X/pr0Edl8+O/ndIIlnikwkD1XEMM/8TQFp/AI1riqJFyPtYNgnU54iRjagIRJE+GRyydaUnQXp5DVn9Htg==",
"dependencies": {
"@types/cordova": "latest"
},
"peerDependencies": {
"@awesome-cordova-plugins/core": "^6.0.1",
"rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.21.4", "version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
@@ -3636,11 +3609,6 @@
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
"dev": true "dev": true
}, },
"node_modules/@types/cordova": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.0.tgz",
"integrity": "sha512-AtBm1IAqqXsXszJe6XxuA2iXLhraNCj25p/FHRyikPeW0Z3YfgM6qzWb+VJglJTmZc5lqRNy84cYM/sQI5v6Vw=="
},
"node_modules/@types/cors": { "node_modules/@types/cors": {
"version": "2.8.13", "version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
@@ -3699,12 +3667,6 @@
"@types/range-parser": "*" "@types/range-parser": "*"
} }
}, },
"node_modules/@types/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==",
"dev": true
},
"node_modules/@types/fs-extra": { "node_modules/@types/fs-extra": {
"version": "8.1.2", "version": "8.1.2",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
@@ -7361,11 +7323,6 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",

View File

@@ -20,7 +20,6 @@
"@angular/platform-browser": "^15.0.0", "@angular/platform-browser": "^15.0.0",
"@angular/platform-browser-dynamic": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0",
"@angular/router": "^15.0.0", "@angular/router": "^15.0.0",
"@awesome-cordova-plugins/file": "^6.3.0",
"@capacitor/android": "4.7.3", "@capacitor/android": "4.7.3",
"@capacitor/app": "4.1.1", "@capacitor/app": "4.1.1",
"@capacitor/core": "4.7.3", "@capacitor/core": "4.7.3",
@@ -28,7 +27,6 @@
"@capacitor/keyboard": "4.1.1", "@capacitor/keyboard": "4.1.1",
"@capacitor/status-bar": "4.1.1", "@capacitor/status-bar": "4.1.1",
"@ionic/angular": "^7.0.0", "@ionic/angular": "^7.0.0",
"file-saver": "^2.0.5",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"marked": "^4.3.0", "marked": "^4.3.0",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
@@ -48,7 +46,6 @@
"@angular/language-service": "^15.0.0", "@angular/language-service": "^15.0.0",
"@capacitor/cli": "4.7.3", "@capacitor/cli": "4.7.3",
"@ionic/angular-toolkit": "^9.0.0", "@ionic/angular-toolkit": "^9.0.0",
"@types/file-saver": "^2.0.5",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",

View File

@@ -42,8 +42,9 @@ export class MailService {
await firstValueFrom(this.client.post(this.iserv.backend + "/mail/send", data)); await firstValueFrom(this.client.post(this.iserv.backend + "/mail/send", data));
} }
public downloadAttachment(mailId: number, attachment: string): Observable<HttpEvent<Blob>> { public downloadAttachment(mailId: number, attachment: string): string {
return this.client.post(this.iserv.backend + `/mail/download/${mailId}/${attachment}`, this.iserv.userdata, {responseType: "blob", reportProgress: true, observe: "events"}); //return this.client.post(this.iserv.backend + `/mail/download/${mailId}/${attachment}`, this.iserv.userdata, {responseType: "blob", reportProgress: true, observe: "events"});
return this.iserv.backend + `/mail/download/${mailId}/${attachment}?credentialString=${JSON.stringify(this.iserv.userdata)}`;
} }
} }

View File

@@ -23,8 +23,9 @@ export class WebdavService {
return contents; return contents;
} }
public downloadFile(url: string): Observable<HttpEvent<Blob>> { public downloadFile(url: string): string {
return this.client.post(this.iserv.backend + "/webdav/download?url=" + url, this.iserv.userdata, {responseType: "blob", reportProgress: true, observe: "events"}); //return this.client.get(this.iserv.backend + `/webdav/download?url=${url}&credentialString=${JSON.stringify(this.iserv.userdata)}`, {responseType: "blob", reportProgress: true, observe: "events"});
return this.iserv.backend + `/webdav/download?url=${url}&credentialString=${JSON.stringify(this.iserv.userdata)}`;
} }
public async delete(url: string) { public async delete(url: string) {

View File

@@ -4,9 +4,7 @@ import {FormsModule} from '@angular/forms';
import {ActionSheetController, AlertController, IonicModule, Platform, ToastController} from '@ionic/angular'; import {ActionSheetController, AlertController, IonicModule, Platform, ToastController} from '@ionic/angular';
import {WebdavService} from "../../api/webdav.service"; import {WebdavService} from "../../api/webdav.service";
import {DirectoryContent} from "../../entities/directoryContent"; import {DirectoryContent} from "../../entities/directoryContent";
import {File} from "@awesome-cordova-plugins/file/ngx"; import {HttpEventType} from "@angular/common/http";
import {saveAs} from "file-saver";
import {HttpDownloadProgressEvent, HttpEventType} from "@angular/common/http";
@Component({ @Component({
selector: 'app-files', selector: 'app-files',
@@ -47,29 +45,8 @@ export class FilesPage implements OnInit {
if (item.type == "dir") { if (item.type == "dir") {
await this.switchDirectory(item.url); await this.switchDirectory(item.url);
}else { }else {
this.webdav.downloadFile(item.url).subscribe(async event => { const download = this.webdav.downloadFile(item.url);
if (event.type == HttpEventType.DownloadProgress) { window.open(download, "_blank");
const e = event as HttpDownloadProgressEvent;
this.progress = e.loaded / e.total * 100;
}
if (event.type == HttpEventType.Response) {
const blob = event.body;
const file = new File();
if (this.platform.is('desktop')) {
saveAs(blob, item.name);
this.progress = -1;
return;
}
const downloadPath = (
this.platform.is('android')
) ? file.externalDataDirectory : file.documentsDirectory;
await file.writeFile(downloadPath, item.name, blob, {replace: true});
this.progress = -1;
}
})
} }
} }

View File

@@ -5,9 +5,6 @@ import {IonicModule, IonModal, Platform, ToastController} from '@ionic/angular';
import {MailService} from "../../api/mail.service"; import {MailService} from "../../api/mail.service";
import {MailContent, MailFolder} from "../../entities/mail"; import {MailContent, MailFolder} from "../../entities/mail";
import {marked} from "marked"; import {marked} from "marked";
import {HttpEventType} from "@angular/common/http";
import {File} from "@awesome-cordova-plugins/file/ngx";
import {saveAs} from "file-saver";
import {MailComponent} from "../../components/mail/mail.component"; import {MailComponent} from "../../components/mail/mail.component";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
@@ -83,25 +80,8 @@ export class MailsPage implements OnInit {
} }
public async downloadAttachment(attachment: string, mailId: number) { public async downloadAttachment(attachment: string, mailId: number) {
this.showLoading = true; const download = this.mail.downloadAttachment(mailId, attachment);
this.mail.downloadAttachment(mailId, attachment).subscribe(async event => { window.open(download, "_blank");
if (event.type == HttpEventType.Response) {
const blob = event.body;
const file = new File();
if (this.platform.is('desktop')) {
saveAs(blob, attachment);
this.showLoading = false;
return;
}
const downloadPath = (
this.platform.is('android')
) ? file.externalDataDirectory : file.documentsDirectory;
await file.writeFile(downloadPath, attachment, blob, {replace: true});
this.showLoading = false;
}
})
} }
public async sendMail(receiver: string, subject: string, message: string, modal: IonModal) { public async sendMail(receiver: string, subject: string, message: string, modal: IonModal) {

View File

@@ -54,7 +54,7 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-button (click)="tableModal.dismiss(null, 'cancel')">Abbrechen</ion-button> <ion-button (click)="tableModal.dismiss(null, 'cancel')">Abbrechen</ion-button>
</ion-buttons> </ion-buttons>
<ion-title>{{currentLesson?.lesson.course || "Neue Stunde"}}</ion-title> <ion-title>{{currentLesson?.lesson?.course || "Neue Stunde"}}</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button (click)="tableModal.dismiss({lesson: {course: course.value, room: room.value, week: week.value}, day: day.value, time: lesson.value}, 'confirm')" [strong]="true">Fertig</ion-button> <ion-button (click)="tableModal.dismiss({lesson: {course: course.value, room: room.value, week: week.value}, day: day.value, time: lesson.value}, 'confirm')" [strong]="true">Fertig</ion-button>
</ion-buttons> </ion-buttons>
@@ -63,7 +63,7 @@
<ion-content class="ion-padding course-content"> <ion-content class="ion-padding course-content">
<ion-item> <ion-item>
<ion-label position="stacked">Kurs</ion-label> <ion-label position="stacked">Kurs</ion-label>
<ion-select aria-label="Kurs" interface="action-sheet" [value]="currentLesson?.lesson.course || courses[0].id" #course> <ion-select aria-label="Kurs" interface="action-sheet" [value]="currentLesson?.lesson?.course || courses[0].id" (ionChange)="room.value = getCommonCourseRoom(course.value)" #course>
<ion-select-option *ngFor="let course of courses" [value]="course.id"> <ion-select-option *ngFor="let course of courses" [value]="course.id">
{{course.name}} {{course.name}}
</ion-select-option> </ion-select-option>
@@ -71,7 +71,7 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label position="stacked">Wochentyp</ion-label> <ion-label position="stacked">Wochentyp</ion-label>
<ion-select aria-label="Wochentyp" interface="action-sheet" [value]="currentLesson?.lesson.week || 'all'" #week> <ion-select aria-label="Wochentyp" interface="action-sheet" [value]="currentLesson?.lesson?.week || 'all'" #week>
<ion-select-option value="all">Immer</ion-select-option> <ion-select-option value="all">Immer</ion-select-option>
<ion-select-option value="a">Woche A</ion-select-option> <ion-select-option value="a">Woche A</ion-select-option>
<ion-select-option value="b">Woche B</ion-select-option> <ion-select-option value="b">Woche B</ion-select-option>
@@ -79,7 +79,7 @@
</ion-item> </ion-item>
<ion-item> <ion-item>
<ion-label position="stacked">Raum</ion-label> <ion-label position="stacked">Raum</ion-label>
<ion-input aria-label="Raum" type="text" [value]="currentLesson?.lesson.room" #room/> <ion-input aria-label="Raum" type="text" [value]="currentLesson?.lesson?.room" #room/>
</ion-item> </ion-item>
<ion-item> <ion-item>

View File

@@ -79,6 +79,10 @@ export class SchedulePage implements OnInit {
} }
if (event.detail.role == "confirm") { if (event.detail.role == "confirm") {
if (this.currentLesson != undefined) {
delete this.timetable[this.currentLesson.day][this.currentLesson.time];
}
const data = event.detail.data as {lesson: Lesson, day: string, time: number}; const data = event.detail.data as {lesson: Lesson, day: string, time: number};
this.timetable[data.day][data.time] = data.lesson; this.timetable[data.day][data.time] = data.lesson;
} }
@@ -87,4 +91,16 @@ export class SchedulePage implements OnInit {
location.reload(); location.reload();
} }
public getCommonCourseRoom(course: string): string {
if (course == undefined) return "";
const rooms: string[] = [];
for (let day of ['mon', 'tue', 'wed', 'thu', 'fri']) {
const courseTime = this.timetable[day].filter(lesson => lesson != undefined && lesson.course == course) as Lesson[];
rooms.push(...courseTime.map(time => time.room));
}
return rooms[rooms.length - 1];
}
} }