Client: add ability to report a video
authorChocobozzz <florian.bigard@gmail.com>
Fri, 20 Jan 2017 18:22:15 +0000 (19:22 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 20 Jan 2017 18:22:15 +0000 (19:22 +0100)
client/src/app/shared/forms/form-validators/index.ts
client/src/app/shared/forms/form-validators/video-report.ts [new file with mode: 0644]
client/src/app/videos/shared/video.service.ts
client/src/app/videos/video-watch/index.ts
client/src/app/videos/video-watch/video-report.component.html [new file with mode: 0644]
client/src/app/videos/video-watch/video-report.component.ts [new file with mode: 0644]
client/src/app/videos/video-watch/video-watch.component.html
client/src/app/videos/video-watch/video-watch.component.scss
client/src/app/videos/video-watch/video-watch.component.ts
client/src/app/videos/videos.module.ts
server/helpers/database-utils.js

index 4c6cc66371a77e55dada04af19be3ac932b34fd0..119b5d9bf39c6d742b4b50db188f112835d830f4 100644 (file)
@@ -1,3 +1,4 @@
 export * from './host.validator';
 export * from './user';
+export * from './video-report';
 export * from './video';
diff --git a/client/src/app/shared/forms/form-validators/video-report.ts b/client/src/app/shared/forms/form-validators/video-report.ts
new file mode 100644 (file)
index 0000000..036ee17
--- /dev/null
@@ -0,0 +1,10 @@
+import { Validators } from '@angular/forms';
+
+export const VIDEO_REPORT_REASON = {
+  VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
+  MESSAGES: {
+    'required': 'Report reason name is required.',
+    'minlength': 'Report reson must be at least 2 characters long.',
+    'maxlength': 'Report reson cannot be more than 300 characters long.'
+  }
+};
index 9d79b2f5ea278c034be0a7b2a0b4dd07f1199201..7094d9a34210e4e7498e426adcec6b240379de1c 100644 (file)
@@ -55,6 +55,17 @@ export class VideoService {
                     .catch((res) => this.restExtractor.handleError(res));
   }
 
+  reportVideo(id: string, reason: string) {
+    const body = {
+      reason
+    };
+    const url = VideoService.BASE_VIDEO_URL + id + '/abuse';
+
+    return this.authHttp.post(url, body)
+                    .map(this.restExtractor.extractDataBool)
+                    .catch((res) => this.restExtractor.handleError(res));
+  }
+
   private extractVideos(result: ResultList) {
     const videosJson = result.data;
     const totalVideos = result.total;
index 1a8403b0a65eb4f8fce2fe4481069fa7c23a44c8..ed0ed2fc03e4e448122e17dd9d116c366294c916 100644 (file)
@@ -1,4 +1,5 @@
 export * from './video-magnet.component';
 export * from './video-share.component';
+export * from './video-report.component';
 export * from './video-watch.component';
 export * from './webtorrent.service';
diff --git a/client/src/app/videos/video-watch/video-report.component.html b/client/src/app/videos/video-watch/video-report.component.html
new file mode 100644 (file)
index 0000000..741080e
--- /dev/null
@@ -0,0 +1,38 @@
+<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
+  <div class="modal-dialog">
+    <div class="modal-content modal-lg">
+
+      <div class="modal-header">
+        <button type="button" class="close" aria-label="Close" (click)="hide()">
+          <span aria-hidden="true">&times;</span>
+        </button>
+        <h4 class="modal-title">Report video</h4>
+      </div>
+
+      <div class="modal-body">
+
+        <form novalidate [formGroup]="form">
+          <div class="form-group">
+            <label for="description">Reason</label>
+            <textarea
+              id="reason" class="form-control" placeholder="Reason..."
+              formControlName="reason"
+            >
+            </textarea>
+            <div *ngIf="formErrors.reason" class="alert alert-danger">
+              {{ formErrors.reason }}
+            </div>
+          </div>
+
+          <div class="form-group">
+            <input
+              type="button" value="Report" class="btn btn-default form-control"
+              [disabled]="!form.valid" (click)="report()"
+            >
+          </div>
+        </form>
+
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/client/src/app/videos/video-watch/video-report.component.ts b/client/src/app/videos/video-watch/video-report.component.ts
new file mode 100644 (file)
index 0000000..7bc1677
--- /dev/null
@@ -0,0 +1,68 @@
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+
+import { ModalDirective } from 'ng2-bootstrap/modal';
+
+import { FormReactive, VIDEO_REPORT_REASON } from '../../shared';
+import { Video, VideoService } from '../shared';
+
+@Component({
+  selector: 'my-video-report',
+  templateUrl: './video-report.component.html'
+})
+export class VideoReportComponent extends FormReactive implements OnInit {
+  @Input() video: Video = null;
+
+  @ViewChild('modal') modal: ModalDirective;
+
+  error: string = null;
+  form: FormGroup;
+  formErrors = {
+    reason: ''
+  };
+  validationMessages = {
+    reason: VIDEO_REPORT_REASON.MESSAGES
+  };
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private videoService: VideoService
+   ) {
+    super();
+  }
+
+  ngOnInit() {
+    this.buildForm();
+  }
+
+  buildForm() {
+    this.form = this.formBuilder.group({
+      reason: [ '', VIDEO_REPORT_REASON.VALIDATORS ]
+    });
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+  }
+
+  show() {
+    this.modal.show();
+  }
+
+  hide() {
+    this.modal.hide();
+  }
+
+  report() {
+    const reason = this.form.value['reason']
+
+    this.videoService.reportVideo(this.video.id, reason)
+                     .subscribe(
+                       // TODO: move alert to beautiful notifications
+                       ok => {
+                         alert('Video reported.');
+                         this.hide();
+                       },
+
+                       err => alert(err.text)
+                      )
+  }
+}
index a726ef3fff8367c1a71b7306c756272e0082ecf0..8cee9959d730d00fb512a52615ce3e05a12f415a 100644 (file)
       <button title="Get magnet URI" id="magnet-uri" class="btn btn-default" (click)="showMagnetUriModal()">
         <span class="glyphicon glyphicon-magnet"></span> Magnet
       </button>
+
+      <div *ngIf="isUserLoggedIn()" class="btn-group" dropdown>
+        <button id="single-button" type="button" id="more" class="btn btn-default" dropdownToggle>
+          <span class="glyphicon glyphicon-option-horizontal"></span> More
+        </button>
+        <ul dropdownMenu id="more-menu" role="menu" aria-labelledby="single-button">
+          <li role="menuitem">
+            <a class="dropdown-item" href="#" (click)="showReportModal($event)">
+              <span class="glyphicon glyphicon-alert"></span> Report
+            </a>
+          </li>
+        </ul>
+      </div>
     </div>
   </div>
 
@@ -79,5 +92,8 @@
   </div>
 </div>
 
-<my-video-share #videoShareModal *ngIf="video !== null" [video]="video"></my-video-share>
-<my-video-magnet #videoMagnetModal *ngIf="video !== null" [video]="video"></my-video-magnet>
+<template [ngIf]="video !== null">
+  <my-video-share #videoShareModal [video]="video"></my-video-share>
+  <my-video-magnet #videoMagnetModal [video]="video"></my-video-magnet>
+  <my-video-report #videoReportModal [video]="video"></my-video-report>
+</template>
index ac62b04e7d7562d103592ce6c1f205f181ac4865..794412707c0003cb3bed868d78a4f6656fe5992a 100644 (file)
       top: 2px;
     }
 
