From 7c05ab01d592abf695f5682cc742a628dd54863d Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 21 Dec 2017 12:46:19 +0100 Subject: [PATCH 001/124] Adds Course Media Manager --- .../course-edit/course-edit.component.html | 6 +++++ .../course/course-edit/course-edit.module.ts | 2 ++ .../course-mediamanager.component.html | 3 +++ .../course-mediamanager.component.scss | 0 .../course-mediamanager.component.spec.ts | 25 +++++++++++++++++++ .../course-mediamanager.component.ts | 17 +++++++++++++ 6 files changed, 53 insertions(+) create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.component.html b/app/webFrontend/src/app/course/course-edit/course-edit.component.html index 80dff802b..3f73bd653 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-edit.component.html @@ -72,6 +72,12 @@

Edit course

+ +
+ +
+
+
diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts index 7e7815301..dd0051c38 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts @@ -13,6 +13,7 @@ import {SharedModule} from '../../shared/shared.module'; import {CourseEditRoutingModule} from './course-edit-routing.module'; import {LectureModule} from '../../lecture/lecture.module'; import {UnitFormModule} from '../../unit/unit-form/unit-form.module'; +import { CourseMediamanagerComponent } from './course-mediamanager/course-mediamanager.component'; @NgModule({ imports: [ @@ -32,6 +33,7 @@ import {UnitFormModule} from '../../unit/unit-form/unit-form.module'; CourseUserListComponent, MembersComponent, TeachersComponent, + CourseMediamanagerComponent, ], exports: [ CourseEditComponent, diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html new file mode 100644 index 000000000..a26a06068 --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html @@ -0,0 +1,3 @@ +

+ course-mediamanager works! +

diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts new file mode 100644 index 000000000..e1055a931 --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CourseMediamanagerComponent } from './course-mediamanager.component'; + +describe('CourseMediamanagerComponent', () => { + let component: CourseMediamanagerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CourseMediamanagerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CourseMediamanagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts new file mode 100644 index 000000000..7ce57a0b1 --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts @@ -0,0 +1,17 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {ICourse} from '../../../../../../../shared/models/ICourse'; + +@Component({ + selector: 'app-course-mediamanager', + templateUrl: './course-mediamanager.component.html', + styleUrls: ['./course-mediamanager.component.scss'] +}) +export class CourseMediamanagerComponent implements OnInit { + @Input() course: ICourse; + + constructor() { } + + ngOnInit() { + } + +} From 78e1cb1785204226f0e6c76c958f41c3ba4b7485 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 4 Jan 2018 22:18:53 +0100 Subject: [PATCH 002/124] WIP: mediamanager layout --- .../course-mediamanager.component.html | 57 ++++++++++++++++++- .../course-mediamanager.component.scss | 16 ++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html index a26a06068..68fafa09d 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html @@ -1,3 +1,54 @@ -

- course-mediamanager works! -

+ +
+ +

Folders here

+
+ + +
+ + + + + +
+ + +
+ +
+ + +
+ + + + + +
+
+
+ + +
+ + + + + +
diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss index e69de29bb..22f99e15a 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss @@ -0,0 +1,16 @@ +.part { + display: grid; + + &.--left { + + } + + &.--center { + + } + + &.--right { + + } +} + From bb9d1ac0469eb8c3122228fb86032e603e5c04a4 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 4 Jan 2018 23:54:13 +0100 Subject: [PATCH 003/124] WIP: Now using flexbox --- .../course-mediamanager.component.html | 100 ++++++++++-------- .../course-mediamanager.component.scss | 37 ++++++- .../course-mediamanager.component.ts | 10 ++ 3 files changed, 98 insertions(+), 49 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html index 68fafa09d..710396145 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html @@ -1,54 +1,66 @@ - -
- -

Folders here

-
+
+ +
+ +

Folders here

+
- -
+ +
- - - -
+ +
- -
- -
+ +
+ +
- -
- - - - - + +
+ + + + + +
-
- -
- - - - - + +
+ + + + + +
+
+ Name + nana +
+
+ Name + nana +
+
+
diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss index 22f99e15a..cd285dbae 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss @@ -1,16 +1,43 @@ +.mm-container { + display: flex; + flex-direction: row; +} + .part { - display: grid; - &.--left { + &--left { + flex-grow: 10; + } + &--center { + flex-grow: 100; } - &.--center { + &--right { + flex-grow: 20; - } + .mat-toolbar-multiple-rows { + min-height: 50px; + + .mat-toolbar-row { + height: 50px; + padding: 0 10px; + } + } + + .mm-detail-wrapper { + padding: 1ex; + } - &.--right { + .mm-detail-group { + display: grid; + grid-template-columns: 30% 69%; + grid-gap: 1%; + } + .mm-detail-label { + font-weight: bold; + } } } diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts index 7ce57a0b1..f7f002bec 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts @@ -8,10 +8,20 @@ import {ICourse} from '../../../../../../../shared/models/ICourse'; }) export class CourseMediamanagerComponent implements OnInit { @Input() course: ICourse; + detailBarVisible: boolean; constructor() { } ngOnInit() { + this.detailBarVisible = true; + } + + isDetailBarVisible(): boolean { + return this.detailBarVisible; + } + + toggleDetailBarVisible(): void { + this.detailBarVisible = !this.detailBarVisible; } } From efd42b15a24dc6bc67f16d726594b7b3ace1c446 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 8 Jan 2018 22:17:04 +0100 Subject: [PATCH 004/124] Basic FE setup for mediamanager works --- .../course-mediamanager.component.html | 7 +-- .../course-mediamanager.component.scss | 49 ++++++++++++++++--- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html index 710396145..7701a96c0 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html @@ -1,4 +1,4 @@ -
+
@@ -6,7 +6,7 @@
-
+
-
+
+ Details
diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss index cd285dbae..9eec46ca9 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss @@ -1,20 +1,56 @@ -.mm-container { - display: flex; - flex-direction: row; +$width__left: 15%; +$width__right: 20%; +$width__right--collapsed: 4%; +$width__center: 100% - $width__left - $width__right; +$width__center--ext: 100% - $width__left - $width__right--collapsed; + +.mm-container {} + +.debug { + .part { + &--left { + background-color: red; + } + &--center { + background: green; + } + &--right { + background: blue; + } + } } .part { + margin: 0; + padding: 0 1ex; + float: left; &--left { - flex-grow: 10; + width: $width__left; + padding-left: 0; } &--center { - flex-grow: 100; + width: $width__center; + transition: width 300ms ease; + + &.hide__right { + width: $width__center--ext; + } } &--right { - flex-grow: 20; + width: $width__right; + padding-right: 0; + transition: width 300ms ease; + + &.hide__right { + width: $width__right--collapsed; + + .mm-detail-wrapper { + display: none; + } + } .mat-toolbar-multiple-rows { min-height: 50px; @@ -40,4 +76,3 @@ } } } - From aed6c34a34c727041a2f1dc247ff8d259cdb1adc Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 8 Jan 2018 22:48:26 +0100 Subject: [PATCH 005/124] Basic FE setup for mediamanager works, the second. Now with flex --- .../course-mediamanager.component.html | 41 +++++------- .../course-mediamanager.component.scss | 63 +++++-------------- .../course-mediamanager.component.ts | 12 ++-- 3 files changed, 33 insertions(+), 83 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html index 7701a96c0..4d598148b 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html @@ -1,12 +1,22 @@
-
- -

Folders here

+
+ + + + Folder + + +
+ content +
-
+
- - -
- - - - Details - - -
-
- Name - nana -
-
- Name - nana -
-
-
diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss index 9eec46ca9..c629f364f 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss @@ -1,53 +1,24 @@ $width__left: 15%; -$width__right: 20%; -$width__right--collapsed: 4%; -$width__center: 100% - $width__left - $width__right; -$width__center--ext: 100% - $width__left - $width__right--collapsed; -.mm-container {} - -.debug { - .part { - &--left { - background-color: red; - } - &--center { - background: green; - } - &--right { - background: blue; - } - } +.mm-container { + display: flex; } .part { margin: 0; padding: 0 1ex; - float: left; &--left { width: $width__left; padding-left: 0; - } - - &--center { - width: $width__center; + min-width: 6em; transition: width 300ms ease; - &.hide__right { - width: $width__center--ext; - } - } + &.hide { + min-width: 4rem; + width: 4rem; - &--right { - width: $width__right; - padding-right: 0; - transition: width 300ms ease; - - &.hide__right { - width: $width__right--collapsed; - - .mm-detail-wrapper { + .folder-wrapper { display: none; } } @@ -59,20 +30,16 @@ $width__center--ext: 100% - $width__left - $width__right--collapsed; height: 50px; padding: 0 10px; } - } - .mm-detail-wrapper { - padding: 1ex; - } - - .mm-detail-group { - display: grid; - grid-template-columns: 30% 69%; - grid-gap: 1%; + .mat-icon-button { + cursor: pointer; + } } + } - .mm-detail-label { - font-weight: bold; - } + &--center { + transition: width 300ms ease; + flex-grow: 1; + padding-right: 0; } } diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts index f7f002bec..d374ce19c 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts @@ -8,20 +8,16 @@ import {ICourse} from '../../../../../../../shared/models/ICourse'; }) export class CourseMediamanagerComponent implements OnInit { @Input() course: ICourse; - detailBarVisible: boolean; + folderBarVisible: boolean; constructor() { } ngOnInit() { - this.detailBarVisible = true; + this.folderBarVisible = true; } - isDetailBarVisible(): boolean { - return this.detailBarVisible; - } - - toggleDetailBarVisible(): void { - this.detailBarVisible = !this.detailBarVisible; + toggleFolderBarVisibility(): void { + this.folderBarVisible = !this.folderBarVisible; } } From d139485e56fe666ee70cde16a521fe20b1bc597d Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 8 Jan 2018 22:57:40 +0100 Subject: [PATCH 006/124] Removes typo in free text unit editor --- .../free-text-unit-editor.dialog.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/unit/unit-form/free-text-unit-form/free-text-unit-editor/free-text-unit-editor-dialog/free-text-unit-editor.dialog.html b/app/webFrontend/src/app/unit/unit-form/free-text-unit-form/free-text-unit-editor/free-text-unit-editor-dialog/free-text-unit-editor.dialog.html index 406f10478..4bdb507cb 100644 --- a/app/webFrontend/src/app/unit/unit-form/free-text-unit-form/free-text-unit-editor/free-text-unit-editor-dialog/free-text-unit-editor.dialog.html +++ b/app/webFrontend/src/app/unit/unit-form/free-text-unit-form/free-text-unit-editor/free-text-unit-editor-dialog/free-text-unit-editor.dialog.html @@ -1,4 +1,4 @@ - + Fullscreen Mode From 6658b27f8fa9174d2e758e88087dae0d239dc0c7 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 8 Jan 2018 23:06:24 +0100 Subject: [PATCH 007/124] Adds rudimental Detail dialog --- .../course/course-edit/course-edit.module.ts | 2 ++ .../course-mediamanager-detail.dialog.html | 14 +++++++++++ .../course-mediamanager-detail.dialog.scss | 0 .../course-mediamanager-detail.dialog.spec.ts | 25 +++++++++++++++++++ .../course-mediamanager-detail.dialog.ts | 20 +++++++++++++++ .../course-mediamanager.component.ts | 23 ++++++++++++++++- 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.scss create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts create mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts index dd0051c38..e07dab65c 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts @@ -14,6 +14,7 @@ import {CourseEditRoutingModule} from './course-edit-routing.module'; import {LectureModule} from '../../lecture/lecture.module'; import {UnitFormModule} from '../../unit/unit-form/unit-form.module'; import { CourseMediamanagerComponent } from './course-mediamanager/course-mediamanager.component'; +import { CourseMediamanagerDetailDialog } from './course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog'; @NgModule({ imports: [ @@ -34,6 +35,7 @@ import { CourseMediamanagerComponent } from './course-mediamanager/course-mediam MembersComponent, TeachersComponent, CourseMediamanagerComponent, + CourseMediamanagerDetailDialog, ], exports: [ CourseEditComponent, diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html new file mode 100644 index 000000000..8ab62155a --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html @@ -0,0 +1,14 @@ + + + File Detail + + + + +
+

+ Content +

+
diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.scss b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.scss new file mode 100644 index 000000000..e69de29bb diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts new file mode 100644 index 000000000..25f8a3026 --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CourseMediamanagerDetail.DialogComponent } from './course-mediamanager-detail-dialog.component'; + +describe('CourseMediamanagerDetail.DialogComponent', () => { + let component: CourseMediamanagerDetail.DialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CourseMediamanagerDetail.DialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CourseMediamanagerDetail.DialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts new file mode 100644 index 000000000..a75ad67db --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts @@ -0,0 +1,20 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; + +@Component({ + selector: 'app-course-mediamanager-detail.dialog', + templateUrl: './course-mediamanager-detail.dialog.html', + styleUrls: ['./course-mediamanager-detail.dialog.scss'] +}) +export class CourseMediamanagerDetailDialog implements OnInit { + + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any) { } + + ngOnInit() { + } + + onCloseClick(): any { + return this.dialogRef.close(); + } +} diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts index d374ce19c..a76f89084 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts @@ -1,5 +1,7 @@ import {Component, Input, OnInit} from '@angular/core'; import {ICourse} from '../../../../../../../shared/models/ICourse'; +import {CourseMediamanagerDetailDialog} from './course-mediamanager-detail-dialog/course-mediamanager-detail.dialog'; +import {MatDialog} from '@angular/material'; @Component({ selector: 'app-course-mediamanager', @@ -10,7 +12,8 @@ export class CourseMediamanagerComponent implements OnInit { @Input() course: ICourse; folderBarVisible: boolean; - constructor() { } + constructor(public dialog: MatDialog) { + } ngOnInit() { this.folderBarVisible = true; @@ -20,4 +23,22 @@ export class CourseMediamanagerComponent implements OnInit { this.folderBarVisible = !this.folderBarVisible; } + openDetailDialog(): void { + const dialogRef = this.dialog.open(CourseMediamanagerDetailDialog, { + width: '80vw', + // height: '94vh', + maxWidth: '100vw', + maxHeight: '90vh', + data: { + markdown: this.course + } + }); + + // dialogRef.afterClosed().subscribe(result => { + // if (typeof result !== 'undefined') { + // this.model.markdown = result; + // } + // }); + } + } From ff0771d0277122fde80ec13ca04582f30f726edc Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 13:30:57 +0100 Subject: [PATCH 008/124] create models --- api/src/models/mediaManager/Directory.ts | 35 ++++++++++++++++++++++++ api/src/models/mediaManager/File.ts | 31 +++++++++++++++++++++ shared/models/mediaManager/IDirectory.ts | 8 ++++++ shared/models/mediaManager/IFile.ts | 7 +++++ 4 files changed, 81 insertions(+) create mode 100644 api/src/models/mediaManager/Directory.ts create mode 100644 api/src/models/mediaManager/File.ts create mode 100644 shared/models/mediaManager/IDirectory.ts create mode 100644 shared/models/mediaManager/IFile.ts diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts new file mode 100644 index 000000000..20678b4e8 --- /dev/null +++ b/api/src/models/mediaManager/Directory.ts @@ -0,0 +1,35 @@ +import {IDirectory} from '../../../../shared/models/mediaManager/IDirectory'; +import * as mongoose from 'mongoose'; + +interface IDIrectoryModel extends IDirectory, mongoose.Document { + +} + +const directorySchema = new mongoose.Schema({ + _course: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Course' + }, + name: { + type: String, + required: true + }, + subDirectories: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'Directory' + } + ], + files: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'File' + } + ] +},{ + timestamps: true, +}); + +const Directory = mongoose.model('Directory', directorySchema); + +export {Directory, IDIrectoryModel}; diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts new file mode 100644 index 000000000..ec8cadb44 --- /dev/null +++ b/api/src/models/mediaManager/File.ts @@ -0,0 +1,31 @@ +import * as mongoose from 'mongoose'; +import {IFile} from '../../../../shared/models/mediaManager/IFile'; + +interface IFileModel extends IFile, mongoose.Document { + +} + +const fileSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + physicalPath: { + type: String, + required: true + }, + size: { + type: Number, + required: true + }, + mimeType: { + type: String, + required: true + }, +},{ + timestamps: true, +}); + +const File = mongoose.model('File', fileSchema); + +export {File, IFileModel}; diff --git a/shared/models/mediaManager/IDirectory.ts b/shared/models/mediaManager/IDirectory.ts new file mode 100644 index 000000000..fb00f14b5 --- /dev/null +++ b/shared/models/mediaManager/IDirectory.ts @@ -0,0 +1,8 @@ +import {IFile} from './IFile'; + +export interface IDirectory { + _id: any; + name: string; + subDirectories: IDirectory[]; + files: IFile[]; +} diff --git a/shared/models/mediaManager/IFile.ts b/shared/models/mediaManager/IFile.ts new file mode 100644 index 000000000..fb1ab915b --- /dev/null +++ b/shared/models/mediaManager/IFile.ts @@ -0,0 +1,7 @@ +export interface IFile { + _id: any; + name: string; + physicalPath: string; + size: number; + mimeType: string; +} From 34f7011fcc53d13efb5cb7961376fd238fbe34e6 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 13:31:15 +0100 Subject: [PATCH 009/124] add media directory --- api/src/models/Course.ts | 6 ++++++ shared/models/ICourse.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/api/src/models/Course.ts b/api/src/models/Course.ts index f2381dba2..22c6649cc 100644 --- a/api/src/models/Course.ts +++ b/api/src/models/Course.ts @@ -27,6 +27,12 @@ const courseSchema = new mongoose.Schema({ type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + media: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'IDirectory' + } + ], teachers: [ { type: mongoose.Schema.Types.ObjectId, diff --git a/shared/models/ICourse.ts b/shared/models/ICourse.ts index 366f96272..24b6e699c 100644 --- a/shared/models/ICourse.ts +++ b/shared/models/ICourse.ts @@ -1,6 +1,7 @@ import {IUser} from './IUser'; import {ILecture} from './ILecture'; import {IWhitelistUser} from './IWhitelistUser'; +import {IDirectory} from './mediaManager/IDirectory'; export const ENROLL_TYPE_WHITELIST = 'whitelist'; export const ENROLL_TYPE_FREE = 'free'; @@ -13,6 +14,7 @@ export interface ICourse { active: boolean; description: string; courseAdmin: IUser; + media: IDirectory; teachers: IUser[]; students: IUser[]; lectures: ILecture[]; From d523c7bb8bab0dee4969e949ccd62fef2cecbe4c Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 13:31:38 +0100 Subject: [PATCH 010/124] create media controller --- api/src/controllers/MediaController.ts | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 api/src/controllers/MediaController.ts diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts new file mode 100644 index 000000000..869f49d22 --- /dev/null +++ b/api/src/controllers/MediaController.ts @@ -0,0 +1,57 @@ +import {Authorized, Body, Delete, Get, JsonController, Param, Put} from 'routing-controllers'; +import {Directory} from '../models/mediaManager/Directory'; +import {File} from '../models/mediaManager/File'; +import {IDirectory} from '../../../shared/models/mediaManager/IDirectory'; +import {IFile} from '../../../shared/models/mediaManager/IFile'; + +@JsonController('/media') +@Authorized() +export class MediaController { + @Get('directory/:id') + async getDirectory(@Param('id') directoryId: string) { + return Directory.findById(directoryId); + } + + @Get('/file/:id') + async getFile(@Param('id') fileId: string) { + return File.findById(fileId); + } + + @Put('directory/:parent') + async createDirectory(@Param('parent') parentDirectoryId: string, @Body() directory: IDirectory) { + const savedDirectory = await new Directory(directory).save(); + + const parent = await Directory.findById(parentDirectoryId); + parent.subDirectories.push(savedDirectory); + await parent.save(); + + return savedDirectory; + } + + @Put('file/:parent') + async createFile(@Param('parent') parentDirectoryId: string, @Body() file: IFile) { + const savedFile = await new File(file).save(); + + const parent = await Directory.findById(parentDirectoryId); + parent.files.push(savedFile); + await parent.save(); + + return savedFile; + } + + @Delete('/directory/:id') + async deleteDirectory(@Param('id') directoryId: string) { + const directoryToDelete = await Directory.findById(directoryId); + await directoryToDelete.remove(); + + return {success: true}; + } + + @Delete('/file/:id') + async deleteFile(@Param('id') fileId: string) { + const fileToDelete = await File.findById(fileId); + await fileToDelete.remove(); + + return {success: true}; + } +} From 37e408ba808526e57a7f8a149fa5d5727fc6b07f Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 14:59:33 +0100 Subject: [PATCH 011/124] add lazy directory --- api/src/controllers/MediaController.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index 869f49d22..8ae59b1f2 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -7,17 +7,28 @@ import {IFile} from '../../../shared/models/mediaManager/IFile'; @JsonController('/media') @Authorized() export class MediaController { - @Get('directory/:id') + @Get('/directory/:id') async getDirectory(@Param('id') directoryId: string) { return Directory.findById(directoryId); } + @Get('/directory/:id/lazy') + async getDirectoryLazy(@Param('id') directoryId: string) { + return Directory.findById(directoryId) + .populate('subDirectories') + .populate('files'); + } + @Get('/file/:id') async getFile(@Param('id') fileId: string) { return File.findById(fileId); } - @Put('directory/:parent') + @Put('/directory') + async createRootDirectory(@Body() directory: IDirectory) { + return new Directory(directory).save(); + } + @Put('/directory/:parent') async createDirectory(@Param('parent') parentDirectoryId: string, @Body() directory: IDirectory) { const savedDirectory = await new Directory(directory).save(); @@ -28,7 +39,7 @@ export class MediaController { return savedDirectory; } - @Put('file/:parent') + @Put('/file/:parent') async createFile(@Param('parent') parentDirectoryId: string, @Body() file: IFile) { const savedFile = await new File(file).save(); From 532e36e726b4feb66276d87ad2166e99918b189e Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 15:09:40 +0100 Subject: [PATCH 012/124] fix linting --- api/src/models/mediaManager/Directory.ts | 2 +- api/src/models/mediaManager/File.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index 20678b4e8..b0a3099c4 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -26,7 +26,7 @@ const directorySchema = new mongoose.Schema({ ref: 'File' } ] -},{ +}, { timestamps: true, }); diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index ec8cadb44..062671d65 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -22,7 +22,7 @@ const fileSchema = new mongoose.Schema({ type: String, required: true }, -},{ +}, { timestamps: true, }); From db3d5e5225a8ac08d848d1dfafbc8e7a0d3d3f46 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 15:09:50 +0100 Subject: [PATCH 013/124] fix spelling --- api/src/models/mediaManager/Directory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index b0a3099c4..c831ad5dd 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -1,7 +1,7 @@ import {IDirectory} from '../../../../shared/models/mediaManager/IDirectory'; import * as mongoose from 'mongoose'; -interface IDIrectoryModel extends IDirectory, mongoose.Document { +interface IDirectoryModel extends IDirectory, mongoose.Document { } @@ -30,6 +30,6 @@ const directorySchema = new mongoose.Schema({ timestamps: true, }); -const Directory = mongoose.model('Directory', directorySchema); +const Directory = mongoose.model('Directory', directorySchema); -export {Directory, IDIrectoryModel}; +export {Directory, IDirectoryModel}; From ce725d61d67aa7221e3815ba6d105052c78aa238 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 11 Jan 2018 15:24:52 +0100 Subject: [PATCH 014/124] add media classes --- app/webFrontend/src/app/models/Course.ts | 2 ++ .../src/app/models/mediaManager/Directory.ts | 16 ++++++++++++++++ .../src/app/models/mediaManager/File.ts | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 app/webFrontend/src/app/models/mediaManager/Directory.ts create mode 100644 app/webFrontend/src/app/models/mediaManager/File.ts diff --git a/app/webFrontend/src/app/models/Course.ts b/app/webFrontend/src/app/models/Course.ts index 6ecb8076f..773ec9d62 100644 --- a/app/webFrontend/src/app/models/Course.ts +++ b/app/webFrontend/src/app/models/Course.ts @@ -2,6 +2,7 @@ import {ICourse} from '../../../../../shared/models/ICourse'; import {IUser} from '../../../../../shared/models/IUser'; import {ILecture} from '../../../../../shared/models/ILecture'; import {IWhitelistUser} from '../../../../../shared/models/IWhitelistUser'; +import {IDirectory} from '../../../../../shared/models/mediaManager/IDirectory'; /** * Created by Alexander on 23.05.2017. @@ -12,6 +13,7 @@ export class Course implements ICourse { active: boolean; description: string; courseAdmin: IUser; + media: IDirectory; teachers: IUser[]; students: IUser[]; lectures: ILecture[]; diff --git a/app/webFrontend/src/app/models/mediaManager/Directory.ts b/app/webFrontend/src/app/models/mediaManager/Directory.ts new file mode 100644 index 000000000..c061fc7ff --- /dev/null +++ b/app/webFrontend/src/app/models/mediaManager/Directory.ts @@ -0,0 +1,16 @@ +import {IDirectory} from '../../../../../../shared/models/mediaManager/IDirectory'; +import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; + +export class Directory implements IDirectory { + _id: any; + name: string; + subDirectories: IDirectory[]; + files: IFile[]; + + public Directory(directory: IDirectory) { + this._id = directory._id; + this.name = directory.name; + this.subDirectories = directory.subDirectories; + this.files = directory.files; + } +} diff --git a/app/webFrontend/src/app/models/mediaManager/File.ts b/app/webFrontend/src/app/models/mediaManager/File.ts new file mode 100644 index 000000000..343d260fc --- /dev/null +++ b/app/webFrontend/src/app/models/mediaManager/File.ts @@ -0,0 +1,17 @@ +import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; + +export class File implements IFile { + _id: any; + name: string; + physicalPath: string; + size: number; + mimeType: string; + + public File(file: IFile) { + this._id = file._id; + this.name = file.name; + this.physicalPath = file.physicalPath; + this.size = file.size; + this.mimeType = file.mimeType; + } +} From 934c6964b713648b8d769ac8dc106ca34f6f8d71 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 11 Jan 2018 18:12:40 +0100 Subject: [PATCH 015/124] Adds MediaDataService with functions --- .../src/app/shared/services/data.service.ts | 225 ++++++++++++------ 1 file changed, 153 insertions(+), 72 deletions(-) diff --git a/app/webFrontend/src/app/shared/services/data.service.ts b/app/webFrontend/src/app/shared/services/data.service.ts index af005820a..d250b0e6e 100644 --- a/app/webFrontend/src/app/shared/services/data.service.ts +++ b/app/webFrontend/src/app/shared/services/data.service.ts @@ -1,11 +1,12 @@ import {Injectable} from '@angular/core'; import {BackendService} from './backend.service'; import {Dependency} from '../../about/licenses/dependency.model'; -import {ITaskUnit} from '../../../../../../shared/models/units/ITaskUnit'; import {ILecture} from '../../../../../../shared/models/ILecture'; import {IUnit} from '../../../../../../shared/models/units/IUnit'; import {IUser} from '../../../../../../shared/models/IUser'; import {ICourse} from '../../../../../../shared/models/ICourse'; +import {IDirectory} from '../../../../../../shared/models/mediaManager/IDirectory'; +import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; export abstract class DataService { @@ -24,76 +25,76 @@ export abstract class DataService { createItem(createItem: any): Promise { return new Promise((resolve, reject) => { this.backendService.post(this.apiPath, createItem) - .subscribe( - (responseItem) => { - resolve(responseItem); - }, - error => reject(error) - ); + .subscribe( + (responseItem) => { + resolve(responseItem); + }, + error => reject(error) + ); }); } readItems(): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath) - .subscribe( - (responseItems: any) => { - if (this.changeProps2Date) { - responseItems.forEach(item => { - this.changeProps2Date.forEach(prop => { - DataService.changeStringProp2DateProp(item, prop); + .subscribe( + (responseItems: any) => { + if (this.changeProps2Date) { + responseItems.forEach(item => { + this.changeProps2Date.forEach(prop => { + DataService.changeStringProp2DateProp(item, prop); + }); }); - }); - } + } - resolve(responseItems); - }, - (error) => reject(error) - ); + resolve(responseItems); + }, + (error) => reject(error) + ); }); } updateItem(updateItem: any): Promise { return new Promise((resolve, reject) => { this.backendService.put(this.apiPath + updateItem._id, JSON.stringify(updateItem)) - .subscribe( - (res) => { - resolve(res); - }, - error => reject(error) - ); + .subscribe( + (res) => { + resolve(res); + }, + error => reject(error) + ); }); } deleteItem(deleteItem: any): Promise { return new Promise((resolve, reject) => { this.backendService.delete(this.apiPath + deleteItem._id) - .subscribe( - () => { - resolve(); - }, - error => reject(error) - ); + .subscribe( + () => { + resolve(); + }, + error => reject(error) + ); }); } readSingleItem(id: string): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath + id) - .subscribe( - (responseItems: any) => { - if (this.changeProps2Date) { - responseItems.forEach(item => { - this.changeProps2Date.forEach(prop => { - DataService.changeStringProp2DateProp(item, prop); + .subscribe( + (responseItems: any) => { + if (this.changeProps2Date) { + responseItems.forEach(item => { + this.changeProps2Date.forEach(prop => { + DataService.changeStringProp2DateProp(item, prop); + }); }); - }); - } + } - resolve(responseItems); - }, - error => reject(error) - ); + resolve(responseItems); + }, + error => reject(error) + ); }); } } @@ -216,7 +217,7 @@ export class DuplicationService extends DataService { duplicateUnit(unit: IUnit, lectureId: string, courseId: string): Promise { return new Promise((resolve, reject) => { - this.backendService.post(this.apiPath + 'unit/' + unit._id, JSON.stringify({courseId: courseId , lectureId: lectureId})) + this.backendService.post(this.apiPath + 'unit/' + unit._id, JSON.stringify({courseId: courseId, lectureId: lectureId})) .subscribe( (responseItem: any) => { resolve(responseItem); @@ -227,6 +228,86 @@ export class DuplicationService extends DataService { } } +@Injectable() +export class MediaService extends DataService { + constructor(public backendService: BackendService) { + super('media/', backendService); + } + + createRootDir(rootDirName: string): Promise { + // TODO: If this works, apply to all + return this.backendService.put(this.apiPath + 'directory', JSON.stringify({name: rootDirName})) + .toPromise(); + } + + createDir(newDirName: string, parentDir: IDirectory): Promise { + return new Promise((resolve, reject) => { + this.backendService.put(this.apiPath + 'directory/' + parentDir._id, JSON.stringify({name: newDirName})) + .subscribe((responseItem: IDirectory) => { + resolve(responseItem); + }, + error => reject(error) + ); + }); + } + + addFile(directory: IDirectory): Promise { + return new Promise((resolve, reject) => { + this.backendService.put(this.apiPath + 'file/' + directory._id, JSON.stringify({})) + .subscribe((responseItem: IFile) => { + resolve(responseItem); + }, + error => reject(error) + ); + }); + } + + getDirectory(dirId: string, lazy: boolean = false): Promise { + const path = this.apiPath + 'directory/' + dirId + (lazy ? '/lazy' : ''); + return new Promise((resolve, reject) => { + this.backendService.get(path) + .subscribe((responseItem: IDirectory) => { + resolve(responseItem); + }, + error => reject(error) + ); + }); + } + + getFile(fileId: string): Promise { + return new Promise((resolve, reject) => { + this.backendService.get(this.apiPath + 'file/' + fileId) + .subscribe((responseItem: IFile) => { + resolve(responseItem); + }, + error => reject(error)); + }); + } + + deleteDirectory(dir: IDirectory): Promise { + return new Promise((resolve, reject) => { + this.backendService.delete(this.apiPath + 'directory/' + dir._id) + .subscribe( + (responseItem: any) => { + resolve(responseItem); + }, + error => reject(error) + ); + }); + } + + deleteFile(file: IFile): Promise { + return new Promise((resolve, reject) => { + this.backendService.delete(this.apiPath + 'file/' + file._id) + .subscribe( + (responseItem: any) => { + resolve(responseItem); + }, + error => reject(error) + ); + }); + } +} @Injectable() export class CourseService extends DataService { @@ -238,19 +319,19 @@ export class CourseService extends DataService { const accessKey: string = data.accessKey; return new Promise((resolve, reject) => { this.backendService.post(this.apiPath + courseId + '/enroll', JSON.stringify({accessKey})) - .subscribe( - (responseItem: any) => { - resolve(responseItem); - }, - error => reject(error) - ); + .subscribe( + (responseItem: any) => { + resolve(responseItem); + }, + error => reject(error) + ); }); } sendMailToSelectedUsers(data: any): Promise { return this.backendService - .post(this.apiPath + 'mail', JSON.stringify(data)) - .toPromise(); + .post(this.apiPath + 'mail', JSON.stringify(data)) + .toPromise(); } leaveStudent(courseId: string): Promise { @@ -350,26 +431,26 @@ export class AboutDataService extends DataService { getApiDependencies(): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath + 'dependencies') - .subscribe((responseItems: any) => { - if (responseItems.httpCode >= 500) { - // FIXME: Just return, right? - return resolve([]); - } - - const out = []; - responseItems.data.forEach(item => { - out.push(new Dependency( - item.name, - item.version, - item.repository, - item.license, - item.devDependency) - ); - }); - resolve(out); - }, - error => reject(error) - ); + .subscribe((responseItems: any) => { + if (responseItems.httpCode >= 500) { + // FIXME: Just return, right? + return resolve([]); + } + + const out = []; + responseItems.data.forEach(item => { + out.push(new Dependency( + item.name, + item.version, + item.repository, + item.license, + item.devDependency) + ); + }); + resolve(out); + }, + error => reject(error) + ); }); } } From f7807a7acdbfb46ed08872b277ef39c0f73daad2 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Fri, 12 Jan 2018 14:29:18 +0100 Subject: [PATCH 016/124] remove unused field --- api/src/models/mediaManager/Directory.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index c831ad5dd..10094c7af 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -6,10 +6,6 @@ interface IDirectoryModel extends IDirectory, mongoose.Document { } const directorySchema = new mongoose.Schema({ - _course: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Course' - }, name: { type: String, required: true From 27c0cb655325dcef541544a5489b84852e5362f5 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Fri, 12 Jan 2018 14:29:34 +0100 Subject: [PATCH 017/124] do not require mimeType --- api/src/models/mediaManager/File.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index 062671d65..b3c036a2f 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -20,7 +20,6 @@ const fileSchema = new mongoose.Schema({ }, mimeType: { type: String, - required: true }, }, { timestamps: true, From ede6d3f9d3f4f86502e29373130285cb2f6f1821 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Mon, 15 Jan 2018 17:30:08 +0100 Subject: [PATCH 018/124] throw 401 if no authorization header is set --- api/src/security/RoleAuthorization.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/security/RoleAuthorization.ts b/api/src/security/RoleAuthorization.ts index cf552f198..f2f0aa94a 100644 --- a/api/src/security/RoleAuthorization.ts +++ b/api/src/security/RoleAuthorization.ts @@ -1,12 +1,16 @@ import config from '../config/main'; -import {Action} from 'routing-controllers'; +import {Action, UnauthorizedError} from 'routing-controllers'; import {User} from '../models/User'; import jwt = require('jsonwebtoken'); import * as mongoose from 'mongoose'; export class RoleAuthorization { static checkAuthorization(action: Action, roles: string[]): Promise { - const token = action.request.headers['authorization'].split(' ')[1]; + const authorizationHeader = action.request.headers['authorization']; + if (!authorizationHeader) { + throw new UnauthorizedError(); + } + const token = authorizationHeader.split(' ')[1]; const decoded: any = jwt.verify(token, config.secret); const userId = decoded._id; From a9696907d6ef4ee0c5619cb055aa431df961af9b Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Mon, 15 Jan 2018 17:30:48 +0100 Subject: [PATCH 019/124] add _id transformation --- api/src/models/mediaManager/Directory.ts | 5 +++++ api/src/models/mediaManager/File.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index 10094c7af..a6c5e47e7 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -24,6 +24,11 @@ const directorySchema = new mongoose.Schema({ ] }, { timestamps: true, + toObject: { + transform: function (doc: IDirectoryModel, ret: any) { + ret._id = ret._id.toString(); + } + }, }); const Directory = mongoose.model('Directory', directorySchema); diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index b3c036a2f..c7430e16e 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -23,6 +23,11 @@ const fileSchema = new mongoose.Schema({ }, }, { timestamps: true, + toObject: { + transform: function (doc: IFileModel, ret: any) { + ret._id = ret._id.toString(); + } + }, }); const File = mongoose.model('File', fileSchema); From 71929c3c4ae7dd3eb88d867a688a1b2b59145de3 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Mon, 15 Jan 2018 17:31:50 +0100 Subject: [PATCH 020/124] add AuthorizationMiddleware --- api/src/controllers/MediaController.ts | 29 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index 8ae59b1f2..f57599878 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -1,4 +1,4 @@ -import {Authorized, Body, Delete, Get, JsonController, Param, Put} from 'routing-controllers'; +import {Authorized, Body, Delete, Get, JsonController, NotFoundError, Param, Put} from 'routing-controllers'; import {Directory} from '../models/mediaManager/Directory'; import {File} from '../models/mediaManager/File'; import {IDirectory} from '../../../shared/models/mediaManager/IDirectory'; @@ -9,25 +9,31 @@ import {IFile} from '../../../shared/models/mediaManager/IFile'; export class MediaController { @Get('/directory/:id') async getDirectory(@Param('id') directoryId: string) { - return Directory.findById(directoryId); + const directory = await Directory.findById(directoryId); + return directory.toObject(); } @Get('/directory/:id/lazy') async getDirectoryLazy(@Param('id') directoryId: string) { - return Directory.findById(directoryId) + const directory = await Directory.findById(directoryId) .populate('subDirectories') .populate('files'); + return directory.toObject(); } @Get('/file/:id') async getFile(@Param('id') fileId: string) { - return File.findById(fileId); + const file = await File.findById(fileId); + return file.toObject(); } + @Authorized(['teacher', 'admin']) @Put('/directory') async createRootDirectory(@Body() directory: IDirectory) { - return new Directory(directory).save(); + const savedDirectory = await new Directory(directory).save(); + return savedDirectory.toObject(); } + @Authorized(['teacher', 'admin']) @Put('/directory/:parent') async createDirectory(@Param('parent') parentDirectoryId: string, @Body() directory: IDirectory) { const savedDirectory = await new Directory(directory).save(); @@ -36,9 +42,10 @@ export class MediaController { parent.subDirectories.push(savedDirectory); await parent.save(); - return savedDirectory; + return savedDirectory.toObject(); } + @Authorized(['teacher', 'admin']) @Put('/file/:parent') async createFile(@Param('parent') parentDirectoryId: string, @Body() file: IFile) { const savedFile = await new File(file).save(); @@ -47,20 +54,28 @@ export class MediaController { parent.files.push(savedFile); await parent.save(); - return savedFile; + return savedFile.toObject(); } + @Authorized(['teacher', 'admin']) @Delete('/directory/:id') async deleteDirectory(@Param('id') directoryId: string) { const directoryToDelete = await Directory.findById(directoryId); + if (!directoryToDelete) { + throw new NotFoundError(); + } await directoryToDelete.remove(); return {success: true}; } + @Authorized(['teacher', 'admin']) @Delete('/file/:id') async deleteFile(@Param('id') fileId: string) { const fileToDelete = await File.findById(fileId); + if (!fileToDelete) { + throw new NotFoundError(); + } await fileToDelete.remove(); return {success: true}; From f47f50f2bba0c3e3aaccfdc84960018c7395e3c0 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 15 Jan 2018 21:58:55 +0100 Subject: [PATCH 021/124] Adapts course edit routing for media manager --- .../app/course/course-edit/course-edit-routing.module.ts | 7 +++++++ .../src/app/course/course-edit/course-edit.component.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts index 025c75c04..dff10d084 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts @@ -5,6 +5,7 @@ import {AuthGuardService} from '../../shared/services/auth-guard.service'; import {GeneralTabComponent} from './general-tab/general-tab.component'; import {MembersComponent} from './members/members.component'; import {TeachersComponent} from './teachers/teachers.component'; +import {CourseMediamanagerComponent} from './course-mediamanager/course-mediamanager.component'; const routes: Routes = [ { @@ -24,6 +25,12 @@ const routes: Routes = [ path: 'content', loadChildren: 'app/course/course-edit/course-manage-content/course-manage-content.module#CourseManageContentModule', }, + { + path: 'media', + component: CourseMediamanagerComponent, + canActivate: [AuthGuardService], + data: {roles: ['teacher', 'admin']}, + }, { path: 'members', component: MembersComponent, diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.component.ts b/app/webFrontend/src/app/course/course-edit/course-edit.component.ts index ddbd8155a..8cf9ed89c 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.component.ts @@ -13,6 +13,7 @@ export class CourseEditComponent implements OnInit { tabs = [ { path: '.', label: 'General' }, { path: 'content', label: 'Content' }, + { path: 'media', label: 'Media' }, { path: 'members', label: 'Members' }, { path: 'teachers', label: 'Teachers' }, ]; From 3208bb2680a325d0965f507cd401cab298006f74 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 15 Jan 2018 23:04:24 +0100 Subject: [PATCH 022/124] WIP: Course Media --- app/webFrontend/src/app/app.module.ts | 3 +- .../course-edit/course-edit-routing.module.ts | 4 +- .../course/course-edit/course-edit.module.ts | 6 +- .../course-mediamanager-detail.dialog.html | 0 .../course-mediamanager-detail.dialog.scss | 0 .../course-mediamanager-detail.dialog.spec.ts | 2 +- .../course-mediamanager-detail.dialog.ts | 0 .../course-media.component.html} | 0 .../course-media.component.scss} | 0 .../course-media.component.spec.ts} | 12 +-- .../course-media/course-media.component.ts | 79 +++++++++++++++++++ .../course-mediamanager.component.ts | 44 ----------- 12 files changed, 93 insertions(+), 57 deletions(-) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager-detail-dialog => course-media/course-media-detail-dialog}/course-mediamanager-detail.dialog.html (100%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager-detail-dialog => course-media/course-media-detail-dialog}/course-mediamanager-detail.dialog.scss (100%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager-detail-dialog => course-media/course-media-detail-dialog}/course-mediamanager-detail.dialog.spec.ts (94%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager-detail-dialog => course-media/course-media-detail-dialog}/course-mediamanager-detail.dialog.ts (100%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager.component.html => course-media/course-media.component.html} (100%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager.component.scss => course-media/course-media.component.scss} (100%) rename app/webFrontend/src/app/course/course-edit/{course-mediamanager/course-mediamanager.component.spec.ts => course-media/course-media.component.spec.ts} (50%) create mode 100644 app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts delete mode 100644 app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts diff --git a/app/webFrontend/src/app/app.module.ts b/app/webFrontend/src/app/app.module.ts index 48d4331d3..e62c7e827 100644 --- a/app/webFrontend/src/app/app.module.ts +++ b/app/webFrontend/src/app/app.module.ts @@ -13,7 +13,7 @@ import { CourseService, DownloadFileService, FreeTextUnitService, - LectureService, + LectureService, MediaService, UnitService, UserDataService } from './shared/services/data.service'; @@ -70,6 +70,7 @@ import {DataSharingService} from './shared/services/data-sharing.service'; TitleService, DownloadFileService, RavenErrorHandler, + MediaService, { provide: ErrorHandler, useExisting: RavenErrorHandler diff --git a/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts index dff10d084..8ba70c41e 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit-routing.module.ts @@ -5,7 +5,7 @@ import {AuthGuardService} from '../../shared/services/auth-guard.service'; import {GeneralTabComponent} from './general-tab/general-tab.component'; import {MembersComponent} from './members/members.component'; import {TeachersComponent} from './teachers/teachers.component'; -import {CourseMediamanagerComponent} from './course-mediamanager/course-mediamanager.component'; +import {CourseMediaComponent} from './course-media/course-media.component'; const routes: Routes = [ { @@ -27,7 +27,7 @@ const routes: Routes = [ }, { path: 'media', - component: CourseMediamanagerComponent, + component: CourseMediaComponent, canActivate: [AuthGuardService], data: {roles: ['teacher', 'admin']}, }, diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts index 10fbd4fc9..f57c664a5 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts @@ -13,8 +13,8 @@ import {SharedModule} from '../../shared/shared.module'; import {CourseEditRoutingModule} from './course-edit-routing.module'; import {GeneralTabComponent} from './general-tab/general-tab.component'; import {CourseManageContentModule} from './course-manage-content/course-manage-content.module'; -import { CourseMediamanagerComponent } from './course-mediamanager/course-mediamanager.component'; -import { CourseMediamanagerDetailDialog } from './course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog'; +import { CourseMediaComponent } from './course-media/course-media.component'; +import { CourseMediamanagerDetailDialog } from './course-media/course-media-detail-dialog/course-mediamanager-detail.dialog'; @NgModule({ imports: [ @@ -32,7 +32,7 @@ import { CourseMediamanagerDetailDialog } from './course-mediamanager/course-med MembersComponent, TeachersComponent, GeneralTabComponent, - CourseMediamanagerComponent, + CourseMediaComponent, CourseMediamanagerDetailDialog, ], exports: [ diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html similarity index 100% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.html rename to app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.scss similarity index 100% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.scss rename to app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.scss diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts similarity index 94% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts rename to app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts index 25f8a3026..f61350833 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.spec.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CourseMediamanagerDetail.DialogComponent } from './course-mediamanager-detail-dialog.component'; +import { CourseMediamanagerDetail.DialogComponent } from './course-media-detail-dialog.component'; describe('CourseMediamanagerDetail.DialogComponent', () => { let component: CourseMediamanagerDetail.DialogComponent; diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts similarity index 100% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager-detail-dialog/course-mediamanager-detail.dialog.ts rename to app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html similarity index 100% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.html rename to app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss similarity index 100% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.scss rename to app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.spec.ts similarity index 50% rename from app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts rename to app/webFrontend/src/app/course/course-edit/course-media/course-media.component.spec.ts index e1055a931..08567111c 100644 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.spec.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CourseMediamanagerComponent } from './course-mediamanager.component'; +import { CourseMediaComponent } from './course-media.component'; -describe('CourseMediamanagerComponent', () => { - let component: CourseMediamanagerComponent; - let fixture: ComponentFixture; +describe('CourseMediaComponent', () => { + let component: CourseMediaComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CourseMediamanagerComponent ] + declarations: [ CourseMediaComponent ] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(CourseMediamanagerComponent); + fixture = TestBed.createComponent(CourseMediaComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts new file mode 100644 index 000000000..024b4fe45 --- /dev/null +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -0,0 +1,79 @@ +import {Component, OnInit} from '@angular/core'; +import {ICourse} from '../../../../../../../shared/models/ICourse'; +import {CourseMediamanagerDetailDialog} from './course-media-detail-dialog/course-mediamanager-detail.dialog'; +import {MatDialog, MatSnackBar} from '@angular/material'; +import {CourseService, MediaService} from '../../../shared/services/data.service'; +import {ActivatedRoute} from '@angular/router'; + +@Component({ + selector: 'app-course-mediamanager', + templateUrl: './course-media.component.html', + styleUrls: ['./course-media.component.scss'] +}) +export class CourseMediaComponent implements OnInit { + course: ICourse; + folderBarVisible: boolean; + + constructor(public dialog: MatDialog, + private mediaService: MediaService, + private courseService: CourseService, + private route: ActivatedRoute, + private snackBar: MatSnackBar) { + } + + ngOnInit() { + this.folderBarVisible = true; + + this.route.params.subscribe(params => { + console.log(params); + const courseId = params['id']; + // this.courseService.readSingleItem(courseId) + // .then(course => { + // console.log(course); + // }); + }, + error => { + }, + + ); + // console.log(this.course); + // if (this.course.media === null) { + // // Root dir does not exist, add one + // this.mediaService.createRootDir(this.course.name) + // .then(value => { + // this.course.media = value; + // console.log(this.course); + // this.courseService.updateItem(this.course) + // .catch(reason => + // this.snackBar.open('Applying root-dir to course failed', '', {duration: 3000}) + // ); + // }) + // .catch(reason => + // this.snackBar.open('Creating root-dir failed', '', {duration: 3000}) + // ); + // } + } + + toggleFolderBarVisibility(): void { + this.folderBarVisible = !this.folderBarVisible; + } + + openDetailDialog(): void { + const dialogRef = this.dialog.open(CourseMediamanagerDetailDialog, { + width: '80vw', + // height: '94vh', + maxWidth: '100vw', + maxHeight: '90vh', + data: { + markdown: this.course + } + }); + + // dialogRef.afterClosed().subscribe(result => { + // if (typeof result !== 'undefined') { + // this.model.markdown = result; + // } + // }); + } + +} diff --git a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts b/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts deleted file mode 100644 index a76f89084..000000000 --- a/app/webFrontend/src/app/course/course-edit/course-mediamanager/course-mediamanager.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {ICourse} from '../../../../../../../shared/models/ICourse'; -import {CourseMediamanagerDetailDialog} from './course-mediamanager-detail-dialog/course-mediamanager-detail.dialog'; -import {MatDialog} from '@angular/material'; - -@Component({ - selector: 'app-course-mediamanager', - templateUrl: './course-mediamanager.component.html', - styleUrls: ['./course-mediamanager.component.scss'] -}) -export class CourseMediamanagerComponent implements OnInit { - @Input() course: ICourse; - folderBarVisible: boolean; - - constructor(public dialog: MatDialog) { - } - - ngOnInit() { - this.folderBarVisible = true; - } - - toggleFolderBarVisibility(): void { - this.folderBarVisible = !this.folderBarVisible; - } - - openDetailDialog(): void { - const dialogRef = this.dialog.open(CourseMediamanagerDetailDialog, { - width: '80vw', - // height: '94vh', - maxWidth: '100vw', - maxHeight: '90vh', - data: { - markdown: this.course - } - }); - - // dialogRef.afterClosed().subscribe(result => { - // if (typeof result !== 'undefined') { - // this.model.markdown = result; - // } - // }); - } - -} From 2cfc39470f8ea5609a3e62e87ba475b897428bc5 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 08:49:12 +0100 Subject: [PATCH 023/124] check split length --- api/src/security/RoleAuthorization.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/src/security/RoleAuthorization.ts b/api/src/security/RoleAuthorization.ts index f2f0aa94a..50f7cba77 100644 --- a/api/src/security/RoleAuthorization.ts +++ b/api/src/security/RoleAuthorization.ts @@ -10,7 +10,11 @@ export class RoleAuthorization { if (!authorizationHeader) { throw new UnauthorizedError(); } - const token = authorizationHeader.split(' ')[1]; + const authorizationSplit = authorizationHeader.split(' '); + if (authorizationSplit.length < 2) { + throw new UnauthorizedError(); + } + const token = authorizationSplit[1]; const decoded: any = jwt.verify(token, config.secret); const userId = decoded._id; From b213da0fb6dc4bf5ad1028c96b38d7a6033d0535 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 09:05:35 +0100 Subject: [PATCH 024/124] add uploadFolder to config --- api/src/config/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/config/main.ts b/api/src/config/main.ts index 44be0b1d4..4a84fda30 100644 --- a/api/src/config/main.ts +++ b/api/src/config/main.ts @@ -37,4 +37,5 @@ export default { maxFileSize: 51200, maxZipSize: 204800, + uploadFolder: process.env.UPLOADFOLDER || (appRoot + 'uploads'), }; From 8e563a1901ac6269fd916fbadd222ee0a2d7e0fa Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 09:06:16 +0100 Subject: [PATCH 025/124] use uploadFolder from config --- api/src/controllers/UnitController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/controllers/UnitController.ts b/api/src/controllers/UnitController.ts index 8ee2fe77a..612ab8923 100644 --- a/api/src/controllers/UnitController.ts +++ b/api/src/controllers/UnitController.ts @@ -11,13 +11,14 @@ import {IUnitModel, Unit} from '../models/units/Unit'; import {IUnit} from '../../../shared/models/units/IUnit'; import {IFileUnit} from '../../../shared/models/units/IFileUnit'; import {ValidationError} from 'mongoose'; +import config from '../config/main' const multer = require('multer'); const uploadOptions = { storage: multer.diskStorage({ destination: (req: any, file: any, cb: any) => { - cb(null, 'uploads/'); + cb(null, config.uploadFolder); }, filename: (req: any, file: any, cb: any) => { const extPos = file.originalname.lastIndexOf('.'); From 395f3bcfb8c907a0062bb7d6968354dc8010e0f6 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 09:06:56 +0100 Subject: [PATCH 026/124] change createFile to accept files --- api/src/controllers/MediaController.ts | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index f57599878..3536c03f8 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -1,8 +1,30 @@ -import {Authorized, Body, Delete, Get, JsonController, NotFoundError, Param, Put} from 'routing-controllers'; +import { + Authorized, Body, Delete, Get, JsonController, NotFoundError, Param, Put, + UploadedFile +} from 'routing-controllers'; import {Directory} from '../models/mediaManager/Directory'; import {File} from '../models/mediaManager/File'; import {IDirectory} from '../../../shared/models/mediaManager/IDirectory'; import {IFile} from '../../../shared/models/mediaManager/IFile'; +import crypto = require('crypto'); +import config from '../config/main'; + +const multer = require('multer'); + +const uploadOptions = { + storage: multer.diskStorage({ + destination: (req: any, file: any, cb: any) => { + cb(null, config.uploadFolder); + }, + filename: (req: any, file: any, cb: any) => { + const extPos = file.originalname.lastIndexOf('.'); + const ext = (extPos !== -1) ? `.${file.originalname.substr(extPos + 1).toLowerCase()}` : ''; + crypto.pseudoRandomBytes(16, (err, raw) => { + cb(err, err ? undefined : `${raw.toString('hex')}${ext}`); + }); + } + }), +}; @JsonController('/media') @Authorized() @@ -47,7 +69,13 @@ export class MediaController { @Authorized(['teacher', 'admin']) @Put('/file/:parent') - async createFile(@Param('parent') parentDirectoryId: string, @Body() file: IFile) { + async createFile(@Param('parent') parentDirectoryId: string, @UploadedFile('file', {options: uploadOptions}) uploadedFile: any) { + const file: IFile = new File({ + name: uploadedFile.originalname, + physicalPath: uploadedFile.path, + size: uploadedFile.size, + mimeType: uploadedFile.mimetype, + }); const savedFile = await new File(file).save(); const parent = await Directory.findById(parentDirectoryId); From dd2769f53c74d93fdc5bdfe1a66431c4c66451d2 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 09:47:41 +0100 Subject: [PATCH 027/124] fix upload folder definition --- api/src/config/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/config/main.ts b/api/src/config/main.ts index 4a84fda30..4d5574deb 100644 --- a/api/src/config/main.ts +++ b/api/src/config/main.ts @@ -37,5 +37,5 @@ export default { maxFileSize: 51200, maxZipSize: 204800, - uploadFolder: process.env.UPLOADFOLDER || (appRoot + 'uploads'), + uploadFolder: process.env.UPLOADFOLDER || (appRoot + '/uploads/'), }; From 3fcf590ed79e54f99aa370e08ecf373e3dba061d Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 11:01:21 +0100 Subject: [PATCH 028/124] Now using generics for Abstract DataService functions --- .../src/app/admin/user-admin/user-admin.component.ts | 2 +- .../course/course-edit/members/members.component.ts | 2 +- .../course/course-edit/teachers/teachers.component.ts | 2 +- .../src/app/shared/services/data.service.ts | 10 +++++----- .../src/app/shared/services/data/report.service.ts | 5 +++-- .../src/app/start/dashboard/dashboard.component.ts | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/webFrontend/src/app/admin/user-admin/user-admin.component.ts b/app/webFrontend/src/app/admin/user-admin/user-admin.component.ts index f631b960e..db23c2188 100644 --- a/app/webFrontend/src/app/admin/user-admin/user-admin.component.ts +++ b/app/webFrontend/src/app/admin/user-admin/user-admin.component.ts @@ -33,7 +33,7 @@ export class UserAdminComponent implements OnInit { } getUsers() { - this.userService.readItems().then(users => { + this.userService.readItems().then(users => { this.allUsers = users; }); } diff --git a/app/webFrontend/src/app/course/course-edit/members/members.component.ts b/app/webFrontend/src/app/course/course-edit/members/members.component.ts index 03415fed0..dee570651 100644 --- a/app/webFrontend/src/app/course/course-edit/members/members.component.ts +++ b/app/webFrontend/src/app/course/course-edit/members/members.component.ts @@ -36,7 +36,7 @@ export class MembersComponent implements OnInit { * TODO: Never load all users! */ getStudents() { - return this.userService.readItems() + return this.userService.readItems() .then(users => { this.allStudents = users.filter(obj => obj.role === 'student'); this.allStudents = this.allStudents.map(data => new User(data)); diff --git a/app/webFrontend/src/app/course/course-edit/teachers/teachers.component.ts b/app/webFrontend/src/app/course/course-edit/teachers/teachers.component.ts index 64cdb7871..2584865b4 100644 --- a/app/webFrontend/src/app/course/course-edit/teachers/teachers.component.ts +++ b/app/webFrontend/src/app/course/course-edit/teachers/teachers.component.ts @@ -36,7 +36,7 @@ export class TeachersComponent implements OnInit { * TODO: Never load all users! */ getTeachers() { - return this.userService.readItems().then(users => { + return this.userService.readItems().then(users => { this.allTeachers = users.filter(obj => obj.role === 'teacher'); this.allTeachers = this.allTeachers.map(data => new User(data)); }); diff --git a/app/webFrontend/src/app/shared/services/data.service.ts b/app/webFrontend/src/app/shared/services/data.service.ts index 1edf3123e..054424043 100644 --- a/app/webFrontend/src/app/shared/services/data.service.ts +++ b/app/webFrontend/src/app/shared/services/data.service.ts @@ -23,7 +23,7 @@ export abstract class DataService { public dependentID?: string) { } - createItem(createItem: any): Promise { + createItem(createItem: T): Promise { return new Promise((resolve, reject) => { this.backendService.post(this.apiPath, createItem) .subscribe( @@ -35,7 +35,7 @@ export abstract class DataService { }); } - readItems(): Promise { + readItems(): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath) .subscribe( @@ -55,7 +55,7 @@ export abstract class DataService { }); } - updateItem(updateItem: any): Promise { + updateItem(updateItem: T): Promise { return new Promise((resolve, reject) => { this.backendService.put(this.apiPath + updateItem._id, JSON.stringify(updateItem)) .subscribe( @@ -67,7 +67,7 @@ export abstract class DataService { }); } - deleteItem(deleteItem: any): Promise { + deleteItem(deleteItem: T): Promise { return new Promise((resolve, reject) => { this.backendService.delete(this.apiPath + deleteItem._id) .subscribe( @@ -79,7 +79,7 @@ export abstract class DataService { }); } - readSingleItem(id: string): Promise { + readSingleItem(id: string): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath + id) .subscribe( diff --git a/app/webFrontend/src/app/shared/services/data/report.service.ts b/app/webFrontend/src/app/shared/services/data/report.service.ts index 2eb3fc80c..f7cbba9d4 100644 --- a/app/webFrontend/src/app/shared/services/data/report.service.ts +++ b/app/webFrontend/src/app/shared/services/data/report.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; import {DataService} from '../data.service'; import {BackendService} from '../backend.service'; +import {IUser} from '../../../../../../../shared/models/IUser'; @Injectable() export class ReportService extends DataService { @@ -19,7 +20,7 @@ export class ReportService extends DataService { getCourseResults(courseId: string) { const originalApiPath = this.apiPath; this.apiPath += 'result/courses/'; - const promise = this.readSingleItem(courseId); + const promise = this.readSingleItem(courseId); this.apiPath = originalApiPath; return promise; } @@ -27,7 +28,7 @@ export class ReportService extends DataService { getUnitDetailForCourse(courseId: string, unitId: string) { const originalApiPath = this.apiPath; this.apiPath += 'details/courses/' + courseId + '/units/'; - const promise = this.readSingleItem(unitId); + const promise = this.readSingleItem(unitId); this.apiPath = originalApiPath; return promise; } diff --git a/app/webFrontend/src/app/start/dashboard/dashboard.component.ts b/app/webFrontend/src/app/start/dashboard/dashboard.component.ts index 612b4e6f2..c83d46cd8 100644 --- a/app/webFrontend/src/app/start/dashboard/dashboard.component.ts +++ b/app/webFrontend/src/app/start/dashboard/dashboard.component.ts @@ -26,7 +26,7 @@ export class DashboardComponent implements OnInit { } getCourses() { - this.courseService.readItems().then(courses => { + this.courseService.readItems().then(courses => { this.allCourses = courses; }); } From fd973cec13a8cd37a55399f3b5268c5a1a3d1f4c Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 11:27:22 +0100 Subject: [PATCH 029/124] Removes array from generics in data service; Adapts it in report service --- app/webFrontend/src/app/shared/services/data.service.ts | 2 +- app/webFrontend/src/app/shared/services/data/report.service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/webFrontend/src/app/shared/services/data.service.ts b/app/webFrontend/src/app/shared/services/data.service.ts index 054424043..d4a540d16 100644 --- a/app/webFrontend/src/app/shared/services/data.service.ts +++ b/app/webFrontend/src/app/shared/services/data.service.ts @@ -79,7 +79,7 @@ export abstract class DataService { }); } - readSingleItem(id: string): Promise { + readSingleItem(id: string): Promise { return new Promise((resolve, reject) => { this.backendService.get(this.apiPath + id) .subscribe( diff --git a/app/webFrontend/src/app/shared/services/data/report.service.ts b/app/webFrontend/src/app/shared/services/data/report.service.ts index f7cbba9d4..3645c793b 100644 --- a/app/webFrontend/src/app/shared/services/data/report.service.ts +++ b/app/webFrontend/src/app/shared/services/data/report.service.ts @@ -28,7 +28,8 @@ export class ReportService extends DataService { getUnitDetailForCourse(courseId: string, unitId: string) { const originalApiPath = this.apiPath; this.apiPath += 'details/courses/' + courseId + '/units/'; - const promise = this.readSingleItem(unitId); + // FIXME: Why do we expect an array? + const promise = this.readSingleItem(unitId); this.apiPath = originalApiPath; return promise; } From baf78aef470b1968bdf3be9696f498d88f714a79 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 11:30:02 +0100 Subject: [PATCH 030/124] fix media definition --- api/src/models/Course.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/api/src/models/Course.ts b/api/src/models/Course.ts index 07ae3b11c..1a25ab49d 100644 --- a/api/src/models/Course.ts +++ b/api/src/models/Course.ts @@ -27,12 +27,11 @@ const courseSchema = new mongoose.Schema({ type: mongoose.Schema.Types.ObjectId, ref: 'User' }, - media: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: 'IDirectory' - } - ], + media: + { + type: mongoose.Schema.Types.ObjectId, + ref: 'IDirectory' + }, teachers: [ { type: mongoose.Schema.Types.ObjectId, From 8b28297e7445c75af8c053ff341de0847d4b7313 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 11:38:38 +0100 Subject: [PATCH 031/124] populate IDirectory to media --- api/src/controllers/CourseController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/controllers/CourseController.ts b/api/src/controllers/CourseController.ts index 1053abe24..644bfa2f2 100644 --- a/api/src/controllers/CourseController.ts +++ b/api/src/controllers/CourseController.ts @@ -99,6 +99,7 @@ export class CourseController { } } }) + .populate('media') .populate('courseAdmin') .populate('teachers') .populate('students') From 01699d06f4839657ca4b48527043e088adc1eed1 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 11:39:19 +0100 Subject: [PATCH 032/124] Finalizes getRootDir in MediaManager --- .../course-media/course-media.component.ts | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 024b4fe45..7155e98a6 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -4,6 +4,7 @@ import {CourseMediamanagerDetailDialog} from './course-media-detail-dialog/cours import {MatDialog, MatSnackBar} from '@angular/material'; import {CourseService, MediaService} from '../../../shared/services/data.service'; import {ActivatedRoute} from '@angular/router'; +import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirectory'; @Component({ selector: 'app-course-mediamanager', @@ -21,37 +22,18 @@ export class CourseMediaComponent implements OnInit { private snackBar: MatSnackBar) { } - ngOnInit() { + async ngOnInit() { this.folderBarVisible = true; - this.route.params.subscribe(params => { - console.log(params); - const courseId = params['id']; - // this.courseService.readSingleItem(courseId) - // .then(course => { - // console.log(course); - // }); + this.route.parent.params.subscribe( + async (params) => { + // retrieve course + this.course = await this.courseService.readSingleItem(params['id']); }, error => { - }, - + this.snackBar.open('Could not load course', '', {duration: 3000}) + } ); - // console.log(this.course); - // if (this.course.media === null) { - // // Root dir does not exist, add one - // this.mediaService.createRootDir(this.course.name) - // .then(value => { - // this.course.media = value; - // console.log(this.course); - // this.courseService.updateItem(this.course) - // .catch(reason => - // this.snackBar.open('Applying root-dir to course failed', '', {duration: 3000}) - // ); - // }) - // .catch(reason => - // this.snackBar.open('Creating root-dir failed', '', {duration: 3000}) - // ); - // } } toggleFolderBarVisibility(): void { @@ -76,4 +58,15 @@ export class CourseMediaComponent implements OnInit { // }); } + async getRootDir(): Promise { + // Check if course has root dir + if (this.course.media === undefined) { + // Root dir does not exist, add one + this.course.media = await this.mediaService.createRootDir(this.course.name); + await this.courseService.updateItem(this.course); + } + + return this.course.media; + } + } From 5cd91ab023b03866200ac1caac1279fe6a98bcc2 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Tue, 16 Jan 2018 13:04:47 +0100 Subject: [PATCH 033/124] fix media reference --- api/src/models/Course.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/models/Course.ts b/api/src/models/Course.ts index 1a25ab49d..7b23c4447 100644 --- a/api/src/models/Course.ts +++ b/api/src/models/Course.ts @@ -30,7 +30,7 @@ const courseSchema = new mongoose.Schema({ media: { type: mongoose.Schema.Types.ObjectId, - ref: 'IDirectory' + ref: 'Directory' }, teachers: [ { From a17ca2be9e162ef94bb5e4f939854e4bed9a08a1 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 23:13:27 +0100 Subject: [PATCH 034/124] Makes simple show works and adding root dir --- .../course-media/course-media.component.html | 33 +++++++++---------- .../course-media/course-media.component.ts | 32 +++++++++++------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 4d598148b..0d0134d45 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -11,42 +11,39 @@
- content +

{{ course.media.name }}

- - +
- +
- - - - + + + + + + +

+ No Files so far. +

diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 7155e98a6..bedaf125e 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -14,6 +14,7 @@ import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirec export class CourseMediaComponent implements OnInit { course: ICourse; folderBarVisible: boolean; + currentFolder: IDirectory; constructor(public dialog: MatDialog, private mediaService: MediaService, @@ -29,6 +30,21 @@ export class CourseMediaComponent implements OnInit { async (params) => { // retrieve course this.course = await this.courseService.readSingleItem(params['id']); + + // Check if course has root dir + if (this.course.media === undefined) { + // Root dir does not exist, add one + this.course.media = await this.mediaService.createRootDir(this.course.name); + // Update course + const request: any = { + '_id': this.course._id, + 'media': this.course.media, + }; + await this.courseService.updateItem(request); + } + + // Set root dir as current + this.currentFolder = this.course.media; }, error => { this.snackBar.open('Could not load course', '', {duration: 3000}) @@ -36,6 +52,10 @@ export class CourseMediaComponent implements OnInit { ); } + dataReady(): boolean { + return this.course !== undefined && this.course.media !== undefined; + } + toggleFolderBarVisibility(): void { this.folderBarVisible = !this.folderBarVisible; } @@ -57,16 +77,4 @@ export class CourseMediaComponent implements OnInit { // } // }); } - - async getRootDir(): Promise { - // Check if course has root dir - if (this.course.media === undefined) { - // Root dir does not exist, add one - this.course.media = await this.mediaService.createRootDir(this.course.name); - await this.courseService.updateItem(this.course); - } - - return this.course.media; - } - } From 6e8e33051943a88c884b65dea67bc17f3569cb18 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 23:38:11 +0100 Subject: [PATCH 035/124] Removes dataReady and use ? --- .../course-media/course-media.component.html | 18 ++++++++---------- .../course-media/course-media.component.ts | 4 ---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 0d0134d45..3946c246b 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -11,7 +11,7 @@
-

{{ course.media.name }}

+

{{ course?.media?.name }}

@@ -35,16 +35,14 @@
- - - - - - -

- No Files so far. -

+ + + + +

+ No Files so far. +

diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index bedaf125e..1c1f2e11c 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -52,10 +52,6 @@ export class CourseMediaComponent implements OnInit { ); } - dataReady(): boolean { - return this.course !== undefined && this.course.media !== undefined; - } - toggleFolderBarVisibility(): void { this.folderBarVisible = !this.folderBarVisible; } From 13bae1828936f874b4f78b363f655f15b49c12f3 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 16 Jan 2018 23:38:35 +0100 Subject: [PATCH 036/124] Introduces upload form dialog --- .../upload-form-dialog.component.html | 5 +++ .../upload-form-dialog.component.scss | 0 .../upload-form-dialog.component.spec.ts | 25 +++++++++++++ .../upload-form-dialog.component.ts | 36 +++++++++++++++++++ .../src/app/shared/shared.module.ts | 2 ++ 5 files changed, 68 insertions(+) create mode 100644 app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html create mode 100644 app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.scss create mode 100644 app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts create mode 100644 app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html new file mode 100644 index 000000000..f949a05a6 --- /dev/null +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html @@ -0,0 +1,5 @@ + diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.scss b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts new file mode 100644 index 000000000..f47770683 --- /dev/null +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UploadFormDialogComponent } from './upload-form-dialog.component'; + +describe('UploadFormDialogComponent', () => { + let component: UploadFormDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UploadFormDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UploadFormDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts new file mode 100644 index 000000000..f7ca5e225 --- /dev/null +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts @@ -0,0 +1,36 @@ +import {Component, OnInit} from '@angular/core'; +import {MatDialogRef, MatSnackBar} from '@angular/material'; +import {UploadDialog} from '../upload-dialog/upload-dialog.component'; +import {IFileUnit} from '../../../../../../../shared/models/units/IFileUnit'; + +@Component({ + selector: 'app-upload-form-dialog', + templateUrl: './upload-form-dialog.component.html', + styleUrls: ['./upload-form-dialog.component.scss'] +}) +export class UploadFormDialogComponent implements OnInit { + + uploadPath = '/'; + + constructor(public dialogRef: MatDialogRef, + private snackBar: MatSnackBar) { + } + + ngOnInit() { + } + + onFileUploaded(event: IFileUnit) { + } + + onAllUploaded() { + } + + public upload() { + this.dialogRef.close(true); + } + + public cancel() { + this.dialogRef.close(false); + } + +} diff --git a/app/webFrontend/src/app/shared/shared.module.ts b/app/webFrontend/src/app/shared/shared.module.ts index 6f543ccbf..de7e37c3f 100644 --- a/app/webFrontend/src/app/shared/shared.module.ts +++ b/app/webFrontend/src/app/shared/shared.module.ts @@ -15,6 +15,7 @@ import {ButtonSaveCancelComponent} from './components/button-save-cancel/button- import {UploadFormComponent} from './components/upload-form/upload-form.component'; import {FileUploadModule} from 'ng2-file-upload'; import {FilesizePipe} from './pipes/filesize/filesize.pipe'; +import { UploadFormDialogComponent } from './components/upload-form-dialog/upload-form-dialog.component'; @NgModule({ imports: [ @@ -37,6 +38,7 @@ import {FilesizePipe} from './pipes/filesize/filesize.pipe'; ButtonSaveCancelComponent, UploadFormComponent, FilesizePipe, + UploadFormDialogComponent, ], exports: [ GravatarDirective, From f82465accf3255315a7617f321ee7f3cded89345 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 12:50:40 +0100 Subject: [PATCH 037/124] add types --- api/fixtures/FixtureUtils.ts | 87 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/api/fixtures/FixtureUtils.ts b/api/fixtures/FixtureUtils.ts index c0709cbe3..5097ae889 100644 --- a/api/fixtures/FixtureUtils.ts +++ b/api/fixtures/FixtureUtils.ts @@ -8,125 +8,125 @@ import {IUnit} from '../../shared/models/units/IUnit'; import {IUser} from '../../shared/models/IUser'; export class FixtureUtils { - public static async getRandomUser(hash?: string) { + public static async getRandomUser(hash?: string): Promise { const array = await this.getUser(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomUsers(min: number, max: number, hash?: string) { + public static async getRandomUsers(min: number, max: number, hash?: string): Promise { const array = await this.getUser(); - return this.getRandomArray(array, min, max, hash); + return this.getRandomArray(array, min, max, hash); } - public static async getRandomAdmin(hash?: string) { + public static async getRandomAdmin(hash?: string): Promise { const array = await this.getAdmins(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomAdmins(min: number, max: number, hash?: string) { + public static async getRandomAdmins(min: number, max: number, hash?: string): Promise { const array = await this.getAdmins(); - return this.getRandomArray(array, min, max, hash); + return this.getRandomArray(array, min, max, hash); } - public static async getRandomTeacher(hash?: string) { + public static async getRandomTeacher(hash?: string): Promise { const array = await this.getTeacher(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomTeacherForCourse(course: ICourse, hash?: string) { + public static async getRandomTeacherForCourse(course: ICourse, hash?: string): Promise { let array: IUser[] = []; array = array.concat(course.teachers); array.push(course.courseAdmin); - const user = await this.getRandom(array, hash); + const user = await this.getRandom(array, hash); return User.findById(user); } - public static async getRandomTeachers(min: number, max: number, hash?: string) { + public static async getRandomTeachers(min: number, max: number, hash?: string): Promise { const array = await this.getTeacher(); - return this.getRandomArray(array, min, max, hash); + return this.getRandomArray(array, min, max, hash); } - public static async getRandomStudent(hash?: string) { + public static async getRandomStudent(hash?: string): Promise { const array = await this.getStudents(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomStudents(min: number, max: number, hash?: string) { + public static async getRandomStudents(min: number, max: number, hash?: string): Promise { const array = await this.getStudents(); - return this.getRandomArray(array, min, max, hash); + return this.getRandomArray(array, min, max, hash); } - public static async getRandomCourse(hash?: string) { + public static async getRandomCourse(hash?: string): Promise { const array = await this.getCourses(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getCoursesFromLecture(lecture: ILecture) { + public static async getCoursesFromLecture(lecture: ILecture): Promise { return Course.findOne({lectures: { $in: [ lecture._id ] }}); } - public static async getCoursesFromUnit(unit: IUnit) { + public static async getCoursesFromUnit(unit: IUnit): Promise { return Course.findById(unit._course); } - public static async getRandomLecture(hash?: string) { + public static async getRandomLecture(hash?: string): Promise { const array = await this.getLectures(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomLectureFromCourse(course: ICourse, hash?: string) { - const lectureId = await this.getRandom(course.lectures, hash); + public static async getRandomLectureFromCourse(course: ICourse, hash?: string): Promise { + const lectureId = await this.getRandom(course.lectures, hash); return Lecture.findById(lectureId); } - public static async getLectureFromUnit(unit: IUnit) { + public static async getLectureFromUnit(unit: IUnit): Promise { return Lecture.findOne({units: { $in: [ unit._id ] }}); } - public static async getRandomUnit(hash?: string) { + public static async getRandomUnit(hash?: string): Promise { const array = await this.getUnits(); - return this.getRandom(array, hash); + return this.getRandom(array, hash); } - public static async getRandomUnitFromLecture(lecture: ILecture, hash?: string) { - const unitId = await this.getRandom(lecture.units, hash); + public static async getRandomUnitFromLecture(lecture: ILecture, hash?: string): Promise { + const unitId = await this.getRandom(lecture.units, hash); return Unit.findById(unitId); } - public static async getRandomUnitFromCourse(course: ICourse, hash?: string) { + public static async getRandomUnitFromCourse(course: ICourse, hash?: string): Promise { let units: Array = []; for (const lecture of course.lectures) { units = units.concat(lecture.units); } - const unitId = await this.getRandom(units, hash); + const unitId = await this.getRandom(units, hash); return Unit.findById(unitId); } - private static async getAdmins() { + private static async getAdmins(): Promise { return this.getUser('admin'); } - private static async getTeacher() { + private static async getTeacher(): Promise { return this.getUser('teacher'); } - private static async getStudents() { + private static async getStudents(): Promise { return this.getUser('student'); } - public static async getCourses() { + public static async getCourses(): Promise { return Course.find(); } - public static async getLectures() { + public static async getLectures(): Promise { return Lecture.find(); } - public static async getUnits() { + public static async getUnits(): Promise { return Unit.find(); } - private static async getUser(role?: string) { + private static async getUser(role?: string): Promise { if (!role) { return User.find(); } @@ -135,7 +135,7 @@ export class FixtureUtils { // returns a random entry out of the array // returns always the same entry when you provide the same hash (given the fixture base did not change) - private static async getRandom(array: any[], hash?: string) { + private static async getRandom(array: T[], hash?: string): Promise{ if (hash) { return array[this.getNumberFromString(hash, 0, array.length)]; } else { @@ -145,7 +145,7 @@ export class FixtureUtils { // returns an random subarray // returns always the same array when you provide the same hash (given the fixture base did not change) - private static async getRandomArray(array: any[], min: number, max: number, hash?: string) { + private static async getRandomArray(array: T[], min: number, max: number, hash?: string): Promise { if (hash) { const count = this.getNumberFromString(hash, min, max); const start = this.getNumberFromString(hash, 0, array.length - count); @@ -172,7 +172,7 @@ export class FixtureUtils { return start + (hash % (end - start)); } - private static shuffleArray (array: Array) { + private static shuffleArray(array: Array): T[] { let tmp; let current; let top = array.length; @@ -185,7 +185,6 @@ export class FixtureUtils { array[top] = tmp; } } - return array; } } From 8dc5d5025a0299cb6664605df32d37d60ecb5d10 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 13:09:05 +0100 Subject: [PATCH 038/124] fix linting error --- api/fixtures/FixtureUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fixtures/FixtureUtils.ts b/api/fixtures/FixtureUtils.ts index 5097ae889..1fdedf4c4 100644 --- a/api/fixtures/FixtureUtils.ts +++ b/api/fixtures/FixtureUtils.ts @@ -135,7 +135,7 @@ export class FixtureUtils { // returns a random entry out of the array // returns always the same entry when you provide the same hash (given the fixture base did not change) - private static async getRandom(array: T[], hash?: string): Promise{ + private static async getRandom(array: T[], hash?: string): Promise { if (hash) { return array[this.getNumberFromString(hash, 0, array.length)]; } else { From ed3775f129e8e5cc97d47c243dba84d21f5f4da0 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 13:09:32 +0100 Subject: [PATCH 039/124] fix type errors --- api/test/controllers/duplicate.ts | 14 +++++++------- api/test/controllers/import.ts | 6 +++--- api/test/integration/course.ts | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/test/controllers/duplicate.ts b/api/test/controllers/duplicate.ts index bead3a277..5aceb609f 100644 --- a/api/test/controllers/duplicate.ts +++ b/api/test/controllers/duplicate.ts @@ -47,9 +47,9 @@ describe('Duplicate', async () => { let unitJson: IUnit; const importResult = await chai.request(app) - .post(`${BASE_URL}/unit/${unit.id}`) + .post(`${BASE_URL}/unit/${unit._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) - .send({courseId: course.id, lectureId: lecture.id}) + .send({courseId: course._id, lectureId: lecture._id}) .catch((err) => err.response); importResult.status.should.be.equal(200, 'failed to duplicate ' + unit.name + @@ -119,9 +119,9 @@ describe('Duplicate', async () => { let lectureJson: ILecture; const importResult = await chai.request(app) - .post(`${BASE_URL}/lecture/${lecture.id}`) + .post(`${BASE_URL}/lecture/${lecture._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) - .send({courseId: course.id}) + .send({courseId: course._id}) .catch((err) => err.response); importResult.status.should.be.equal(200, 'failed to import ' + lecture.name + @@ -151,9 +151,9 @@ describe('Duplicate', async () => { let courseJson: ICourse; const importResult = await chai.request(app) - .post(`${BASE_URL}/course/${course.id}`) + .post(`${BASE_URL}/course/${course._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) - .send({courseAdmin: teacher.id}) + .send({courseAdmin: teacher._id}) .catch((err) => err.response); importResult.status.should.be.equal(200, 'failed to duplicate ' + course.name + @@ -164,7 +164,7 @@ describe('Duplicate', async () => { should.exist(importResult.body.updatedAt); should.exist(courseJson._id); courseJson.active.should.be.equal(false); - courseJson.courseAdmin.should.be.equal(teacher.id); + courseJson.courseAdmin.should.be.equal(teacher._id.toString()); courseJson.name.startsWith(course.name).should.be.equal(true); courseJson.description.should.be.equal(course.description); courseJson.lectures.should.be.instanceOf(Array).and.have.lengthOf(course.lectures.length); diff --git a/api/test/controllers/import.ts b/api/test/controllers/import.ts index a7ab44b79..49705b292 100644 --- a/api/test/controllers/import.ts +++ b/api/test/controllers/import.ts @@ -61,7 +61,7 @@ describe('Import', async () => { let unitJson: IUnit; const importResult = await chai.request(app) - .post(`${BASE_URL}/unit/${course.id}/${lecture.id}`) + .post(`${BASE_URL}/unit/${course._id}/${lecture._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) .attach('file', fs.readFileSync(tmpUnitFile.path), unit.name) .catch((err) => err.response); @@ -145,7 +145,7 @@ describe('Import', async () => { let lectureJson: ILecture; const importResult = await chai.request(app) - .post(`${BASE_URL}/lecture/${course.id}`) + .post(`${BASE_URL}/lecture/${course._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) .attach('file', fs.readFileSync(tmpLectureFile.path), lecture.name) .catch((err) => err.response); @@ -193,7 +193,7 @@ describe('Import', async () => { should.exist(importResult.body.updatedAt); should.exist(courseJson._id); courseJson.active.should.be.equal(false); - courseJson.courseAdmin.should.be.equal(teacher.id); + courseJson.courseAdmin.should.be.equal(teacher._id.toString()); courseJson.name.startsWith(course.name).should.be.equal(true); courseJson.description.should.be.equal(course.description); courseJson.lectures.should.be.instanceOf(Array).and.have.lengthOf(course.lectures.length); diff --git a/api/test/integration/course.ts b/api/test/integration/course.ts index d30759fec..e19092c96 100644 --- a/api/test/integration/course.ts +++ b/api/test/integration/course.ts @@ -93,7 +93,7 @@ describe('Course', () => { name: 'Test Course', description: 'Test description', active: true, - courseAdmin: teacher.id + courseAdmin: teacher._id }); const savedCourse = await testData.save(); @@ -113,7 +113,7 @@ describe('Course', () => { name: 'Test Course', description: 'Test description', active: true, - courseAdmin: teacher[0].id + courseAdmin: teacher[0]._id }); const savedCourse = await testData.save(); @@ -134,14 +134,14 @@ describe('Course', () => { name: 'Test Course Update', description: 'Test description update', active: true, - courseAdmin: teacher.id + courseAdmin: teacher._id }); const testData = new Course( { name: 'Test Course', description: 'Test description', active: false, - courseAdmin: teacher.id + courseAdmin: teacher._id }); const savedCourse = await testData.save(); testDataUpdate._id = savedCourse._id; From 22e53dd3a6ba860903a245f68ecf2492ab42cd55 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 17 Jan 2018 14:38:35 +0100 Subject: [PATCH 040/124] Course Edit Module Code Style --- .../src/app/course/course-edit/course-edit.module.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts index f57c664a5..aad4ca6f1 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts @@ -2,7 +2,6 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {MembersComponent} from './members/members.component'; import {CourseUserListComponent} from './course-user-list/course-user-list.component'; -import {CourseManageContentComponent} from './course-manage-content/course-manage-content.component'; import {CourseEditComponent} from './course-edit.component'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FileUploadModule} from 'ng2-file-upload'; @@ -12,9 +11,8 @@ import {MatFabMenuComponent} from '../../shared/components/mat-fab-menu/mat-fab- import {SharedModule} from '../../shared/shared.module'; import {CourseEditRoutingModule} from './course-edit-routing.module'; import {GeneralTabComponent} from './general-tab/general-tab.component'; -import {CourseManageContentModule} from './course-manage-content/course-manage-content.module'; -import { CourseMediaComponent } from './course-media/course-media.component'; -import { CourseMediamanagerDetailDialog } from './course-media/course-media-detail-dialog/course-mediamanager-detail.dialog'; +import {CourseMediaComponent} from './course-media/course-media.component'; +import {CourseMediamanagerDetailDialog} from './course-media/course-media-detail-dialog/course-mediamanager-detail.dialog'; @NgModule({ imports: [ From 4a759f8283a3384b0adbf4be0f86e086ad3bc5d6 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 17 Jan 2018 14:39:04 +0100 Subject: [PATCH 041/124] Makes media file upload work --- .../course-media/course-media.component.html | 2 +- .../course-media/course-media.component.ts | 19 +++++++++++ .../upload-form-dialog.component.html | 14 +++++++- .../upload-form-dialog.component.spec.ts | 12 +++---- .../upload-form-dialog.component.ts | 33 ++++++++++++++----- .../src/app/shared/shared.module.ts | 8 +++-- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 3946c246b..239e29530 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -25,7 +25,7 @@
- + +

diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts index f47770683..f60ce0381 100644 --- a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { UploadFormDialogComponent } from './upload-form-dialog.component'; +import { UploadFormDialog } from './upload-form-dialog.component'; -describe('UploadFormDialogComponent', () => { - let component: UploadFormDialogComponent; - let fixture: ComponentFixture; +describe('UploadFormDialog', () => { + let component: UploadFormDialog; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ UploadFormDialogComponent ] + declarations: [ UploadFormDialog ] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(UploadFormDialogComponent); + fixture = TestBed.createComponent(UploadFormDialog); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts index f7ca5e225..967963469 100644 --- a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.ts @@ -1,19 +1,26 @@ -import {Component, OnInit} from '@angular/core'; -import {MatDialogRef, MatSnackBar} from '@angular/material'; +import {Component, Inject, OnInit, ViewChild} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef, MatSnackBar} from '@angular/material'; import {UploadDialog} from '../upload-dialog/upload-dialog.component'; import {IFileUnit} from '../../../../../../../shared/models/units/IFileUnit'; +import {UploadFormComponent} from '../upload-form/upload-form.component'; @Component({ selector: 'app-upload-form-dialog', templateUrl: './upload-form-dialog.component.html', styleUrls: ['./upload-form-dialog.component.scss'] }) -export class UploadFormDialogComponent implements OnInit { +export class UploadFormDialog implements OnInit { - uploadPath = '/'; + @ViewChild(UploadFormComponent) + public uploadForm: UploadFormComponent; + + uploadPathTmp = '/api/media/file/'; + uploadPath: string; constructor(public dialogRef: MatDialogRef, - private snackBar: MatSnackBar) { + private snackBar: MatSnackBar, + @Inject(MAT_DIALOG_DATA) public data: any) { + this.uploadPath = this.uploadPathTmp + data.targetDir._id; } ngOnInit() { @@ -23,14 +30,24 @@ export class UploadFormDialogComponent implements OnInit { } onAllUploaded() { + this.dialogRef.close(true); } - public upload() { - this.dialogRef.close(true); + uploadAll() { + this.uploadForm.fileUploader.uploadAll(); + this.uploadForm.onAllUploaded.subscribe( + result => + this.onAllUploaded() + , error => + this.snackBar.open('Could not upload files', '', {duration: 3000}) + ); } - public cancel() { + cancel() { this.dialogRef.close(false); } + checkSave(): boolean { + return this.uploadForm.fileUploader.queue.length !== 0; + } } diff --git a/app/webFrontend/src/app/shared/shared.module.ts b/app/webFrontend/src/app/shared/shared.module.ts index de7e37c3f..b5f43ba27 100644 --- a/app/webFrontend/src/app/shared/shared.module.ts +++ b/app/webFrontend/src/app/shared/shared.module.ts @@ -15,7 +15,7 @@ import {ButtonSaveCancelComponent} from './components/button-save-cancel/button- import {UploadFormComponent} from './components/upload-form/upload-form.component'; import {FileUploadModule} from 'ng2-file-upload'; import {FilesizePipe} from './pipes/filesize/filesize.pipe'; -import { UploadFormDialogComponent } from './components/upload-form-dialog/upload-form-dialog.component'; +import {UploadFormDialog} from './components/upload-form-dialog/upload-form-dialog.component'; @NgModule({ imports: [ @@ -38,7 +38,7 @@ import { UploadFormDialogComponent } from './components/upload-form-dialog/uploa ButtonSaveCancelComponent, UploadFormComponent, FilesizePipe, - UploadFormDialogComponent, + UploadFormDialog, ], exports: [ GravatarDirective, @@ -54,6 +54,10 @@ import { UploadFormDialogComponent } from './components/upload-form-dialog/uploa ButtonSaveCancelComponent, UploadFormComponent, FilesizePipe, + UploadFormDialog, + ], + entryComponents: [ + UploadFormDialog, ] }) export class SharedModule { From ce78e709d6d873336b42ebf86ff2cdf88456b366 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 15:44:28 +0100 Subject: [PATCH 042/124] add tests --- api/test/controllers/media.ts | 160 ++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 api/test/controllers/media.ts diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts new file mode 100644 index 000000000..7a58f7b9a --- /dev/null +++ b/api/test/controllers/media.ts @@ -0,0 +1,160 @@ +import {Server} from '../../src/server'; +import {FixtureLoader} from '../../fixtures/FixtureLoader'; +import * as chai from 'chai'; +import chaiHttp = require('chai-http'); +import {FixtureUtils} from '../../fixtures/FixtureUtils'; +import {JwtUtils} from '../../src/security/JwtUtils'; +import {Directory} from '../../src/models/mediaManager/Directory'; +import {File} from '../../src/models/mediaManager/File'; +import * as fs from 'fs'; + +chai.use(chaiHttp); +const should = chai.should(); +const app = new Server().app; +const BASE_URL = '/api/media'; +const fixtureLoader = new FixtureLoader(); + +describe('Media', async () => { + // Before each test we reset the database + beforeEach(async () => { + await fixtureLoader.load(); + }); + + describe(`GET ${BASE_URL}`, async () => { + + }); + + describe(`PUT ${BASE_URL}`, async () => { + it('should create a root directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const rootDirectory = new Directory({ + name: 'root' + }); + + const result = await chai.request(app) + .put(`${BASE_URL}/directory`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .send(rootDirectory) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not create root' + + ' -> ' + result.body.message); + result.body.__v.should.equal(0); + result.body.name.should.equal(rootDirectory.name); + result.body.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(0); + result.body.files.should.be.instanceOf(Array).and.lengthOf(0); + }); + + it('should create a sub directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const rootDirectory = await new Directory({ + name: 'root' + }).save(); + + const subDirectory = await new Directory({ + name: 'sub' + }); + + const result = await chai.request(app) + .put(`${BASE_URL}/directory/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .send(subDirectory) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not create subdirectory' + + ' -> ' + result.body.message); + result.body.__v.should.equal(0); + result.body.name.should.equal(subDirectory.name); + result.body.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(0); + result.body.files.should.be.instanceOf(Array) + .and.lengthOf(0); + + const updatedRoot = (await Directory.findById(rootDirectory)); + updatedRoot.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(1) + .and.contains(result.body._id); + }); + + it('should upload a file', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const rootDirectory = await new Directory({ + name: 'root' + }).save(); + + const testFileName = fs.readdirSync('./')[0]; + const testFile = fs.readFileSync(testFileName); + + const result = await chai.request(app) + .put(`${BASE_URL}/file/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .attach('file', testFile, testFileName) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not upload file' + + ' -> ' + result.body.message); + result.body.__v.should.equal(0); + should.exist(result.body._id); + should.exist(result.body.mimeType); + should.exist(result.body.size); + should.exist(result.body.physicalPath); + result.body.name.should.be.equal(testFileName); + + const updatedRoot = (await Directory.findById(rootDirectory)); + updatedRoot.files.should.be.instanceOf(Array) + .and.have.lengthOf(1) + .and.contains(result.body._id); + }); + }); + + describe(`DELETE ${BASE_URL}`, async () => { + it('should delete a directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const rootDirectory = await new Directory({ + name: 'root' + }).save(); + + const result = await chai.request(app) + .del(`${BASE_URL}/directory/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not delete directory' + + ' -> ' + result.body.message); + + should.not.exist(await Directory.findById(rootDirectory)); + // TODO: test if subDirectories and files got deleted + }); + + it('should delete a file', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const file = await new File({ + name: 'root', + physicalPath: 'test/a', + size: 129 + }).save(); + + const result = await chai.request(app) + .del(`${BASE_URL}/file/${file._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not delete file' + + ' -> ' + result.body.message); + + should.not.exist(await File.findById(file)); + // TODO: test if the actual file got deleted + }); + }); +}); From cfd02c2e5d6da44e626b3e9c2612a725dc5da4aa Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 17 Jan 2018 18:22:57 +0100 Subject: [PATCH 043/124] Media Manager cards look good, selecting works --- .../course/course-edit/course-edit.module.ts | 2 - .../course-mediamanager-detail.dialog.html | 14 ---- .../course-mediamanager-detail.dialog.scss | 0 .../course-mediamanager-detail.dialog.spec.ts | 25 ------- .../course-mediamanager-detail.dialog.ts | 20 ------ .../course-media/course-media.component.html | 37 ++++++++-- .../course-media/course-media.component.scss | 70 +++++++++++++++++++ .../course-media/course-media.component.ts | 60 +++++++++++----- .../course-user-list.component.scss | 6 +- 9 files changed, 149 insertions(+), 85 deletions(-) delete mode 100644 app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html delete mode 100644 app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.scss delete mode 100644 app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts delete mode 100644 app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts diff --git a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts index aad4ca6f1..7c479cfef 100644 --- a/app/webFrontend/src/app/course/course-edit/course-edit.module.ts +++ b/app/webFrontend/src/app/course/course-edit/course-edit.module.ts @@ -12,7 +12,6 @@ import {SharedModule} from '../../shared/shared.module'; import {CourseEditRoutingModule} from './course-edit-routing.module'; import {GeneralTabComponent} from './general-tab/general-tab.component'; import {CourseMediaComponent} from './course-media/course-media.component'; -import {CourseMediamanagerDetailDialog} from './course-media/course-media-detail-dialog/course-mediamanager-detail.dialog'; @NgModule({ imports: [ @@ -31,7 +30,6 @@ import {CourseMediamanagerDetailDialog} from './course-media/course-media-detail TeachersComponent, GeneralTabComponent, CourseMediaComponent, - CourseMediamanagerDetailDialog, ], exports: [ CourseEditComponent, diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html deleted file mode 100644 index 8ab62155a..000000000 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.html +++ /dev/null @@ -1,14 +0,0 @@ - - - File Detail - - - - -
-

- Content -

-
diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts deleted file mode 100644 index f61350833..000000000 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CourseMediamanagerDetail.DialogComponent } from './course-media-detail-dialog.component'; - -describe('CourseMediamanagerDetail.DialogComponent', () => { - let component: CourseMediamanagerDetail.DialogComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CourseMediamanagerDetail.DialogComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CourseMediamanagerDetail.DialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts deleted file mode 100644 index a75ad67db..000000000 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media-detail-dialog/course-mediamanager-detail.dialog.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Component, Inject, OnInit} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; - -@Component({ - selector: 'app-course-mediamanager-detail.dialog', - templateUrl: './course-mediamanager-detail.dialog.html', - styleUrls: ['./course-mediamanager-detail.dialog.scss'] -}) -export class CourseMediamanagerDetailDialog implements OnInit { - - constructor(public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any) { } - - ngOnInit() { - } - - onCloseClick(): any { - return this.dialogRef.close(); - } -} diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 239e29530..01a8bdc5b 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -25,19 +25,46 @@
- -
- - + + +
    +
  • {{file.name}}
  • +
  • {{file.size}} Byte
  • +
  • {{file.mimeType}}
  • +
+ +
+ +
+ + + +
+

diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss index c629f364f..a57d51b8a 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss @@ -1,3 +1,5 @@ +@import '../../../../variables'; + $width__left: 15%; .mm-container { @@ -43,3 +45,71 @@ $width__left: 15%; padding-right: 0; } } + +.card button { + width: 184px; + margin-left: 10px; + margin-bottom: 10px; +} + +.text-fit { + min-width: 1em; + width: auto; +} + +.card { + height: 248px; + width: 248px; + float: left; + margin: 10px; + border-radius: 8px; + background: no-repeat 75px 15px; + + &:hover { + background-color: map-get($app-primary, 700);; + } + + &:first-of-type { + margin-left: 0; + } + + &:last-of-type { + margin-right: 0; + } + + ul { + list-style-type: none; + padding: 0; + } + + .card-btn-wrapper { + position: absolute; + bottom: 24px; + } +} + +.card_active { + @extend .card; + background: #4490E2; + + .info { + color: #ffffff; + } +} + +.opacity { + background-color: #000; + opacity: .33; + top: 0; + right: 0; + bottom: 0; + left: 0; + position: absolute; + border-radius: 8px; +} + +.info { + position: absolute; + margin-bottom: 0; + font-size: .9em; +} diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 86b88a029..b122dc32e 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -1,11 +1,12 @@ import {Component, OnInit} from '@angular/core'; import {ICourse} from '../../../../../../../shared/models/ICourse'; -import {CourseMediamanagerDetailDialog} from './course-media-detail-dialog/course-mediamanager-detail.dialog'; import {MatDialog, MatSnackBar} from '@angular/material'; import {CourseService, MediaService} from '../../../shared/services/data.service'; import {ActivatedRoute} from '@angular/router'; import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirectory'; import {UploadFormDialog} from '../../../shared/components/upload-form-dialog/upload-form-dialog.component'; +import {IFile} from '../../../../../../../shared/models/mediaManager/IFile'; +import {DialogService} from '../../../shared/services/dialog.service'; @Component({ selector: 'app-course-mediamanager', @@ -16,9 +17,12 @@ export class CourseMediaComponent implements OnInit { course: ICourse; folderBarVisible: boolean; currentFolder: IDirectory; + selectedFiles: IFile[] = []; + toggleBlocked = false; constructor(public dialog: MatDialog, private mediaService: MediaService, + public dialogService: DialogService, private courseService: CourseService, private route: ActivatedRoute, private snackBar: MatSnackBar) { @@ -44,8 +48,11 @@ export class CourseMediaComponent implements OnInit { await this.courseService.updateItem(request); } + this.course.media = await this.mediaService.getDirectory(this.course.media._id, true); + // Set root dir as current this.currentFolder = this.course.media; + console.log(this.course.media); }, error => { this.snackBar.open('Could not load course', '', {duration: 3000}) @@ -75,21 +82,42 @@ export class CourseMediaComponent implements OnInit { }); } - openDetailDialog(): void { - const dialogRef = this.dialog.open(CourseMediamanagerDetailDialog, { - width: '80vw', - // height: '94vh', - maxWidth: '100vw', - maxHeight: '90vh', - data: { - markdown: this.course - } - }); + isInSelectedFiles(file: IFile) { + return this.selectedFiles.indexOf(file) !== -1; + } + + toggleSelection(file: IFile) { + if (this.toggleBlocked) { + return; + } + const position = this.selectedFiles.indexOf(file); + if (position !== -1) { + this.selectedFiles.splice(position, 1); + } else { + this.selectedFiles.push(file); + } + } + + async removeSelectedFile() { + this.toggleBlocked = true; + const res = await this.dialogService + .confirmRemove('selected files', '', 'course') + .toPromise(); + this.toggleBlocked = false; + if (res) { + this.selectedFiles.forEach(file => console.log('TODO: Remove file', file)); + this.selectedFiles = []; + } + } + + initFileDownload(file: IFile) { + // FIXME: REMOVE split/pop when physical path holds correct value + const url = '/api/uploads/' + file.physicalPath.split('/').pop(); + window.open(url, '_blank'); + this.toggleSelection(file); + } + + renameFile(file: IFile) { - // dialogRef.afterClosed().subscribe(result => { - // if (typeof result !== 'undefined') { - // this.model.markdown = result; - // } - // }); } } diff --git a/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list.component.scss b/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list.component.scss index f99d8561f..40568b9d9 100644 --- a/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list.component.scss @@ -32,10 +32,10 @@ margin: 10px; border-radius: 8px; background: no-repeat 75px 15px; -} -.profile:hover { - background-color: cornsilk; + &:hover { + background-color: cornsilk; + } } .active { From 9d0de6bd6e8c1ed266fdfb03d8dd7b222d279f28 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 23:07:58 +0100 Subject: [PATCH 044/124] refactor Put->Post for create routes --- api/src/controllers/MediaController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index 3536c03f8..257abe4c7 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -1,5 +1,5 @@ import { - Authorized, Body, Delete, Get, JsonController, NotFoundError, Param, Put, + Authorized, Body, Delete, Get, JsonController, NotFoundError, Param, Post, Put, UploadedFile } from 'routing-controllers'; import {Directory} from '../models/mediaManager/Directory'; @@ -50,13 +50,13 @@ export class MediaController { } @Authorized(['teacher', 'admin']) - @Put('/directory') + @Post('/directory') async createRootDirectory(@Body() directory: IDirectory) { const savedDirectory = await new Directory(directory).save(); return savedDirectory.toObject(); } @Authorized(['teacher', 'admin']) - @Put('/directory/:parent') + @Post('/directory/:parent') async createDirectory(@Param('parent') parentDirectoryId: string, @Body() directory: IDirectory) { const savedDirectory = await new Directory(directory).save(); @@ -68,7 +68,7 @@ export class MediaController { } @Authorized(['teacher', 'admin']) - @Put('/file/:parent') + @Post('/file/:parent') async createFile(@Param('parent') parentDirectoryId: string, @UploadedFile('file', {options: uploadOptions}) uploadedFile: any) { const file: IFile = new File({ name: uploadedFile.originalname, From 0e617af6175ebd54037255c90245f383e902d9d5 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 23:08:31 +0100 Subject: [PATCH 045/124] add Put routes for updating directories/files --- api/src/controllers/MediaController.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index 257abe4c7..b96028a9d 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -85,6 +85,22 @@ export class MediaController { return savedFile.toObject(); } + @Authorized(['teacher', 'admin']) + @Put('/directory/:id') + async updateDirectory(@Param('id') directoryId: string, @Body() updatedDirectory: IDirectory) { + const directory = await Directory.findById(directoryId); + directory.set(updatedDirectory); + return directory.save(); + } + + @Authorized(['teacher', 'admin']) + @Put('/file/:id') + async updateFile(@Param('id') fileId: string, @Body() updatedFile: IFile) { + const file = await File.findById(fileId); + file.set(updatedFile); + return file.save(); + } + @Authorized(['teacher', 'admin']) @Delete('/directory/:id') async deleteDirectory(@Param('id') directoryId: string) { From b62b76f2c4095a78934beb3617897f0403e0b4e1 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Wed, 17 Jan 2018 23:51:34 +0100 Subject: [PATCH 046/124] adapt test --- api/test/controllers/media.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts index 7a58f7b9a..e16bdde1f 100644 --- a/api/test/controllers/media.ts +++ b/api/test/controllers/media.ts @@ -24,7 +24,7 @@ describe('Media', async () => { }); - describe(`PUT ${BASE_URL}`, async () => { + describe(`POST ${BASE_URL}`, async () => { it('should create a root directory', async () => { const teacher = await FixtureUtils.getRandomTeacher(); @@ -33,7 +33,7 @@ describe('Media', async () => { }); const result = await chai.request(app) - .put(`${BASE_URL}/directory`) + .post(`${BASE_URL}/directory`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) .send(rootDirectory) .catch((err) => err.response); @@ -60,7 +60,7 @@ describe('Media', async () => { }); const result = await chai.request(app) - .put(`${BASE_URL}/directory/${rootDirectory._id}`) + .post(`${BASE_URL}/directory/${rootDirectory._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) .send(subDirectory) .catch((err) => err.response); @@ -92,7 +92,7 @@ describe('Media', async () => { const testFile = fs.readFileSync(testFileName); const result = await chai.request(app) - .put(`${BASE_URL}/file/${rootDirectory._id}`) + .post(`${BASE_URL}/file/${rootDirectory._id}`) .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) .attach('file', testFile, testFileName) .catch((err) => err.response); From e624d0e338cfff563ff30ba82543f8afbe4e15ea Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 11:42:53 +0100 Subject: [PATCH 047/124] Adds tooltips and better styles --- .../course-media/course-media.component.html | 9 ++++++--- .../course-media/course-media.component.scss | 18 ++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 01a8bdc5b..02d11e5a9 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -54,13 +54,16 @@

- - -
diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss index a57d51b8a..d965a7b89 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss @@ -1,6 +1,6 @@ @import '../../../../variables'; -$width__left: 15%; +$width__left: 30%; .mm-container { display: flex; @@ -57,11 +57,17 @@ $width__left: 15%; width: auto; } +$margin-card: 10px; + +.file-wrapper { + margin-left: -$margin-card; +} + .card { height: 248px; width: 248px; float: left; - margin: 10px; + margin: $margin-card; border-radius: 8px; background: no-repeat 75px 15px; @@ -69,14 +75,6 @@ $width__left: 15%; background-color: map-get($app-primary, 700);; } - &:first-of-type { - margin-left: 0; - } - - &:last-of-type { - margin-right: 0; - } - ul { list-style-type: none; padding: 0; From 58f5f21d027d34a3ded5e553827dd14721c4087d Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 11:54:30 +0100 Subject: [PATCH 048/124] add get tests --- api/src/models/mediaManager/Directory.ts | 13 +++ api/test/controllers/media.ts | 101 +++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index a6c5e47e7..602435836 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -1,5 +1,6 @@ import {IDirectory} from '../../../../shared/models/mediaManager/IDirectory'; import * as mongoose from 'mongoose'; +import {ObjectID} from 'bson'; interface IDirectoryModel extends IDirectory, mongoose.Document { @@ -27,6 +28,18 @@ const directorySchema = new mongoose.Schema({ toObject: { transform: function (doc: IDirectoryModel, ret: any) { ret._id = ret._id.toString(); + ret.subDirectories = ret.subDirectories.map((dir: any) => { + if (!dir._id) { + dir = dir.toString(); + } + return dir; + }); + ret.files = ret.files.map((file: any) => { + if (!file._id) { + file = file.toString(); + } + return file; + }); } }, }); diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts index e16bdde1f..f97252a6f 100644 --- a/api/test/controllers/media.ts +++ b/api/test/controllers/media.ts @@ -21,7 +21,108 @@ describe('Media', async () => { }); describe(`GET ${BASE_URL}`, async () => { + it('should get a directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const file = await new File({ + name: 'root', + physicalPath: 'test/a', + size: 129 + }).save(); + const subDirectory = await new Directory({ + name: 'sub' + }).save(); + const rootDirectory = await new Directory({ + name: 'root', + subDirectories: [subDirectory], + files: [file] + }).save(); + + + const result = await chai.request(app) + .get(`${BASE_URL}/directory/${rootDirectory.id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + result.status.should.be.equal(200, + 'could not get directory' + + ' -> ' + result.body.message); + result.body.name.should.equal(rootDirectory.name); + result.body.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(1) + .and.contains(subDirectory.id); + result.body.files.should.be.instanceOf(Array) + .and.have.lengthOf(1) + .and.contains(file.id); + }); + + it('should get a populated directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const file = await new File({ + name: 'root', + physicalPath: 'test/a', + size: 129 + }).save(); + const subDirectory = await new Directory({ + name: 'sub' + }).save(); + const rootDirectory = await new Directory({ + name: 'root', + subDirectories: [subDirectory], + files: [file] + }).save(); + + + const result = await chai.request(app) + .get(`${BASE_URL}/directory/${rootDirectory.id}/lazy`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not get directory' + + ' -> ' + result.body.message); + result.body._id.should.be.equal(rootDirectory.id); + result.body.name.should.equal(rootDirectory.name); + result.body.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(1); + result.body.subDirectories[0]._id.should.be.equal(subDirectory.id); + result.body.subDirectories[0].name.should.be.equal(subDirectory.name); + result.body.subDirectories[0].subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(subDirectory.subDirectories.length); + result.body.subDirectories[0].files.should.be.instanceOf(Array) + .and.have.lengthOf(subDirectory.files.length); + result.body.files.should.be.instanceOf(Array) + .and.have.lengthOf(1); + result.body.files[0]._id.should.be.equal(file.id); + result.body.files[0].name.should.be.equal(file.name); + result.body.files[0].size.should.be.equal(file.size); + result.body.files[0].physicalPath.should.be.equal(file.physicalPath); + }); + + it('should get a file', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const file = await new File({ + name: 'root', + physicalPath: 'test/a', + size: 129 + }).save(); + + const result = await chai.request(app) + .get(`${BASE_URL}/file/${file.id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not get file' + + ' -> ' + result.body.message); + + result.body._id.should.be.equal(file.id); + result.body.name.should.be.equal(file.name); + result.body.size.should.be.equal(file.size); + result.body.physicalPath.should.be.equal(file.physicalPath); + }); }); describe(`POST ${BASE_URL}`, async () => { From 8e862395c370d6386bdd552c0a86f2d070510c6f Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 12:14:48 +0100 Subject: [PATCH 049/124] fix missing toObject() --- api/src/controllers/MediaController.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index b96028a9d..4dfc1b704 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -90,7 +90,8 @@ export class MediaController { async updateDirectory(@Param('id') directoryId: string, @Body() updatedDirectory: IDirectory) { const directory = await Directory.findById(directoryId); directory.set(updatedDirectory); - return directory.save(); + const savedDirectory = await directory.save(); + return savedDirectory.toObject(); } @Authorized(['teacher', 'admin']) @@ -98,7 +99,8 @@ export class MediaController { async updateFile(@Param('id') fileId: string, @Body() updatedFile: IFile) { const file = await File.findById(fileId); file.set(updatedFile); - return file.save(); + const savedFile = await file.save(); + return savedFile.toObject(); } @Authorized(['teacher', 'admin']) From ca9a66ff385ca3cc09e84b6c84713361a875fcca Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 12:14:55 +0100 Subject: [PATCH 050/124] add put tests --- api/test/controllers/media.ts | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts index f97252a6f..e59c47a46 100644 --- a/api/test/controllers/media.ts +++ b/api/test/controllers/media.ts @@ -215,6 +215,66 @@ describe('Media', async () => { }); }); + describe(`PUT ${BASE_URL}`, async () => { + it('should rename a directory', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const rootDirectory = await new Directory({ + name: 'root' + }).save(); + + const renamedDirectory = rootDirectory; + renamedDirectory.name = 'renamedRoot'; + + + const result = await chai.request(app) + .put(`${BASE_URL}/directory/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .send(renamedDirectory) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not rename directory' + + ' -> ' + result.body.message); + + result.body._id.should.equal(rootDirectory.id); + result.body.name.should.equal(renamedDirectory.name); + result.body.subDirectories.should.be.instanceOf(Array) + .and.have.lengthOf(rootDirectory.subDirectories.length); + result.body.files.should.be.instanceOf(Array) + .and.lengthOf(rootDirectory.files.length); + }); + + it('should rename a file', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const file = await new File({ + name: 'file', + physicalPath: 'test/a', + size: 129 + }).save(); + + const renamedFile = file; + file.name = 'renamedFile'; + + + const result = await chai.request(app) + .put(`${BASE_URL}/file/${file._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .send(renamedFile) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not rename file' + + ' -> ' + result.body.message); + + result.body._id.should.equal(file.id); + result.body.name.should.equal(renamedFile.name); + result.body.physicalPath.should.equal(file.physicalPath); + result.body.size.should.equal(file.size); + }); + }); + describe(`DELETE ${BASE_URL}`, async () => { it('should delete a directory', async () => { const teacher = await FixtureUtils.getRandomTeacher(); From 96b825ce35feec1c81935e694547cd8051caddb0 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 12:40:23 +0100 Subject: [PATCH 051/124] rename physicalPath to link --- api/src/controllers/MediaController.ts | 1 + api/src/models/mediaManager/File.ts | 7 ++++++- api/test/controllers/media.ts | 18 +++++++++--------- .../src/app/models/mediaManager/File.ts | 4 ++-- shared/models/mediaManager/IFile.ts | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/api/src/controllers/MediaController.ts b/api/src/controllers/MediaController.ts index 4dfc1b704..5f8f1ead5 100644 --- a/api/src/controllers/MediaController.ts +++ b/api/src/controllers/MediaController.ts @@ -73,6 +73,7 @@ export class MediaController { const file: IFile = new File({ name: uploadedFile.originalname, physicalPath: uploadedFile.path, + link: uploadedFile.filename, size: uploadedFile.size, mimeType: uploadedFile.mimetype, }); diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index c7430e16e..f3d7371f1 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -2,7 +2,7 @@ import * as mongoose from 'mongoose'; import {IFile} from '../../../../shared/models/mediaManager/IFile'; interface IFileModel extends IFile, mongoose.Document { - + physicalPath: string; } const fileSchema = new mongoose.Schema({ @@ -11,6 +11,9 @@ const fileSchema = new mongoose.Schema({ required: true }, physicalPath: { + type: String + }, + link: { type: String, required: true }, @@ -26,6 +29,8 @@ const fileSchema = new mongoose.Schema({ toObject: { transform: function (doc: IFileModel, ret: any) { ret._id = ret._id.toString(); + delete ret.physicalPath; + return ret; } }, }); diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts index e59c47a46..b25f420a2 100644 --- a/api/test/controllers/media.ts +++ b/api/test/controllers/media.ts @@ -26,7 +26,7 @@ describe('Media', async () => { const file = await new File({ name: 'root', - physicalPath: 'test/a', + link: 'test/a', size: 129 }).save(); const subDirectory = await new Directory({ @@ -61,7 +61,7 @@ describe('Media', async () => { const file = await new File({ name: 'root', - physicalPath: 'test/a', + link: 'test/a', size: 129 }).save(); const subDirectory = await new Directory({ @@ -97,7 +97,7 @@ describe('Media', async () => { result.body.files[0]._id.should.be.equal(file.id); result.body.files[0].name.should.be.equal(file.name); result.body.files[0].size.should.be.equal(file.size); - result.body.files[0].physicalPath.should.be.equal(file.physicalPath); + result.body.files[0].link.should.be.equal(file.link); }); it('should get a file', async () => { @@ -105,7 +105,7 @@ describe('Media', async () => { const file = await new File({ name: 'root', - physicalPath: 'test/a', + link: 'test/a', size: 129 }).save(); @@ -121,7 +121,7 @@ describe('Media', async () => { result.body._id.should.be.equal(file.id); result.body.name.should.be.equal(file.name); result.body.size.should.be.equal(file.size); - result.body.physicalPath.should.be.equal(file.physicalPath); + result.body.link.should.be.equal(file.link); }); }); @@ -205,7 +205,7 @@ describe('Media', async () => { should.exist(result.body._id); should.exist(result.body.mimeType); should.exist(result.body.size); - should.exist(result.body.physicalPath); + should.exist(result.body.link); result.body.name.should.be.equal(testFileName); const updatedRoot = (await Directory.findById(rootDirectory)); @@ -250,7 +250,7 @@ describe('Media', async () => { const file = await new File({ name: 'file', - physicalPath: 'test/a', + link: 'test/a', size: 129 }).save(); @@ -270,7 +270,7 @@ describe('Media', async () => { result.body._id.should.equal(file.id); result.body.name.should.equal(renamedFile.name); - result.body.physicalPath.should.equal(file.physicalPath); + result.body.link.should.equal(file.link); result.body.size.should.equal(file.size); }); }); @@ -301,7 +301,7 @@ describe('Media', async () => { const file = await new File({ name: 'root', - physicalPath: 'test/a', + link: 'test/a', size: 129 }).save(); diff --git a/app/webFrontend/src/app/models/mediaManager/File.ts b/app/webFrontend/src/app/models/mediaManager/File.ts index 343d260fc..82533ec87 100644 --- a/app/webFrontend/src/app/models/mediaManager/File.ts +++ b/app/webFrontend/src/app/models/mediaManager/File.ts @@ -3,14 +3,14 @@ import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; export class File implements IFile { _id: any; name: string; - physicalPath: string; + link: string; size: number; mimeType: string; public File(file: IFile) { this._id = file._id; this.name = file.name; - this.physicalPath = file.physicalPath; + this.link = file.link; this.size = file.size; this.mimeType = file.mimeType; } diff --git a/shared/models/mediaManager/IFile.ts b/shared/models/mediaManager/IFile.ts index fb1ab915b..ed832d571 100644 --- a/shared/models/mediaManager/IFile.ts +++ b/shared/models/mediaManager/IFile.ts @@ -1,7 +1,7 @@ export interface IFile { _id: any; name: string; - physicalPath: string; + link: string; size: number; mimeType: string; } From 0c41cfd3cc48cf80257662c0ca979ee837236cc8 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 12:57:34 +0100 Subject: [PATCH 052/124] cascade removes --- api/src/models/mediaManager/Directory.ts | 18 +++++++++++++++++- api/src/models/mediaManager/File.ts | 12 ++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index 602435836..063bc1740 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -1,6 +1,6 @@ import {IDirectory} from '../../../../shared/models/mediaManager/IDirectory'; +import {File} from './File'; import * as mongoose from 'mongoose'; -import {ObjectID} from 'bson'; interface IDirectoryModel extends IDirectory, mongoose.Document { @@ -44,6 +44,22 @@ const directorySchema = new mongoose.Schema({ }, }); +directorySchema.pre('remove', async function(next: () => void) { + for (const subdir of this.subDirectories) { + const model = await Directory.findById(subdir); + if (model) { + await model.remove(); + } + } + for (const file of this.files) { + const model = await File.findById(file); + if (model) { + await model.remove(); + } + } + next(); +}); + const Directory = mongoose.model('Directory', directorySchema); export {Directory, IDirectoryModel}; diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index f3d7371f1..30aee7a37 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -1,5 +1,8 @@ import * as mongoose from 'mongoose'; import {IFile} from '../../../../shared/models/mediaManager/IFile'; +import * as fs from 'fs'; + +const {promisify} = require('util'); interface IFileModel extends IFile, mongoose.Document { physicalPath: string; @@ -35,6 +38,15 @@ const fileSchema = new mongoose.Schema({ }, }); +fileSchema.pre('remove', async function(next: () => void) { + if (fs.existsSync(this.physicalPath)) { + await promisify(fs.unlink)(this.physicalPath); + } + + // TODO: look for references + next(); +}); + const File = mongoose.model('File', fileSchema); export {File, IFileModel}; From 5009ad5a59c3935676ef71ce2d4ff6b9900c1d92 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 13:16:15 +0100 Subject: [PATCH 053/124] disable lint rule --- api/src/models/mediaManager/Directory.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/models/mediaManager/Directory.ts b/api/src/models/mediaManager/Directory.ts index 063bc1740..92b509327 100644 --- a/api/src/models/mediaManager/Directory.ts +++ b/api/src/models/mediaManager/Directory.ts @@ -46,6 +46,8 @@ const directorySchema = new mongoose.Schema({ directorySchema.pre('remove', async function(next: () => void) { for (const subdir of this.subDirectories) { + // linting won't let us use 'Directory' before it is actually declared + // tslint:disable-next-line:no-use-before-declare const model = await Directory.findById(subdir); if (model) { await model.remove(); From 0ee70884b27e5a639fe028ee03137dd59f21dc75 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 13:19:01 +0100 Subject: [PATCH 054/124] fix linting --- api/fixtures/FixtureUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fixtures/FixtureUtils.ts b/api/fixtures/FixtureUtils.ts index 739580b59..f286520cc 100644 --- a/api/fixtures/FixtureUtils.ts +++ b/api/fixtures/FixtureUtils.ts @@ -71,7 +71,7 @@ export class FixtureUtils { } }); } - + public static async getRandomCourse(hash?: string): Promise { const array = await this.getCourses(); return this.getRandom(array, hash); From 64a5f7d27e4db3fbfcb1bb11ba95dd219148006d Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 13:21:30 +0100 Subject: [PATCH 055/124] Changes upload type from post to put --- .../upload-form-dialog/upload-form-dialog.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html index d1c0de44e..cc782ee93 100644 --- a/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html +++ b/app/webFrontend/src/app/shared/components/upload-form-dialog/upload-form-dialog.component.html @@ -1,6 +1,6 @@

Add files

- Date: Thu, 18 Jan 2018 13:22:08 +0100 Subject: [PATCH 056/124] Stylestuff Course Media --- .../course-media/course-media.component.html | 7 ++++++- .../course-media/course-media.component.scss | 19 ++++++++++++++----- .../course-media/course-media.component.ts | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 02d11e5a9..9e90ee5de 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -11,7 +11,10 @@
-

{{ course?.media?.name }}

+

+ folder + {{ course?.media?.name }} +

@@ -45,6 +48,8 @@ +
folder
+
  • {{file.name}}
  • {{file.size}} Byte
  • diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss index d965a7b89..c19cc7b7e 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss @@ -1,6 +1,8 @@ @import '../../../../variables'; +$margin__card: 10px; $width__left: 30%; +$toolbar__height: 50px; .mm-container { display: flex; @@ -29,7 +31,7 @@ $width__left: 30%; min-height: 50px; .mat-toolbar-row { - height: 50px; + height: $toolbar__height; padding: 0 10px; } @@ -57,17 +59,24 @@ $width__left: 30%; width: auto; } -$margin-card: 10px; - .file-wrapper { - margin-left: -$margin-card; + margin-left: -$margin__card; + margin-right: -$margin__card; +} + +.inline-mat-icon { + font-size: 1.2em; +} + +.btn__area { + height: $toolbar__height; } .card { height: 248px; width: 248px; float: left; - margin: $margin-card; + margin: $margin__card; border-radius: 8px; background: no-repeat 75px 15px; diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index b122dc32e..1201983c3 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -29,7 +29,7 @@ export class CourseMediaComponent implements OnInit { } async ngOnInit() { - this.folderBarVisible = true; + this.folderBarVisible = false; this.route.parent.params.subscribe( async (params) => { From 8a9a1fe3e0e6e5d609c696ff06017ea7ea9f2050 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 15:12:28 +0100 Subject: [PATCH 057/124] Adds prettyBytes --- app/webFrontend/package-lock.json | 5 +++++ app/webFrontend/package.json | 1 + .../course-edit/course-media/course-media.component.html | 2 +- .../course-edit/course-media/course-media.component.ts | 5 +++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/webFrontend/package-lock.json b/app/webFrontend/package-lock.json index 2c7f54efd..7d385ad83 100644 --- a/app/webFrontend/package-lock.json +++ b/app/webFrontend/package-lock.json @@ -6846,6 +6846,11 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" + }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", diff --git a/app/webFrontend/package.json b/app/webFrontend/package.json index 8a7a47860..5ce9c7b57 100644 --- a/app/webFrontend/package.json +++ b/app/webFrontend/package.json @@ -42,6 +42,7 @@ "ng2-dragula": "^1.5.0", "ng2-file-upload": "^1.3.0", "node-sass": "^4.7.2", + "pretty-bytes": "^4.0.2", "raven-js": "^3.20.1", "rxjs": "^5.5.5", "zone.js": "^0.8.18" diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 9e90ee5de..f4f76ce39 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -52,7 +52,7 @@
    • {{file.name}}
    • -
    • {{file.size}} Byte
    • +
    • {{bytesHumanReadable(file.size)}}
    • {{file.mimeType}}
    diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 1201983c3..508f849df 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -7,6 +7,7 @@ import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirec import {UploadFormDialog} from '../../../shared/components/upload-form-dialog/upload-form-dialog.component'; import {IFile} from '../../../../../../../shared/models/mediaManager/IFile'; import {DialogService} from '../../../shared/services/dialog.service'; +const prettyBytes = require('pretty-bytes'); @Component({ selector: 'app-course-mediamanager', @@ -60,6 +61,10 @@ export class CourseMediaComponent implements OnInit { ); } + bytesHumanReadable(bytes: number): string { + return prettyBytes(bytes); + } + toggleFolderBarVisibility(): void { this.folderBarVisible = !this.folderBarVisible; } From d6960d0809f62c98b450ab7e65f84ff72478b17b Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 15:26:52 +0100 Subject: [PATCH 058/124] Introduces icons on MediaManager Cards --- .../course-media/course-media.component.html | 10 +++++++++- .../course-media/course-media.component.scss | 13 +++++++++++++ .../course-media/course-media.component.ts | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index f4f76ce39..3633bf6c8 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -48,7 +48,15 @@ -
    folder
    +
    + + video + image + pdf + zip + file + +
    • {{file.name}}
    • diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss index c19cc7b7e..8e3641cf2 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss @@ -72,6 +72,19 @@ $toolbar__height: 50px; height: $toolbar__height; } +.icon-wrapper { + left: 45px; + width: 160px; + height: 160px; + position: absolute; + background: center; + border-radius: 80px; + + .mat-icon { + + } +} + .card { height: 248px; width: 248px; diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 508f849df..f364df350 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -7,6 +7,7 @@ import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirec import {UploadFormDialog} from '../../../shared/components/upload-form-dialog/upload-form-dialog.component'; import {IFile} from '../../../../../../../shared/models/mediaManager/IFile'; import {DialogService} from '../../../shared/services/dialog.service'; + const prettyBytes = require('pretty-bytes'); @Component({ @@ -125,4 +126,20 @@ export class CourseMediaComponent implements OnInit { renameFile(file: IFile) { } + + getSimpleMimeType(file: IFile): string { + const mimeType = file.mimeType.toLowerCase(); + + if (mimeType.startsWith('video')) { + return 'video'; + } else if (mimeType.startsWith('image')) { + return 'image'; + } else if (mimeType.startsWith('pdf')) { + return 'pdf'; + } else if (mimeType.startsWith('zip')) { + return 'zip'; + } else { + return 'unknown'; + } + } } From bf4de1fd1522660792ed3a8d49f5b3a0adf06ba7 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 18:19:31 +0100 Subject: [PATCH 059/124] add migrations --- api/src/migrations/scripts/fileUnit.ts | 90 ++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 api/src/migrations/scripts/fileUnit.ts diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts new file mode 100644 index 000000000..143eedead --- /dev/null +++ b/api/src/migrations/scripts/fileUnit.ts @@ -0,0 +1,90 @@ +// tslint:disable:no-console +import * as mongoose from 'mongoose'; +import {ObjectID} from 'bson'; +import {IUnitModel} from '../../models/units/Unit'; +import {ITaskUnitModel} from '../../models/units/TaskUnit'; +import {ITaskUnit} from '../../../../shared/models/units/ITaskUnit'; +import {ITask} from '../../../../shared/models/task/ITask'; +import {IUnit} from '../../../../shared/models/units/IUnit'; + +const unitSchema = new mongoose.Schema({ + _course: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Course' + }, + name: { + type: String, + required: true + }, + description: { + type: String + }, + progressable: { + type: Boolean + }, + weight: { + type: Number + } + }, + { + collection: 'units', + timestamps: true, + toObject: { + transform: function (doc: IUnitModel, ret: any) { + ret._id = doc._id.toString(); + ret._course = ret._course.toString(); + } + }, + } +); + +const Unit = mongoose.model('Unit', unitSchema); + +const fileUnitSchema = new mongoose.Schema({ + files: [ + { + path: { + type: String, + }, + name: { + type: String, + }, + alias: { + type: String, + }, + size: { + type: Number + } + } + ], + fileUnitType: { + type: String, + required: true + } +}, { + toObject: { + transform: function (doc: any, ret: any) { + ret._id = ret._id.toString(); + ret.files = ret.files.map((file: any) => { + file._id = file._id.toString(); + return file; + }); + ret._course = ret._course.toString(); + } + }, +}); + +const FileUnit = mongoose.model('File', fileUnitSchema); + +class FileUnitMigration { + + async up() { + console.log('FileUnit up was called'); + } + + down() { + console.log('FileUnit down was called'); + } +} + +export = FileUnitMigration; From 80f9877b254b1d1ce7c5ea65111441e545e1363a Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 19:06:05 +0100 Subject: [PATCH 060/124] Removes deprecated HttpModule, instead loading HttpClientModule --- app/webFrontend/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/webFrontend/src/app/app.module.ts b/app/webFrontend/src/app/app.module.ts index 439cbddda..85e44ccf1 100644 --- a/app/webFrontend/src/app/app.module.ts +++ b/app/webFrontend/src/app/app.module.ts @@ -1,6 +1,6 @@ import {BrowserModule} from '@angular/platform-browser'; import {ErrorHandler, NgModule} from '@angular/core'; -import {HttpModule} from '@angular/http'; +import {HttpClientModule} from '@angular/common/http'; import {AppComponent} from './app.component'; import {RavenErrorHandler} from './shared/services/raven-error-handler.service'; import {UserService} from './shared/services/user.service'; @@ -44,7 +44,7 @@ import {DataSharingService} from './shared/services/data-sharing.service'; ], imports: [ BrowserModule, - HttpModule, + HttpClientModule, AppRoutingModule, BrowserAnimationsModule, StartModule, From bab57bb3d87a57c4d63af286d281a99ea5181030 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 19:18:02 +0100 Subject: [PATCH 061/124] Fixes switch case in course media html --- .../course/course-edit/course-media/course-media.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 3633bf6c8..c56080e25 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -54,7 +54,7 @@ image pdf zip - file + file
From 1427da6e9eaf83ca0ca6e96d8703377898be5f96 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 19:18:32 +0100 Subject: [PATCH 062/124] TEMPORARY: Adds tslint:diable:no-console for course-media.ts --- .../course/course-edit/course-media/course-media.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index f364df350..6cde4b0c5 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -1,3 +1,5 @@ +// tslint:disable:no-console + import {Component, OnInit} from '@angular/core'; import {ICourse} from '../../../../../../../shared/models/ICourse'; import {MatDialog, MatSnackBar} from '@angular/material'; @@ -118,7 +120,7 @@ export class CourseMediaComponent implements OnInit { initFileDownload(file: IFile) { // FIXME: REMOVE split/pop when physical path holds correct value - const url = '/api/uploads/' + file.physicalPath.split('/').pop(); + const url = '/api/uploads/' + file.link.split('/').pop(); window.open(url, '_blank'); this.toggleSelection(file); } From 60db39e3bdd6b20adaa23a887eb12f55e067beee Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 19:34:41 +0100 Subject: [PATCH 063/124] Cleans up .travis.yml --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6babdb86..a863131a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ # required for chrome to install dist: trusty -# required for e2e tests to complete succesfully (page crash???) +# required for e2e tests to complete successfully (page crash???) sudo: required language: node_js node_js: @@ -28,10 +28,8 @@ branches: only: - develop - master - # This matches tags wit a format of v{MAJOR}.{MINOR}.{FIX} were major minor and fix is supposed to be a number. + # This matches tags wit a format of v{MAJOR}.{MINOR}.{FIX} were major, minor and fix is supposed to be a number. - /^v(\d+).(\d+).(\d+)$/ - # Only for development purposes - # - feature/14-travis-new cache: directories: From 68963540d6aa665636723f7c52e2f670d8b9f746 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Thu, 18 Jan 2018 19:37:14 +0100 Subject: [PATCH 064/124] test cascading for delete --- api/test/controllers/media.ts | 76 ++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/api/test/controllers/media.ts b/api/test/controllers/media.ts index b25f420a2..acfbb2b9a 100644 --- a/api/test/controllers/media.ts +++ b/api/test/controllers/media.ts @@ -6,6 +6,7 @@ import {FixtureUtils} from '../../fixtures/FixtureUtils'; import {JwtUtils} from '../../src/security/JwtUtils'; import {Directory} from '../../src/models/mediaManager/Directory'; import {File} from '../../src/models/mediaManager/File'; +import config from '../../src/config/main'; import * as fs from 'fs'; chai.use(chaiHttp); @@ -13,6 +14,7 @@ const should = chai.should(); const app = new Server().app; const BASE_URL = '/api/media'; const fixtureLoader = new FixtureLoader(); +const appRoot = require('app-root-path'); describe('Media', async () => { // Before each test we reset the database @@ -279,8 +281,67 @@ describe('Media', async () => { it('should delete a directory', async () => { const teacher = await FixtureUtils.getRandomTeacher(); + const subDirectory = await new Directory({ + name: 'sub' + }).save(); const rootDirectory = await new Directory({ - name: 'root' + name: 'root', + subDirectories: [subDirectory], + }).save(); + + const result = await chai.request(app) + .del(`${BASE_URL}/directory/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not delete directory' + + ' -> ' + result.body.message); + + should.not.exist(await Directory.findById(rootDirectory)); + }); + + it('should delete a directory and its subdirectories', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const subDirectory = await new Directory({ + name: 'sub' + }).save(); + const rootDirectory = await new Directory({ + name: 'root', + subDirectories: [subDirectory], + }).save(); + + const result = await chai.request(app) + .del(`${BASE_URL}/directory/${rootDirectory._id}`) + .set('Authorization', `JWT ${JwtUtils.generateToken(teacher)}`) + .catch((err) => err.response); + + result.status.should.be.equal(200, + 'could not delete directory' + + ' -> ' + result.body.message); + + should.not.exist(await Directory.findById(rootDirectory)); + should.not.exist(await Directory.findById(subDirectory)); + }); + + + it('should delete a directory and its files', async () => { + const teacher = await FixtureUtils.getRandomTeacher(); + + const testFileName = fs.readdirSync('./')[0]; + const testFile = fs.readFileSync(testFileName); + fs.copyFileSync(testFileName, config.uploadFolder + '/test.file'); + + const file = await new File({ + name: 'root', + physicalPath: config.uploadFolder + '/test.file', + link: testFileName, + size: testFile.length + }).save(); + const rootDirectory = await new Directory({ + name: 'root', + files: [file] }).save(); const result = await chai.request(app) @@ -293,16 +354,21 @@ describe('Media', async () => { ' -> ' + result.body.message); should.not.exist(await Directory.findById(rootDirectory)); - // TODO: test if subDirectories and files got deleted + should.not.exist(await File.findById(file)); }); it('should delete a file', async () => { const teacher = await FixtureUtils.getRandomTeacher(); + const testFileName = fs.readdirSync('./')[0]; + const testFile = fs.readFileSync(testFileName); + fs.copyFileSync(testFileName, config.uploadFolder + '/test.file'); + const file = await new File({ name: 'root', - link: 'test/a', - size: 129 + physicalPath: config.uploadFolder + '/test.file', + link: testFileName, + size: testFile.length }).save(); const result = await chai.request(app) @@ -315,7 +381,7 @@ describe('Media', async () => { ' -> ' + result.body.message); should.not.exist(await File.findById(file)); - // TODO: test if the actual file got deleted + fs.existsSync(config.uploadFolder + '/test.file').should.be.equal(false); }); }); }); From 9d0b52fc0243e79ca1c4217af687f2fe83808ff4 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 18 Jan 2018 22:56:49 +0100 Subject: [PATCH 065/124] Moves card style to shared file --- .../course-media/course-media.component.html | 6 +- .../course-media/course-media.component.scss | 74 +------------------ .../course-media/course-media.component.ts | 12 ++- .../src/app/shared/services/data.service.ts | 6 +- app/webFrontend/src/styles/_card.scss | 73 ++++++++++++++++++ 5 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 app/webFrontend/src/styles/_card.scss diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index c56080e25..67d26ab8a 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -50,10 +50,10 @@
- video + music_video image - pdf - zip + picture_as_pdf + archive file
diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss index 8e3641cf2..d9384a23f 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.scss @@ -1,4 +1,5 @@ @import '../../../../variables'; +@import '../../../../styles/card'; $margin__card: 10px; $width__left: 30%; @@ -48,17 +49,6 @@ $toolbar__height: 50px; } } -.card button { - width: 184px; - margin-left: 10px; - margin-bottom: 10px; -} - -.text-fit { - min-width: 1em; - width: auto; -} - .file-wrapper { margin-left: -$margin__card; margin-right: -$margin__card; @@ -71,65 +61,3 @@ $toolbar__height: 50px; .btn__area { height: $toolbar__height; } - -.icon-wrapper { - left: 45px; - width: 160px; - height: 160px; - position: absolute; - background: center; - border-radius: 80px; - - .mat-icon { - - } -} - -.card { - height: 248px; - width: 248px; - float: left; - margin: $margin__card; - border-radius: 8px; - background: no-repeat 75px 15px; - - &:hover { - background-color: map-get($app-primary, 700);; - } - - ul { - list-style-type: none; - padding: 0; - } - - .card-btn-wrapper { - position: absolute; - bottom: 24px; - } -} - -.card_active { - @extend .card; - background: #4490E2; - - .info { - color: #ffffff; - } -} - -.opacity { - background-color: #000; - opacity: .33; - top: 0; - right: 0; - bottom: 0; - left: 0; - position: absolute; - border-radius: 8px; -} - -.info { - position: absolute; - margin-bottom: 0; - font-size: .9em; -} diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 6cde4b0c5..143d0b61f 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -1,5 +1,3 @@ -// tslint:disable:no-console - import {Component, OnInit} from '@angular/core'; import {ICourse} from '../../../../../../../shared/models/ICourse'; import {MatDialog, MatSnackBar} from '@angular/material'; @@ -56,6 +54,7 @@ export class CourseMediaComponent implements OnInit { // Set root dir as current this.currentFolder = this.course.media; + // tslint:disable-next-line:no-console console.log(this.course.media); }, error => { @@ -85,6 +84,7 @@ export class CourseMediaComponent implements OnInit { dialogRef.afterClosed().subscribe(value => { if (value) { // TODO: Reload folder content + // tslint:disable-next-line:no-console console.log('Reload site, folder not yet updated'); } }); @@ -113,6 +113,7 @@ export class CourseMediaComponent implements OnInit { .toPromise(); this.toggleBlocked = false; if (res) { + // tslint:disable-next-line:no-console this.selectedFiles.forEach(file => console.log('TODO: Remove file', file)); this.selectedFiles = []; } @@ -132,14 +133,17 @@ export class CourseMediaComponent implements OnInit { getSimpleMimeType(file: IFile): string { const mimeType = file.mimeType.toLowerCase(); + // tslint:disable-next-line:no-console + console.log('MIME: ' + mimeType); + if (mimeType.startsWith('video')) { return 'video'; } else if (mimeType.startsWith('image')) { return 'image'; - } else if (mimeType.startsWith('pdf')) { + } else if (mimeType === 'application/pdf') { return 'pdf'; } else if (mimeType.startsWith('zip')) { - return 'zip'; + return 'archive'; } else { return 'unknown'; } diff --git a/app/webFrontend/src/app/shared/services/data.service.ts b/app/webFrontend/src/app/shared/services/data.service.ts index f6743fa69..8cd7c8e59 100644 --- a/app/webFrontend/src/app/shared/services/data.service.ts +++ b/app/webFrontend/src/app/shared/services/data.service.ts @@ -237,13 +237,13 @@ export class MediaService extends DataService { createRootDir(rootDirName: string): Promise { // TODO: If this works, apply to all - return this.backendService.put(this.apiPath + 'directory', JSON.stringify({name: rootDirName})) + return this.backendService.post(this.apiPath + 'directory', JSON.stringify({name: rootDirName})) .toPromise(); } createDir(newDirName: string, parentDir: IDirectory): Promise { return new Promise((resolve, reject) => { - this.backendService.put(this.apiPath + 'directory/' + parentDir._id, JSON.stringify({name: newDirName})) + this.backendService.post(this.apiPath + 'directory/' + parentDir._id, JSON.stringify({name: newDirName})) .subscribe((responseItem: IDirectory) => { resolve(responseItem); }, @@ -254,7 +254,7 @@ export class MediaService extends DataService { addFile(directory: IDirectory): Promise { return new Promise((resolve, reject) => { - this.backendService.put(this.apiPath + 'file/' + directory._id, JSON.stringify({})) + this.backendService.post(this.apiPath + 'file/' + directory._id, JSON.stringify({})) .subscribe((responseItem: IFile) => { resolve(responseItem); }, diff --git a/app/webFrontend/src/styles/_card.scss b/app/webFrontend/src/styles/_card.scss new file mode 100644 index 000000000..8df77485e --- /dev/null +++ b/app/webFrontend/src/styles/_card.scss @@ -0,0 +1,73 @@ +.card { + height: 248px; + width: 248px; + float: left; + margin: 10px; + border-radius: 8px; + background: no-repeat 75px 15px; + + &:hover { + background-color: map-get($app-primary, 700); + } + + ul { + list-style-type: none; + padding: 0; + } + + .card-btn-wrapper { + .mat-raised-button { + width: 184px; + margin-left: 10px; + margin-bottom: 10px; + } + } + + .opacity { + background-color: #000; + opacity: .33; + top: 0; + right: 0; + bottom: 0; + left: 0; + position: absolute; + border-radius: 8px; + } + + .info { + position: absolute; + bottom: 10px; + left: 10px; + margin-bottom: 0; + font-size: .9em; + } + + .text-fit { + min-width: 1em; + width: auto; + } + + .icon-wrapper { + $icon-wrapper-size: 140px; + left: 45px; + width: $icon-wrapper-size; + height: $icon-wrapper-size; + position: absolute; + background: center; + border-radius: 50%; + + .mat-icon { + height: 100%; + width: 100%; + font-size: $icon-wrapper-size - 20px; + line-height: $icon-wrapper-size - 20px; + text-align: center; + } + } +} + +.card_active { + @extend .card; + background-color: map-get($app-primary, 800);; +} + From 87e23562716bc0c81f66f5ab78be710058db72d2 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Fri, 19 Jan 2018 00:09:37 +0100 Subject: [PATCH 066/124] Adapts icons and style of mediamanager --- .../course-edit/course-media/course-media.component.html | 5 +++-- app/webFrontend/src/styles/_card.scss | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 67d26ab8a..f1a4b1369 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -50,11 +50,12 @@
- music_video + movie image picture_as_pdf archive - file + subject + insert_drive_file
diff --git a/app/webFrontend/src/styles/_card.scss b/app/webFrontend/src/styles/_card.scss index 8df77485e..860c8cedd 100644 --- a/app/webFrontend/src/styles/_card.scss +++ b/app/webFrontend/src/styles/_card.scss @@ -55,6 +55,7 @@ position: absolute; background: center; border-radius: 50%; + user-select: none; .mat-icon { height: 100%; From 1a6d0fab0128707847b91d19d99b5f97fcaea0e7 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Fri, 19 Jan 2018 00:10:51 +0100 Subject: [PATCH 067/124] Implements remove, sort, changeDir in mediamanager --- .../course-media/course-media.component.ts | 82 +++++++++++++------ 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 143d0b61f..4a5784428 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -43,19 +43,10 @@ export class CourseMediaComponent implements OnInit { // Root dir does not exist, add one this.course.media = await this.mediaService.createRootDir(this.course.name); // Update course - const request: any = { - '_id': this.course._id, - 'media': this.course.media, - }; - await this.courseService.updateItem(request); + this.course = await this.courseService.updateItem(this.course); } - this.course.media = await this.mediaService.getDirectory(this.course.media._id, true); - - // Set root dir as current - this.currentFolder = this.course.media; - // tslint:disable-next-line:no-console - console.log(this.course.media); + await this.changeDirectory(this.course.media._id, true); }, error => { this.snackBar.open('Could not load course', '', {duration: 3000}) @@ -63,6 +54,34 @@ export class CourseMediaComponent implements OnInit { ); } + async reloadDirectory() { + await this.changeDirectory(this.currentFolder._id, true); + } + + async changeDirectory(mediaId: string, lazy: boolean = false) { + // Set current dir + this.currentFolder = await this.mediaService.getDirectory(mediaId, lazy); + + // Define Sort function to sort by name + const sortFnc = (a, b): number => { + const aName = a.name.toLowerCase(); + const bName = b.name.toLowerCase(); + + if (aName < bName) { + return -1; + } else if (aName > bName) { + return 1; + } + return 0; + }; + + // Sort files by name + this.currentFolder.files.sort(sortFnc); + + // Sort subdirs by name + this.currentFolder.subDirectories.sort(sortFnc); + } + bytesHumanReadable(bytes: number): string { return prettyBytes(bytes); } @@ -83,9 +102,8 @@ export class CourseMediaComponent implements OnInit { dialogRef.afterClosed().subscribe(value => { if (value) { - // TODO: Reload folder content - // tslint:disable-next-line:no-console - console.log('Reload site, folder not yet updated'); + // Reload current folder + this.changeDirectory(this.currentFolder._id, true); } }); } @@ -113,28 +131,44 @@ export class CourseMediaComponent implements OnInit { .toPromise(); this.toggleBlocked = false; if (res) { - // tslint:disable-next-line:no-console - this.selectedFiles.forEach(file => console.log('TODO: Remove file', file)); + let failed = false; + this.selectedFiles.forEach(async file => { + await this.mediaService.deleteFile(file) + .catch(reason => { + this.snackBar.open('Could not remove file: ' + file.name, '', {duration: 2000}); + failed = true; + }); + }); + if (!failed) { + this.snackBar.open('Removed all selected files', '', {duration: 3000}); + } this.selectedFiles = []; + await this.reloadDirectory(); } } initFileDownload(file: IFile) { - // FIXME: REMOVE split/pop when physical path holds correct value - const url = '/api/uploads/' + file.link.split('/').pop(); + const url = '/api/uploads/' + file.link; window.open(url, '_blank'); this.toggleSelection(file); } - renameFile(file: IFile) { + async renameFile(file: IFile) { + await this.reloadDirectory(); } getSimpleMimeType(file: IFile): string { const mimeType = file.mimeType.toLowerCase(); - - // tslint:disable-next-line:no-console - console.log('MIME: ' + mimeType); + const archives = [ + 'application/x-bzip', + 'application/x-bzip2', + 'application/x-rar-compressed', + 'application/x-tar', + 'application/x-zip-compressed', + 'application/zip', + 'application/x-7z-compressed', + ]; if (mimeType.startsWith('video')) { return 'video'; @@ -142,8 +176,10 @@ export class CourseMediaComponent implements OnInit { return 'image'; } else if (mimeType === 'application/pdf') { return 'pdf'; - } else if (mimeType.startsWith('zip')) { + } else if (archives.indexOf(mimeType) >= 0) { return 'archive'; + } else if (mimeType.startsWith('text')) { + return 'text'; } else { return 'unknown'; } From bec2c5884aa17c7f2ca7fa871be017797b7e7dbf Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Fri, 19 Jan 2018 00:42:21 +0100 Subject: [PATCH 068/124] Course User List Overview now uses shared card styling User-Image-Directive refactored --- .../course-media/course-media.component.html | 4 +- .../course-user-list-overview.component.html | 6 +- .../course-user-list-overview.component.scss | 74 ++----------------- .../shared/directives/user-image.directive.ts | 8 +- .../user-profile/user-profile.component.html | 2 +- 5 files changed, 19 insertions(+), 75 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index f1a4b1369..12647015d 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -46,7 +46,9 @@
- +
diff --git a/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list-overview/course-user-list-overview.component.html b/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list-overview/course-user-list-overview.component.html index 7643aeb1e..999eedeaf 100644 --- a/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list-overview/course-user-list-overview.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-user-list/course-user-list-overview/course-user-list-overview.component.html @@ -18,10 +18,12 @@
- -
+
+
+
- + +

+ +

diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts index f79254afc..827c0cae9 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts @@ -1,5 +1,5 @@ -import {Component, OnInit, Input, ViewChild} from '@angular/core'; -import {MatSnackBar} from '@angular/material'; +import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import {MatDialog, MatSnackBar} from '@angular/material'; import {ICourse} from '../../../../../../../shared/models/ICourse'; import {ILecture} from '../../../../../../../shared/models/ILecture'; import {IFileUnit} from '../../../../../../../shared/models/units/IFileUnit'; @@ -9,6 +9,7 @@ import {FileUnit} from '../../../models/units/FileUnit'; import {UploadFormComponent} from '../../../shared/components/upload-form/upload-form.component'; import {ShowProgressService} from '../../../shared/services/show-progress.service'; import {VideoUnit} from '../../../models/units/VideoUnit'; +import {PickMediaDialog} from '../../../shared/components/pick-media-dialog/pick-media-dialog.component'; @Component({ selector: 'app-file-unit-form', @@ -39,7 +40,9 @@ export class FileUnitFormComponent implements OnInit { constructor(public snackBar: MatSnackBar, private unitService: UnitService, - private showProgress: ShowProgressService) { } + private showProgress: ShowProgressService, + private dialog: MatDialog) { + } ngOnInit() { if (!this.model) { @@ -127,4 +130,19 @@ export class FileUnitFormComponent implements OnInit { return true; } } + + async openAddFilesDialog() { + const res = await this.dialog.open(PickMediaDialog, { + data: { + directoryId: this.course.media._id, + }, + }); + + res.afterClosed().subscribe(value => { + console.log(value); + if (value) { + // TODO + } + }); + } } From 9acb44eb84983b87d23609bc9dfd26da7bc810ca Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sun, 21 Jan 2018 11:00:06 +0100 Subject: [PATCH 081/124] Pick Media Dialog little adaptions --- .../pick-media-dialog.component.html | 2 +- .../pick-media-dialog.component.ts | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html index 2c773f0d7..6813610a3 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html @@ -1,7 +1,7 @@

Add files to unit

Files selected: {{fileList.selectedOptions.selected.length}} - + {{file.name}} diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts index 2c5e1b81b..10d033b94 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts @@ -1,5 +1,5 @@ import {Component, Inject, OnInit, ViewChild} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef, MatSelectionList} from '@angular/material'; +import {MAT_DIALOG_DATA, MatDialogRef, MatSelectionList, MatSnackBar} from '@angular/material'; import {IFile} from '../../../../../../../shared/models/mediaManager/IFile'; import {IDirectory} from '../../../../../../../shared/models/mediaManager/IDirectory'; import {MediaService} from '../../services/data.service'; @@ -15,8 +15,9 @@ export class PickMediaDialog implements OnInit { allFiles: IFile[]; @ViewChild('fileList') fileList: MatSelectionList; - constructor(private dialogref: MatDialogRef, + constructor(private dialogRef: MatDialogRef, private mediaService: MediaService, + private snackBar: MatSnackBar, @Inject(MAT_DIALOG_DATA) public data: any) { } @@ -25,20 +26,21 @@ export class PickMediaDialog implements OnInit { this.directory = await this.mediaService.getDirectory(this.data.directoryId, true); this.allFiles = this.directory.files; } else { - this.dialogref.close(false); + this.dialogRef.close(false); } } save(): void { - console.log(this.fileList.selectedOptions); - console.log(this.fileList.selectedOptions.selected); - console.log(this.fileList.selectedOptions.selected.values()); - console.log(this.fileList.selectedOptions.selected.pop()); - this.dialogref.close(this.fileList.selectedOptions); + if (this.fileList.selectedOptions.selected.length === 0) { + this.snackBar.open('Please choose at least one file', '', {duration: 2000}); + return; + } + + this.dialogRef.close(this.fileList.selectedOptions.selected); } cancel(): void { - this.dialogref.close(false); + this.dialogRef.close(false); } } From cef03be82164d8cdc372b3541f933a13e6d8cded Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sun, 21 Jan 2018 11:52:05 +0100 Subject: [PATCH 082/124] Pick MEdia Dialog now works --- .../pick-media-dialog/pick-media-dialog.component.html | 6 +++--- .../pick-media-dialog/pick-media-dialog.component.ts | 5 +++-- app/webFrontend/src/app/shared/shared.module.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html index 6813610a3..96dd3318d 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html @@ -1,8 +1,8 @@

Add files to unit

-Files selected: {{fileList.selectedOptions.selected.length}} +Files selected: {{selectedOptions.length}} - - + + {{file.name}} diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts index 10d033b94..37f910780 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts @@ -14,6 +14,7 @@ export class PickMediaDialog implements OnInit { directory: IDirectory; allFiles: IFile[]; @ViewChild('fileList') fileList: MatSelectionList; + selectedOptions: IFile[] = []; constructor(private dialogRef: MatDialogRef, private mediaService: MediaService, @@ -31,12 +32,12 @@ export class PickMediaDialog implements OnInit { } save(): void { - if (this.fileList.selectedOptions.selected.length === 0) { + if (this.selectedOptions.length === 0) { this.snackBar.open('Please choose at least one file', '', {duration: 2000}); return; } - this.dialogRef.close(this.fileList.selectedOptions.selected); + this.dialogRef.close(this.selectedOptions); } cancel(): void { diff --git a/app/webFrontend/src/app/shared/shared.module.ts b/app/webFrontend/src/app/shared/shared.module.ts index 79eace09e..f1de83c8c 100644 --- a/app/webFrontend/src/app/shared/shared.module.ts +++ b/app/webFrontend/src/app/shared/shared.module.ts @@ -18,7 +18,7 @@ import {FilesizePipe} from './pipes/filesize/filesize.pipe'; import {UploadFormDialog} from './components/upload-form-dialog/upload-form-dialog.component'; import {MarkdownEditorComponent} from './components/markdown-editor/markdown-editor.component'; import {AceEditorModule} from 'ng2-ace-editor'; -import { PickMediaDialog } from './components/pick-media-dialog/pick-media-dialog.component'; +import {PickMediaDialog} from './components/pick-media-dialog/pick-media-dialog.component'; @NgModule({ imports: [ From 736faf904a8da869f8f3234a676bba98e6925f97 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sun, 21 Jan 2018 12:36:04 +0100 Subject: [PATCH 083/124] File Unit Form now works --- .../course-media/course-media.component.ts | 1 + .../file-unit-form.component.html | 2 +- .../file-unit-form.component.ts | 97 +++++++------------ 3 files changed, 35 insertions(+), 65 deletions(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index c1bb50cf2..fa35f60f2 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -85,6 +85,7 @@ export class CourseMediaComponent implements OnInit { this.currentFolder.subDirectories.sort(sortFnc); } + // Todo: Use filesize directive: `1024 | filesize` bytesHumanReadable(bytes: number): string { return prettyBytes(bytes); } diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html index cf199bc70..059d92a02 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html @@ -9,7 +9,7 @@

Files

Actions
- {{file.alias}} + {{file.name}} {{file.size | filesize}}
@@ -31,5 +32,8 @@

Files

save Save - +

diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts index 1f2b9ca55..bbe409f67 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts @@ -36,19 +36,12 @@ export class FileUnitFormComponent implements OnInit { ngOnInit() { if (!this.model) { - switch (this.fileUnitType) { - case 'video': { - this.model = new VideoUnit(this.course); - break; - } - case 'file': { - this.model = new FileUnit(this.course); - break; - } - default: { - this.model = new FileUnit(this.course); - break; - } + if (this.fileUnitType === 'video') { + // 'video' + this.model = new VideoUnit(this.course); + } else { + // default or 'file' + this.model = new FileUnit(this.course); } } } @@ -102,6 +95,12 @@ export class FileUnitFormComponent implements OnInit { const res = await this.dialog.open(PickMediaDialog, { data: { directoryId: this.course.media._id, + allowedMimeTypes: [ + 'video/mp4', + 'video/webm', + 'video/ogg', + 'video/avi', + ], }, }); From db189bc3c6df913a271c17c6be3efc22e8c467bb Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 23 Jan 2018 23:02:25 +0100 Subject: [PATCH 093/124] Renames code-kata fab btn --- .../course-manage-content/course-manage-content.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/course/course-edit/course-manage-content/course-manage-content.component.html b/app/webFrontend/src/app/course/course-edit/course-manage-content/course-manage-content.component.html index c79d2f7cd..5ffa42078 100644 --- a/app/webFrontend/src/app/course/course-edit/course-manage-content/course-manage-content.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-manage-content/course-manage-content.component.html @@ -21,7 +21,7 @@
Import unit
Add tasks
Add files
-
Add Code-Kata
+
Add code-kata
Add videos
Add free text
Import lecture
From 1fc3be4b1d856b07c178c3d5d599c5e139a4878a Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 23 Jan 2018 23:35:42 +0100 Subject: [PATCH 094/124] Makes VideoUpload work --- .../pick-media-dialog.component.html | 11 ++++++----- .../pick-media-dialog.component.scss | 5 +++++ .../pick-media-dialog.component.ts | 4 +--- .../file-unit-form/file-unit-form.component.html | 5 ++++- .../file-unit-form/file-unit-form.component.ts | 14 ++++++++------ 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html index bfee2e97a..76798862e 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.html @@ -1,15 +1,16 @@

Add files to unit

-

Files selected: {{ selectedOptions.length }}

- -

Allowed MimeTypes:

+ + Allowed MimeTypes:
    -
  • {{ type }}
  • +
  • {{ type }}
+Files selected: {{ selectedOptions.length }} +
- + {{ file.name }} diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.scss b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.scss index 072193297..cdbc0501f 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.scss +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.scss @@ -1,3 +1,8 @@ .mat-dialog-actions { float: right; } + +ul { + margin: 0; + padding-left: 1.5em; +} diff --git a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts index b7aaaee42..badfbb3e7 100644 --- a/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts +++ b/app/webFrontend/src/app/shared/components/pick-media-dialog/pick-media-dialog.component.ts @@ -15,7 +15,7 @@ export class PickMediaDialog implements OnInit { allFiles: IFile[]; @ViewChild('fileList') fileList: MatSelectionList; selectedOptions: IFile[] = []; - allowedMimeTypes: string[] = []; + allowedMimeTypes: string[]; constructor(private dialogRef: MatDialogRef, private mediaService: MediaService, @@ -33,8 +33,6 @@ export class PickMediaDialog implements OnInit { if (this.data.allowedMimeTypes) { this.allowedMimeTypes = this.data.allowedMimeTypes; - } else { - this.dialogRef.close(false); } } diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html index f1451dbf7..4923f16e2 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.html @@ -1,4 +1,7 @@ -

Files

+ +

Add Videos

+

Add Files

+
diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts index bbe409f67..bb20de52d 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts @@ -92,15 +92,17 @@ export class FileUnitFormComponent implements OnInit { return; } + const allowedMimeTypes = (this.fileUnitType !== 'video') ? undefined : [ + 'video/mp4', + 'video/webm', + 'video/ogg', + 'video/avi', + ]; + const res = await this.dialog.open(PickMediaDialog, { data: { directoryId: this.course.media._id, - allowedMimeTypes: [ - 'video/mp4', - 'video/webm', - 'video/ogg', - 'video/avi', - ], + allowedMimeTypes: allowedMimeTypes, }, }); From 8ece732d6a33a5df45b246c1c1645a70d8960b72 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 23 Jan 2018 23:58:05 +0100 Subject: [PATCH 095/124] Removes pretty-bytes, using pipe: filsize --- app/webFrontend/package-lock.json | 13 ++++--------- app/webFrontend/package.json | 1 - .../course-media/course-media.component.html | 2 +- .../course-media/course-media.component.ts | 7 ------- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/app/webFrontend/package-lock.json b/app/webFrontend/package-lock.json index 45e563b5c..2396eac79 100644 --- a/app/webFrontend/package-lock.json +++ b/app/webFrontend/package-lock.json @@ -3417,7 +3417,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -4437,7 +4437,7 @@ "istanbul-lib-coverage": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz", - "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==", + "integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=", "dev": true }, "istanbul-lib-hook": { @@ -5326,7 +5326,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.8" } @@ -5460,7 +5460,7 @@ "ng2-dragula": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/ng2-dragula/-/ng2-dragula-1.5.0.tgz", - "integrity": "sha512-uSVq66Rv+ZhDLBGYCGZ7mTaseP7rvYJOijiQZlzfy8dxL614Sw7rhtnLqvK8nqa3tI/wVv8CEGZaZkMnWJokwQ==", + "integrity": "sha1-EqIh16NPxinMdNyY77JkSCkOtuU=", "requires": { "dragula": "3.7.2" } @@ -6851,11 +6851,6 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, - "pretty-bytes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", - "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", diff --git a/app/webFrontend/package.json b/app/webFrontend/package.json index f3a0dc55b..73b4e4df4 100644 --- a/app/webFrontend/package.json +++ b/app/webFrontend/package.json @@ -44,7 +44,6 @@ "ng2-dragula": "^1.5.0", "ng2-file-upload": "^1.3.0", "node-sass": "^4.7.2", - "pretty-bytes": "^4.0.2", "raven-js": "^3.20.1", "rxjs": "^5.5.5", "zone.js": "^0.8.18" diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html index 12647015d..ce23fecfa 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.html @@ -63,7 +63,7 @@
  • {{file.name}}
  • -
  • {{bytesHumanReadable(file.size)}}
  • +
  • {{file.size | filesize}}
  • {{file.mimeType}}
diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index fa35f60f2..6b613f864 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -9,8 +9,6 @@ import {IFile} from '../../../../../../../shared/models/mediaManager/IFile'; import {DialogService} from '../../../shared/services/dialog.service'; import {RenameDialogComponent} from '../../../shared/components/rename-dialog/rename-dialog.component'; -const prettyBytes = require('pretty-bytes'); - @Component({ selector: 'app-course-mediamanager', templateUrl: './course-media.component.html', @@ -85,11 +83,6 @@ export class CourseMediaComponent implements OnInit { this.currentFolder.subDirectories.sort(sortFnc); } - // Todo: Use filesize directive: `1024 | filesize` - bytesHumanReadable(bytes: number): string { - return prettyBytes(bytes); - } - toggleFolderBarVisibility(): void { this.folderBarVisible = !this.folderBarVisible; } From a7c1b8814a5d717ed5e93217943b838a39f1453e Mon Sep 17 00:00:00 2001 From: "steffen.grosspersky" Date: Wed, 24 Jan 2018 10:23:51 +0100 Subject: [PATCH 096/124] Fix file upload and download on Download and User controller --- api/src/controllers/DownloadController.ts | 4 ++-- api/src/controllers/UserController.ts | 16 +++++++--------- shared/models/IUser.ts | 2 +- shared/models/mediaManager/IFile.ts | 2 +- shared/models/units/IFileUnit.ts | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/api/src/controllers/DownloadController.ts b/api/src/controllers/DownloadController.ts index f6d75230e..b2766816a 100644 --- a/api/src/controllers/DownloadController.ts +++ b/api/src/controllers/DownloadController.ts @@ -71,7 +71,7 @@ export class DownloadController { fileUnit.files.forEach((file, index) => { if (unit.files.indexOf(index) > -1) { if ((file.size / 1024 ) > config.maxFileSize) { - // TODO: localTooLargeFiles.push(file.path); + localTooLargeFiles.push(file.link); } localTotalSize += (file.size / 1024 ); } @@ -187,7 +187,7 @@ export class DownloadController { const fileUnit = localUnit; fileUnit.files.forEach((file, index) => { if (unit.files.indexOf(index) > -1) { - // TODO: archive.file(file.path, {name: lecCounter + '_' + lcName + '/' + unitCounter + '_' + file.alias}); + archive.file(file.link, {name: lecCounter + '_' + lcName + '/' + unitCounter + '_' + file.name}); } }); } else if (localUnit.__t === 'task') { diff --git a/api/src/controllers/UserController.ts b/api/src/controllers/UserController.ts index 646b7c480..ab8cab802 100644 --- a/api/src/controllers/UserController.ts +++ b/api/src/controllers/UserController.ts @@ -1,6 +1,6 @@ import { Body, JsonController, UseBefore, Get, Param, QueryParam, Put, Delete, Authorized, CurrentUser, - BadRequestError, ForbiddenError, UploadedFile, Post, HttpError + BadRequestError, ForbiddenError, UploadedFile, Post } from 'routing-controllers'; import passportJwtMiddleware from '../security/passportJwtMiddleware'; import fs = require('fs'); @@ -99,18 +99,16 @@ export class UserController { @CurrentUser() currentUser: IUser) { return User.findById(id) .then((user: IUserModel) => { - /* TODO: Update for new file schema - if (user.profile.picture && user.profile.picture.path && fs.existsSync(user.profile.picture.path)) { - fs.unlinkSync(user.profile.picture.path); + if (user.profile.picture && user.profile.picture.link && fs.existsSync(user.profile.picture.link)) { + fs.unlinkSync(user.profile.picture.link); } user.profile.picture = { - path: file.path, - name: file.filename, - alias: file.originalname, - size: file.size + name: file.originalname, + link: file.path, + size: file.size, + mimeType: file.mimeType }; - */ return user.save(); }) .then((user) => { diff --git a/shared/models/IUser.ts b/shared/models/IUser.ts index ff429eed4..c7dc7fffc 100644 --- a/shared/models/IUser.ts +++ b/shared/models/IUser.ts @@ -1,4 +1,4 @@ -import {IFile} from './IFile'; +import {IFile} from './mediaManager/IFile'; export interface IUser { _id: any; uid: string; diff --git a/shared/models/mediaManager/IFile.ts b/shared/models/mediaManager/IFile.ts index ed832d571..b9df7f9d5 100644 --- a/shared/models/mediaManager/IFile.ts +++ b/shared/models/mediaManager/IFile.ts @@ -1,5 +1,5 @@ export interface IFile { - _id: any; + _id?: any; name: string; link: string; size: number; diff --git a/shared/models/units/IFileUnit.ts b/shared/models/units/IFileUnit.ts index 553d29589..73ffcd412 100644 --- a/shared/models/units/IFileUnit.ts +++ b/shared/models/units/IFileUnit.ts @@ -1,5 +1,5 @@ import {IUnit} from './IUnit'; -import {IFile} from '../IFile'; +import {IFile} from '../mediaManager/IFile'; export interface IFileUnit extends IUnit { From 084b50375d36dba1cfa640bf2a209b2de0d9cdc9 Mon Sep 17 00:00:00 2001 From: PatrickSkowronek Date: Wed, 24 Jan 2018 10:24:05 +0100 Subject: [PATCH 097/124] Fixed small bug with the wrong naming of tasks --- api/src/controllers/DownloadController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/controllers/DownloadController.ts b/api/src/controllers/DownloadController.ts index eafd722c6..0de7adcf7 100644 --- a/api/src/controllers/DownloadController.ts +++ b/api/src/controllers/DownloadController.ts @@ -193,7 +193,7 @@ export class DownloadController { } else if (localUnit.__t === 'task') { const taskUnit = localUnit; archive.append(await TaskUnit.schema.statics.toFile(taskUnit), - {name: lecCounter + '_' + lcName + '/' + unitCounter + '. ' + this.replaceCharInFilename(taskUnit.name) + '.txt'}); + {name: lecCounter + '_' + lcName + '/' + unitCounter + '_' + this.replaceCharInFilename(taskUnit.name) + '.txt'}); } else { throw new NotFoundError(); } From 5fb6d0611927c9665272ccc698027deed4537eb9 Mon Sep 17 00:00:00 2001 From: "steffen.grosspersky" Date: Wed, 24 Jan 2018 11:38:51 +0100 Subject: [PATCH 098/124] Fix user picture upload and test --- api/src/controllers/UserController.ts | 3 ++- api/test/integration/user.ts | 2 +- shared/models/mediaManager/IFile.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/controllers/UserController.ts b/api/src/controllers/UserController.ts index ab8cab802..b5642186b 100644 --- a/api/src/controllers/UserController.ts +++ b/api/src/controllers/UserController.ts @@ -104,8 +104,9 @@ export class UserController { } user.profile.picture = { + _id: null, name: file.originalname, - link: file.path, + link: file.filename, size: file.size, mimeType: file.mimeType }; diff --git a/api/test/integration/user.ts b/api/test/integration/user.ts index e505460f0..e1cc89fce 100644 --- a/api/test/integration/user.ts +++ b/api/test/integration/user.ts @@ -341,7 +341,7 @@ describe('User', () => { .attach('file', fs.readFileSync('test/resources/test.png'), 'test.png'); res.status.should.be.equal(200); - res.body.profile.picture.name.should.match(new RegExp(`${admin._id}-[0-9]{4}.png`)); + res.body.profile.picture.name.should.be.equal('test.png'); }); }); diff --git a/shared/models/mediaManager/IFile.ts b/shared/models/mediaManager/IFile.ts index b9df7f9d5..ed832d571 100644 --- a/shared/models/mediaManager/IFile.ts +++ b/shared/models/mediaManager/IFile.ts @@ -1,5 +1,5 @@ export interface IFile { - _id?: any; + _id: any; name: string; link: string; size: number; From b051a290535c0a85b061cda89c4f5c53370faf31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Wed, 24 Jan 2018 23:54:07 +0100 Subject: [PATCH 099/124] Start implementation of file unit migration --- api/src/migrations/scripts/fileUnit.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index 143eedead..f3f99f376 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -1,11 +1,7 @@ // tslint:disable:no-console import * as mongoose from 'mongoose'; -import {ObjectID} from 'bson'; import {IUnitModel} from '../../models/units/Unit'; -import {ITaskUnitModel} from '../../models/units/TaskUnit'; -import {ITaskUnit} from '../../../../shared/models/units/ITaskUnit'; -import {ITask} from '../../../../shared/models/task/ITask'; -import {IUnit} from '../../../../shared/models/units/IUnit'; +import {ObjectID} from 'bson'; const unitSchema = new mongoose.Schema({ _course: { @@ -80,6 +76,17 @@ class FileUnitMigration { async up() { console.log('FileUnit up was called'); + try { + const fileUnits = await Unit.find({'__t': 'file'}).exec(); + const updatedFileUnits = await Promise.all(fileUnits.map(async (fileUnit) => { + if (fileUnit._id instanceof ObjectID) { + const fileUnitObj = fileUnit.toObject(); + } + })); + } catch (error) { + console.log(error); + } + return true; } down() { From b6bbf6a568138c5eae4e2db3248016537a28eef1 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Thu, 25 Jan 2018 14:12:45 +0100 Subject: [PATCH 100/124] Removes duplicated showProgess Service --- .../unit/unit-form/file-unit-form/file-unit-form.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts index 32323d1d8..00949a1df 100644 --- a/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts +++ b/app/webFrontend/src/app/unit/unit-form/file-unit-form/file-unit-form.component.ts @@ -32,7 +32,6 @@ export class FileUnitFormComponent implements OnInit { private unitService: UnitService, private showProgress: ShowProgressService, private dialog: MatDialog, - private showProgress: ShowProgressService, private notificationService: NotificationService) { } From 02ab16c75c7192339504c1390d067139233bb88d Mon Sep 17 00:00:00 2001 From: "steffen.grosspersky" Date: Mon, 12 Mar 2018 11:49:39 +0100 Subject: [PATCH 101/124] Remove upload param from unit controller --- api/src/controllers/UnitController.ts | 30 ++++----------------------- app/webFrontend/package-lock.json | 8 +++---- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/api/src/controllers/UnitController.ts b/api/src/controllers/UnitController.ts index dc8cc32cb..bafd05bd2 100644 --- a/api/src/controllers/UnitController.ts +++ b/api/src/controllers/UnitController.ts @@ -1,34 +1,12 @@ import { Body, Get, Put, Delete, Param, JsonController, UseBefore, NotFoundError, BadRequestError, Post, - Authorized, UploadedFile + Authorized } from 'routing-controllers'; import passportJwtMiddleware from '../security/passportJwtMiddleware'; -import crypto = require('crypto'); import {Lecture} from '../models/Lecture'; import {IUnitModel, Unit} from '../models/units/Unit'; import {ValidationError} from 'mongoose'; -import config from '../config/main' -import {Notification} from '../models/Notification'; -import {ICourse} from '../../../shared/models/ICourse'; -import {Course} from '../models/Course'; - -const multer = require('multer'); - -const uploadOptions = { - storage: multer.diskStorage({ - destination: (req: any, file: any, cb: any) => { - cb(null, config.uploadFolder); - }, - filename: (req: any, file: any, cb: any) => { - const extPos = file.originalname.lastIndexOf('.'); - const ext = (extPos !== -1) ? `.${file.originalname.substr(extPos + 1).toLowerCase()}` : ''; - crypto.pseudoRandomBytes(16, (err, raw) => { - cb(err, err ? undefined : `${raw.toString('hex')}${ext}`); - }); - } - }), -}; @JsonController('/units') @UseBefore(passportJwtMiddleware) @@ -42,9 +20,9 @@ export class UnitController { @Authorized(['teacher', 'admin']) @Post('/') - addUnit(@UploadedFile('file', {options: uploadOptions}) file: any, @Body() data: any) { + addUnit(@Body() data: any) { // discard invalid requests - this.checkPostParam(data, file); + this.checkPostParam(data); return Unit.create(data.model) .then((createdUnit) => { @@ -114,7 +92,7 @@ export class UnitController { }); } - protected checkPostParam(data: any, file?: any) { + protected checkPostParam(data: any) { if (!data.lectureId) { throw new BadRequestError('No lecture ID was submitted.'); } diff --git a/app/webFrontend/package-lock.json b/app/webFrontend/package-lock.json index 2396eac79..22fffec4b 100644 --- a/app/webFrontend/package-lock.json +++ b/app/webFrontend/package-lock.json @@ -3417,7 +3417,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -4437,7 +4437,7 @@ "istanbul-lib-coverage": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz", - "integrity": "sha1-c7+5mIhSmUFck9OKPprfeEp3qdo=", + "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==", "dev": true }, "istanbul-lib-hook": { @@ -5326,7 +5326,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "1.1.8" } @@ -5460,7 +5460,7 @@ "ng2-dragula": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/ng2-dragula/-/ng2-dragula-1.5.0.tgz", - "integrity": "sha1-EqIh16NPxinMdNyY77JkSCkOtuU=", + "integrity": "sha512-uSVq66Rv+ZhDLBGYCGZ7mTaseP7rvYJOijiQZlzfy8dxL614Sw7rhtnLqvK8nqa3tI/wVv8CEGZaZkMnWJokwQ==", "requires": { "dragula": "3.7.2" } From 7a563bca649a62bd7ab872f6286a6a1f49c40b7b Mon Sep 17 00:00:00 2001 From: "steffen.grosspersky" Date: Sat, 17 Mar 2018 17:03:24 +0100 Subject: [PATCH 102/124] More migration code --- api/gulpfile.js | 4 ++-- api/src/migrations/scripts/fileUnit.ts | 29 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/api/gulpfile.js b/api/gulpfile.js index 1119d94cd..fe2124605 100644 --- a/api/gulpfile.js +++ b/api/gulpfile.js @@ -208,7 +208,7 @@ gulp.task(LOAD_FIXTURES, [BUILD_DEV], function () { }); gulp.task(MIGRATE, [BUILD], function () { - require(__dirname + "/build/migrations/migrate"); + require(__dirname + "/build/src/migrations"); }); gulp.task(DEBUG, [BUILD_DEV], function () { @@ -234,7 +234,7 @@ gulp.task(INSPECT, [BUILD_DEV], function () { gulp.task(INSPECT_MIGRATOR, [BUILD_DEV], function () { return nodemon({ ext: "ts js json", - script: "build/migrations/migrate.js", + script: "build/src/migrate.js", watch: ["migrations/*"], tasks: [BUILD_DEV], nodeArgs: ["--inspect=0.0.0.0:9229"] diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index f3f99f376..30bd1fd4a 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -2,6 +2,11 @@ import * as mongoose from 'mongoose'; import {IUnitModel} from '../../models/units/Unit'; import {ObjectID} from 'bson'; +import {IFileUnit} from '../../../../shared/models/units/IFileUnit'; +import fs = require('fs'); +import {IFile} from '../../../../shared/models/IFile'; +import {IFileUnitModel} from '../../models/units/FileUnit'; +import {IFileModel} from '../../models/mediaManager/File'; const unitSchema = new mongoose.Schema({ _course: { @@ -34,9 +39,9 @@ const unitSchema = new mongoose.Schema({ } ); -const Unit = mongoose.model('Unit', unitSchema); +const Unit = mongoose.model('FileOldUnit', unitSchema); -const fileUnitSchema = new mongoose.Schema({ +const fileSchema = new mongoose.Schema({ files: [ { path: { @@ -70,7 +75,7 @@ const fileUnitSchema = new mongoose.Schema({ }, }); -const FileUnit = mongoose.model('File', fileUnitSchema); +const FileUnit = mongoose.model('File', fileSchema); class FileUnitMigration { @@ -80,9 +85,25 @@ class FileUnitMigration { const fileUnits = await Unit.find({'__t': 'file'}).exec(); const updatedFileUnits = await Promise.all(fileUnits.map(async (fileUnit) => { if (fileUnit._id instanceof ObjectID) { - const fileUnitObj = fileUnit.toObject(); + const fileUnitObj: IFileUnit = fileUnit.toObject(); + const fileUnitWithUpdatedFiles = fileUnitObj.files.map((file) => { + if (file instanceof ObjectID) { + return; + } + + const oldFile = file; + const absolutePath = fs.realpathSync('api\\' + oldFile.path); + const newFile = { + physicalPath: absolutePath, + name: oldFile.alias, + size: oldFile.size, + link: oldFile.name + }; + }); } })); + + const debug = 0; } catch (error) { console.log(error); } From 5f38a40a99ddf35cfc335928a5ea0e565be8b2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Sun, 18 Mar 2018 15:55:36 +0100 Subject: [PATCH 103/124] Completed migration script for file unit --- api/src/migrations/scripts/fileUnit.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index 30bd1fd4a..4650e3366 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -4,9 +4,7 @@ import {IUnitModel} from '../../models/units/Unit'; import {ObjectID} from 'bson'; import {IFileUnit} from '../../../../shared/models/units/IFileUnit'; import fs = require('fs'); -import {IFile} from '../../../../shared/models/IFile'; -import {IFileUnitModel} from '../../models/units/FileUnit'; -import {IFileModel} from '../../models/mediaManager/File'; +import {File} from '../../models/mediaManager/File'; const unitSchema = new mongoose.Schema({ _course: { @@ -75,7 +73,7 @@ const fileSchema = new mongoose.Schema({ }, }); -const FileUnit = mongoose.model('File', fileSchema); +const FileUnit = mongoose.model('Files', fileSchema); class FileUnitMigration { @@ -86,9 +84,9 @@ class FileUnitMigration { const updatedFileUnits = await Promise.all(fileUnits.map(async (fileUnit) => { if (fileUnit._id instanceof ObjectID) { const fileUnitObj: IFileUnit = fileUnit.toObject(); - const fileUnitWithUpdatedFiles = fileUnitObj.files.map((file) => { + fileUnitObj.files = await Promise.all(fileUnitObj.files.map(async (file) => { if (file instanceof ObjectID) { - return; + return file; } const oldFile = file; @@ -99,11 +97,18 @@ class FileUnitMigration { size: oldFile.size, link: oldFile.name }; - }); + + const createdFile = await File.create(newFile); + return createdFile._id; + })); + + fileUnitObj._id = new ObjectID(fileUnitObj._id); + + const unitAfterReplace = await mongoose.connection.collection('units') + .findOneAndReplace({'_id': fileUnit._id}, fileUnitObj); + return fileUnitObj; } })); - - const debug = 0; } catch (error) { console.log(error); } From 76bfc78f2ac94a4c7b67bf06d4337f6b7e62b315 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Sun, 18 Mar 2018 17:42:48 +0100 Subject: [PATCH 104/124] delete file references --- api/src/models/mediaManager/File.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index 30aee7a37..40eb424aa 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -1,6 +1,8 @@ import * as mongoose from 'mongoose'; import {IFile} from '../../../../shared/models/mediaManager/IFile'; import * as fs from 'fs'; +import {FileUnit} from '../units/Unit'; +import {IFileUnitModel} from '../units/FileUnit'; const {promisify} = require('util'); @@ -43,7 +45,15 @@ fileSchema.pre('remove', async function(next: () => void) { await promisify(fs.unlink)(this.physicalPath); } - // TODO: look for references + const units2Check: IFileUnitModel[] = await FileUnit.find({files: { $in: [ this._id ] }}); + Promise.all(units2Check.map(async unit => { + let index = unit.files.indexOf(this._id); + if (index > -1) { + unit.files.splice(index, 1); + await unit.save(); + } + })); + next(); }); From a749e18667326132c10da0e97a2dcb7e9a537750 Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Sun, 18 Mar 2018 17:46:55 +0100 Subject: [PATCH 105/124] fix semantic --- api/src/models/mediaManager/File.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/models/mediaManager/File.ts b/api/src/models/mediaManager/File.ts index 40eb424aa..29a86c1d6 100644 --- a/api/src/models/mediaManager/File.ts +++ b/api/src/models/mediaManager/File.ts @@ -47,7 +47,7 @@ fileSchema.pre('remove', async function(next: () => void) { const units2Check: IFileUnitModel[] = await FileUnit.find({files: { $in: [ this._id ] }}); Promise.all(units2Check.map(async unit => { - let index = unit.files.indexOf(this._id); + const index = unit.files.indexOf(this._id); if (index > -1) { unit.files.splice(index, 1); await unit.save(); From ed9ac3354af41deaedd437f165d05982c809b924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Wed, 21 Mar 2018 11:28:49 +0100 Subject: [PATCH 106/124] Display true error --- api/src/migrations/MigrationHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/migrations/MigrationHandler.ts b/api/src/migrations/MigrationHandler.ts index 00b31c910..cf6d6ef93 100644 --- a/api/src/migrations/MigrationHandler.ts +++ b/api/src/migrations/MigrationHandler.ts @@ -23,6 +23,7 @@ export class MigrationHandler { this.scripts[filename] = new requiredFile(); } catch (error) { console.log('The file ' + file + ' is missing a class definition.'); + console.log(error); return false; } }); From 491764696dd1d4903fe4edfe741983fc0e28db7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Wed, 21 Mar 2018 11:42:47 +0100 Subject: [PATCH 107/124] More flexible realpathSync --- api/src/migrations/scripts/fileUnit.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index 4650e3366..174078699 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -90,7 +90,17 @@ class FileUnitMigration { } const oldFile = file; - const absolutePath = fs.realpathSync('api\\' + oldFile.path); + let absolutePath = ''; + try { + absolutePath = fs.realpathSync(oldFile.path); + } catch (error) { + absolutePath = ''; + } + + if (absolutePath.length === 0) { + absolutePath = fs.realpathSync('api/' + oldFile.path); + } + const newFile = { physicalPath: absolutePath, name: oldFile.alias, From 36ffd23f5bef0881b04ad30e6ccc4d20bfa754dd Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 21 Mar 2018 12:06:06 +0100 Subject: [PATCH 108/124] Fixes IFile usage in User model --- app/webFrontend/src/app/models/User.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/models/User.ts b/app/webFrontend/src/app/models/User.ts index a434ef08f..80319aac5 100644 --- a/app/webFrontend/src/app/models/User.ts +++ b/app/webFrontend/src/app/models/User.ts @@ -1,6 +1,6 @@ import {IUser} from '../../../../../shared/models/IUser'; import md5 from 'blueimp-md5'; -import {IFile} from '../../../../../shared/models/IFile'; +import {IFile} from '../../../../../shared/models/mediaManager/IFile'; export class User implements IUser { _id: any; From 573b699980c9dbb1d3fc678c5751264d5e876eed Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 21 Mar 2018 12:17:01 +0100 Subject: [PATCH 109/124] Fixes IFile usage in FileUnit --- app/webFrontend/src/app/models/units/FileUnit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/models/units/FileUnit.ts b/app/webFrontend/src/app/models/units/FileUnit.ts index 39c0cb7c7..a814984a0 100644 --- a/app/webFrontend/src/app/models/units/FileUnit.ts +++ b/app/webFrontend/src/app/models/units/FileUnit.ts @@ -1,5 +1,5 @@ import {IFileUnit} from '../../../../../../shared/models/units/IFileUnit'; -import {IFile} from '../../../../../../shared/models/IFile'; +import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; import {ICourse} from '../../../../../../shared/models/ICourse'; export class FileUnit implements IFileUnit { From 1d1719bf577552175f42a0f284db098dc8283c31 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 21 Mar 2018 12:20:08 +0100 Subject: [PATCH 110/124] Fixes IFile usage in FileUnit --- app/webFrontend/src/app/unit/file-unit/file-unit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts b/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts index c140c9463..69dee7701 100644 --- a/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts +++ b/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts @@ -20,7 +20,7 @@ export class FileUnitComponent implements OnInit { if (this.isPicture(file.name)) { const src = '/api/uploads/' + file.name; const thumb = src; - const caption = file.alias; + const caption = file.name; const image = { src: src, thumb: thumb, From 3cf8a3ed7d402dd39bdac7e1ca8a21c1010a22f9 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Wed, 21 Mar 2018 12:25:06 +0100 Subject: [PATCH 111/124] Fixes IFile usage in UserProfile --- .../src/app/user/user-profile/user-profile.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webFrontend/src/app/user/user-profile/user-profile.component.ts b/app/webFrontend/src/app/user/user-profile/user-profile.component.ts index a6db00dec..817f33600 100644 --- a/app/webFrontend/src/app/user/user-profile/user-profile.component.ts +++ b/app/webFrontend/src/app/user/user-profile/user-profile.component.ts @@ -1,6 +1,6 @@ import {Component, Input, OnInit} from '@angular/core'; import {UserService} from '../../shared/services/user.service'; -import {IFile} from '../../../../../../shared/models/IFile'; +import {IFile} from '../../../../../../shared/models/mediaManager/IFile'; import {User} from '../../models/User'; @Component({ From 36128c20d4ed5eec653e9193846b7cf668a83ab2 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Fri, 23 Mar 2018 00:34:42 +0100 Subject: [PATCH 112/124] Comments out failing test for file upload --- api/test/integration/unit/fileUnit.ts | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/api/test/integration/unit/fileUnit.ts b/api/test/integration/unit/fileUnit.ts index 6ff91adc7..3529bf02e 100644 --- a/api/test/integration/unit/fileUnit.ts +++ b/api/test/integration/unit/fileUnit.ts @@ -19,29 +19,30 @@ describe('FileUnit', () => { await fixtureLoader.load(); }); - describe(`POST ${BASE_URL}`, () => { - it('should upload a video and return the created unit', async () => { - const course = await FixtureUtils.getRandomCourse(); - const courseAdmin = await User.findOne({_id: course.courseAdmin}); + // FIXME: Add working test! + // describe(`POST ${BASE_URL}`, () => { + // it('should upload a video and return the created unit', async () => { + // const course = await FixtureUtils.getRandomCourse(); + // const courseAdmin = await User.findOne({_id: course.courseAdmin}); - const data = { - model: { - _course: course._id.toString(), - name: 'Test Upload', - description: 'This is my test upload.' - }, - lectureId: course.lectures[0].toString() - }; + // const data = { + // model: { + // _course: course._id.toString(), + // name: 'Test Upload', + // description: 'This is my test upload.' + // }, + // lectureId: course.lectures[0].toString() + // }; - const res = await chai.request(app) - .post(BASE_URL) - .field('data', JSON.stringify(data)) - .attach('file', fs.readFileSync('fixtures/binaryData/testvideo.mp4'), 'testvideo.mp4') - .set('Authorization', `JWT ${JwtUtils.generateToken(courseAdmin)}`); + // const res = await chai.request(app) + // .post(BASE_URL) + // .field('data', JSON.stringify(data)) + // .attach('file', fs.readFileSync('fixtures/binaryData/testvideo.mp4'), 'testvideo.mp4') + // .set('Authorization', `JWT ${JwtUtils.generateToken(courseAdmin)}`); - res.status.should.be.equal(200); - res.body.name.should.be.equal('Test Upload'); - res.body.description.should.be.equal('This is my test upload.'); - }); - }); + // res.status.should.be.equal(200); + // res.body.name.should.be.equal('Test Upload'); + // res.body.description.should.be.equal('This is my test upload.'); + // }); + // }); }); From b154eaaf3c11656b92c04dec485a0afaeab0ade7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Fri, 23 Mar 2018 19:44:15 +0100 Subject: [PATCH 113/124] Fix change of _course ObjectID --- api/src/migrations/scripts/fileUnit.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index 174078699..a24da2e39 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -113,6 +113,7 @@ class FileUnitMigration { })); fileUnitObj._id = new ObjectID(fileUnitObj._id); + fileUnitObj._course = new ObjectID(fileUnitObj._course); const unitAfterReplace = await mongoose.connection.collection('units') .findOneAndReplace({'_id': fileUnit._id}, fileUnitObj); From 2b0ed2e7d6d6e646f151e2dfc22b9cbd7ae21bcd Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sat, 24 Mar 2018 02:21:59 +0100 Subject: [PATCH 114/124] Bump version to 0.6.0 --- api/package.json | 2 +- app/webFrontend/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/package.json b/api/package.json index 9a0297ac4..58cb1cf27 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "geli-api", - "version": "0.5.0", + "version": "0.6.0", "license": "GPL-3.0", "repository": "h-da/geli", "description": "This is the API-Package.json for geli.", diff --git a/app/webFrontend/package.json b/app/webFrontend/package.json index 886a2db2c..d875fe0ac 100644 --- a/app/webFrontend/package.json +++ b/app/webFrontend/package.json @@ -2,7 +2,7 @@ "name": "geli-web-frontend", "description": "This is the WebFronted-Package.json for geli.", "repository": "h-da/geli", - "version": "0.5.0", + "version": "0.6.0", "license": "GPL-3.0", "scripts": { "ng": "ng", From aaf0b1b5c188ef34285d26cc6436a472dfa24859 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sat, 24 Mar 2018 19:20:25 +0100 Subject: [PATCH 115/124] Moves apidoc before docker, so gulp is still available --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 893bf3ca6..435fa3518 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,10 +88,10 @@ after_script: after_success: - .travis/package-checker.sh + - .travis/apidoc.sh - .travis/docker.sh - .travis/deploy.sh - .travis/sentry.sh - - .travis/apidoc.sh # Configure notification notifications: From 802d2e6cb8e7470e7f814b7102b1302ad41785ef Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Sun, 25 Mar 2018 15:32:06 +0200 Subject: [PATCH 116/124] Set version in package-locks to 0.6.0 --- api/package-lock.json | 2 +- app/webFrontend/package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 10126ba88..fd26300cb 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,6 +1,6 @@ { "name": "geli-api", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/app/webFrontend/package-lock.json b/app/webFrontend/package-lock.json index 25e15c75b..b000e5825 100644 --- a/app/webFrontend/package-lock.json +++ b/app/webFrontend/package-lock.json @@ -1,6 +1,6 @@ { "name": "geli-web-frontend", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { From 0aa6021031aaa5d2bf7d99f234e2ecfe747756fe Mon Sep 17 00:00:00 2001 From: Ken Hasenbank Date: Sun, 25 Mar 2018 15:51:01 +0200 Subject: [PATCH 117/124] remove duplicate import --- api/fixtures/FixtureUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/fixtures/FixtureUtils.ts b/api/fixtures/FixtureUtils.ts index dc4ff5fa8..f1d14f072 100644 --- a/api/fixtures/FixtureUtils.ts +++ b/api/fixtures/FixtureUtils.ts @@ -11,7 +11,6 @@ import {ITaskUnit} from '../../shared/models/units/ITaskUnit'; import {ITaskUnitModel} from '../src/models/units/TaskUnit'; import * as mongoose from 'mongoose'; import ObjectId = mongoose.Types.ObjectId; -import {IWhitelistUser} from '../../shared/models/IWhitelistUser'; export class FixtureUtils { public static async getRandomUser(hash?: string): Promise { From 7ca89dfa2c5325fd134dbae6c07ce4456b1d6a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Sun, 25 Mar 2018 17:45:18 +0200 Subject: [PATCH 118/124] Add filesize to database during migration if it is missing --- api/src/migrations/scripts/fileUnit.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index a24da2e39..a57ade33d 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -91,14 +91,22 @@ class FileUnitMigration { const oldFile = file; let absolutePath = ''; + let fileStats = null; try { absolutePath = fs.realpathSync(oldFile.path); + fileStats = fs.statSync(oldFile.path); } catch (error) { + fileStats = null; absolutePath = ''; } if (absolutePath.length === 0) { absolutePath = fs.realpathSync('api/' + oldFile.path); + fileStats = fs.statSync('api/' + oldFile.path); + } + + if (typeof oldFile.size === 'undefined') { + oldFile.size = fileStats.size; } const newFile = { @@ -114,9 +122,10 @@ class FileUnitMigration { fileUnitObj._id = new ObjectID(fileUnitObj._id); fileUnitObj._course = new ObjectID(fileUnitObj._course); - + /* const unitAfterReplace = await mongoose.connection.collection('units') .findOneAndReplace({'_id': fileUnit._id}, fileUnitObj); + */ return fileUnitObj; } })); From cf66480e4a79b7af18d2d9bea74019aed7aa84c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Sun, 25 Mar 2018 17:45:58 +0200 Subject: [PATCH 119/124] Uncomment write command in migration script --- api/src/migrations/scripts/fileUnit.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index a57ade33d..a57681685 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -122,10 +122,8 @@ class FileUnitMigration { fileUnitObj._id = new ObjectID(fileUnitObj._id); fileUnitObj._course = new ObjectID(fileUnitObj._course); - /* const unitAfterReplace = await mongoose.connection.collection('units') .findOneAndReplace({'_id': fileUnit._id}, fileUnitObj); - */ return fileUnitObj; } })); From 9c9cd03338a6424bd351860509acd6592c3e3a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Sun, 25 Mar 2018 18:50:03 +0200 Subject: [PATCH 120/124] Fix error message in migration script --- api/src/migrations/scripts/taskUnit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/migrations/scripts/taskUnit.ts b/api/src/migrations/scripts/taskUnit.ts index 114c302c0..ac3e81039 100644 --- a/api/src/migrations/scripts/taskUnit.ts +++ b/api/src/migrations/scripts/taskUnit.ts @@ -38,7 +38,7 @@ const unitSchema = new mongoose.Schema({ } ); -const Unit = mongoose.model('Unit', unitSchema); +const Unit = mongoose.model('OldUnitForTask', unitSchema); const taskSchema = new mongoose.Schema( { From 7d8c46827d7a174788e871edf0f02aed703abf0e Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Mon, 26 Mar 2018 21:26:12 +0200 Subject: [PATCH 121/124] Bugfix: Change file.name to file.link in FE --- app/webFrontend/src/app/unit/file-unit/file-unit.component.html | 2 +- app/webFrontend/src/app/unit/file-unit/file-unit.component.ts | 2 +- .../src/app/unit/video-unit/video-unit.component.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/webFrontend/src/app/unit/file-unit/file-unit.component.html b/app/webFrontend/src/app/unit/file-unit/file-unit.component.html index 83d31720a..5848e148c 100644 --- a/app/webFrontend/src/app/unit/file-unit/file-unit.component.html +++ b/app/webFrontend/src/app/unit/file-unit/file-unit.component.html @@ -1,6 +1,6 @@
- +
diff --git a/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts b/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts index 69dee7701..fbae5f5a5 100644 --- a/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts +++ b/app/webFrontend/src/app/unit/file-unit/file-unit.component.ts @@ -18,7 +18,7 @@ export class FileUnitComponent implements OnInit { ngOnInit() { this.fileUnit.files.forEach(file => { if (this.isPicture(file.name)) { - const src = '/api/uploads/' + file.name; + const src = '/api/uploads/' + file.link; const thumb = src; const caption = file.name; const image = { diff --git a/app/webFrontend/src/app/unit/video-unit/video-unit.component.html b/app/webFrontend/src/app/unit/video-unit/video-unit.component.html index 6796c70f4..2dc839cfc 100644 --- a/app/webFrontend/src/app/unit/video-unit/video-unit.component.html +++ b/app/webFrontend/src/app/unit/video-unit/video-unit.component.html @@ -1,3 +1,3 @@
- +
From 643441e14b8f79105ca0ec3b33bda42bcf0d828e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Mon, 26 Mar 2018 23:37:18 +0200 Subject: [PATCH 122/124] Add missing migrations parts for file unit --- api/src/migrations/scripts/fileUnit.ts | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index a57681685..94c63ab62 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -5,6 +5,9 @@ import {ObjectID} from 'bson'; import {IFileUnit} from '../../../../shared/models/units/IFileUnit'; import fs = require('fs'); import {File} from '../../models/mediaManager/File'; +import {Course} from '../../models/Course'; +import {Directory} from '../../models/mediaManager/Directory'; +import {ICourse} from '../../../../shared/models/ICourse'; const unitSchema = new mongoose.Schema({ _course: { @@ -80,6 +83,34 @@ class FileUnitMigration { async up() { console.log('FileUnit up was called'); try { + const courses = await Course.find().exec(); + const directories: any = {}; + const updatedCoursesMap: any = {}; + const updatedCourses = await Promise.all(courses.map(async (course) => { + const courseObj: ICourse = course.toObject(); + const returnObj: any = {}; + if (!courseObj.hasOwnProperty('media')) { + const directoryObj: any = { + name: courseObj.name, + subDirectories: [], + files: [] + }; + + const createdDirectory: any = await Directory.create(directoryObj); + courseObj.media = createdDirectory._id; + const updatedCourse = await Course.findOneAndUpdate({'_id': courseObj._id}, courseObj, {new: true}).exec(); + + directories[createdDirectory._id] = await createdDirectory.toObject(); + + updatedCoursesMap[courseObj._id] = await updatedCourse.toObject(); + return updatedCourse; + } else { + const directory = await Directory.findById(courseObj.media).exec(); + directories[directory._id] = await directory.toObject(); + updatedCoursesMap[courseObj._id] = courseObj; + return course; + } + })); const fileUnits = await Unit.find({'__t': 'file'}).exec(); const updatedFileUnits = await Promise.all(fileUnits.map(async (fileUnit) => { if (fileUnit._id instanceof ObjectID) { @@ -120,13 +151,22 @@ class FileUnitMigration { return createdFile._id; })); + const directoryId = updatedCoursesMap[fileUnitObj._course].media.toString(); fileUnitObj._id = new ObjectID(fileUnitObj._id); fileUnitObj._course = new ObjectID(fileUnitObj._course); + directories[directoryId].files = directories[directoryId].files.concat(fileUnitObj.files); + const unitAfterReplace = await mongoose.connection.collection('units') .findOneAndReplace({'_id': fileUnit._id}, fileUnitObj); return fileUnitObj; } })); + + for (const directoryId of Object.keys(directories)) { + const directory = directories[directoryId]; + const updatedDirectory = await Directory.findOneAndUpdate({'_id': directoryId}, directory, {new: true}).exec(); + } + } catch (error) { console.log(error); } From 0a83198d7ca812581fe61afa6989c37dbf345d73 Mon Sep 17 00:00:00 2001 From: Alexander Eimer Date: Tue, 27 Mar 2018 00:28:35 +0200 Subject: [PATCH 123/124] Check if MimeType is set, to prevent hard error in CourseEdit --- .../course/course-edit/course-media/course-media.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts index 6b613f864..f189e3f9b 100644 --- a/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts +++ b/app/webFrontend/src/app/course/course-edit/course-media/course-media.component.ts @@ -171,6 +171,10 @@ export class CourseMediaComponent implements OnInit { } getSimpleMimeType(file: IFile): string { + if (file.mimeType === undefined) { + return 'unknown'; + } + const mimeType = file.mimeType.toLowerCase(); const archives = [ 'application/x-bzip', From 58e5a8edc7212ce1ca53d0ed04560b317a6c5f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Gro=C3=9Fpersky?= Date: Fri, 30 Mar 2018 20:29:32 +0200 Subject: [PATCH 124/124] Remove non existing files from units, add default mimetype --- api/src/migrations/scripts/fileUnit.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api/src/migrations/scripts/fileUnit.ts b/api/src/migrations/scripts/fileUnit.ts index 94c63ab62..d49b649c2 100644 --- a/api/src/migrations/scripts/fileUnit.ts +++ b/api/src/migrations/scripts/fileUnit.ts @@ -132,8 +132,12 @@ class FileUnitMigration { } if (absolutePath.length === 0) { - absolutePath = fs.realpathSync('api/' + oldFile.path); - fileStats = fs.statSync('api/' + oldFile.path); + try { + absolutePath = fs.realpathSync('api/' + oldFile.path); + fileStats = fs.statSync('api/' + oldFile.path); + } catch (error) { + return null; + } } if (typeof oldFile.size === 'undefined') { @@ -144,13 +148,18 @@ class FileUnitMigration { physicalPath: absolutePath, name: oldFile.alias, size: oldFile.size, - link: oldFile.name + link: oldFile.name, + mimeType: 'plain/text' }; const createdFile = await File.create(newFile); return createdFile._id; })); + fileUnitObj.files = await fileUnitObj.files.filter((element, index, array) => { + return (element !== null); + }); + const directoryId = updatedCoursesMap[fileUnitObj._course].media.toString(); fileUnitObj._id = new ObjectID(fileUnitObj._id); fileUnitObj._course = new ObjectID(fileUnitObj._course);