-    #magnet-uri, #share {
+    #magnet-uri, #share, #more {
       font-weight: bold;
       opacity: 0.85;
     }
+
+    #more-menu .dropdown-item .glyphicon {
+      margin-right: 5px;
+    }
   }
 
   #video-by-date {
index 256ffef994f1e5a1e64418ff5cb02ae8e981d32d..d83cc5a7aa9f4a3ab6562ded5f232c382e9ce8af 100644 (file)
@@ -5,8 +5,10 @@ import { ActivatedRoute } from '@angular/router';
 import { MetaService } from 'ng2-meta';
 import * as videojs from 'video.js';
 
+import { AuthService } from '../../core';
 import { VideoMagnetComponent } from './video-magnet.component';
 import { VideoShareComponent } from './video-share.component';
+import { VideoReportComponent } from './video-report.component';
 import { Video, VideoService } from '../shared';
 import { WebTorrentService } from './webtorrent.service';
 
@@ -21,6 +23,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
   @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent;
   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent;
+  @ViewChild('videoReportModal') videoReportModal: VideoReportComponent;
 
   downloadSpeed: number;
   error: boolean = false;
@@ -42,7 +45,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     private route: ActivatedRoute,
     private videoService: VideoService,
     private metaService: MetaService,
-    private webTorrentService: WebTorrentService
+    private webTorrentService: WebTorrentService,
+    private authService: AuthService
   ) {}
 
   ngOnInit() {
@@ -123,6 +127,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     });
   }
 
+  showReportModal(event: Event) {
+    event.preventDefault();
+    this.videoReportModal.show();
+  }
+
   showShareModal() {
     this.videoShareModal.show();
   }
@@ -131,6 +140,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.videoMagnetModal.show();
   }
 
+  isUserLoggedIn() {
+    return this.authService.isLoggedIn();
+  }
+
   private loadTooLong() {
     this.error = true;
     console.error('The video load seems to be abnormally long.');
index fb2f453b0a3a7fc8dfd5077e07de6e8cf0b95378..03dea17b5b433e27cda9d6362e6fc6e7fb03e58e 100644 (file)
@@ -4,7 +4,13 @@ import { VideosRoutingModule } from './videos-routing.module';
 import { VideosComponent } from './videos.component';
 import { VideoAddComponent } from './video-add';
 import { VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list';
-import { VideoWatchComponent, VideoMagnetComponent, VideoShareComponent, WebTorrentService } from './video-watch';
+import {
+  VideoWatchComponent,
+  VideoMagnetComponent,
+  VideoReportComponent,
+  VideoShareComponent,
+  WebTorrentService
+} from './video-watch';
 import { LoaderComponent, VideoService } from './shared';
 import { SharedModule } from '../shared';
 
@@ -26,6 +32,7 @@ import { SharedModule } from '../shared';
     VideoWatchComponent,
     VideoMagnetComponent,
     VideoShareComponent,
+    VideoReportComponent,
 
     LoaderComponent
   ],
index 6fe7e99aaca7cde5ca50084e97f132c9c5f7e6dd..c72d194298dd3c35d553198bad8d4886684ba3e2 100644 (file)
@@ -61,7 +61,6 @@ function transactionRetryer (func, callback) {
 }
 
 function startSerializableTransaction (callback) {
-  console.log(db)
   db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
     // We force to return only two parameters
     return callback(err, t)