<p-column field="following.host" header="Host"></p-column>
<p-column field="state" header="State"></p-column>
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
- <p-column header="Unfollow" styleClass="action-cell">
+ <p-column styleClass="action-cell">
<ng-template pTemplate="body" let-following="rowData">
<my-delete-button (click)="removeFollowing(following)"></my-delete-button>
</ng-template>
>
<p-column field="id" header="ID" [style]="{ width: '40px' }"></p-column>
<p-column field="category" header="Category" [style]="{ width: '100px' }"></p-column>
- <p-column field="handlerName" header="Handler name" [style]="{ width: '150px' }"></p-column>
+ <p-column field="handlerName" header="Handler name" [style]="{ width: '200px' }"></p-column>
<p-column header="Input data">
<ng-template pTemplate="body" let-job="rowData">
<pre>{{ job.handlerInputData }}</pre>
-<div class="row">
- <div class="content-padding">
-
- <h3>Video abuses list</h3>
-
- <p-dataTable
- [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
- sortField="id" (onLazyLoad)="loadLazy($event)"
- >
- <p-column field="id" header="ID" [sortable]="true"></p-column>
- <p-column field="reason" header="Reason"></p-column>
- <p-column field="reporterServerHost" header="Reporter server host"></p-column>
- <p-column field="reporterUsername" header="Reporter username"></p-column>
- <p-column field="videoName" header="Video name"></p-column>
- <p-column header="Video" styleClass="action-cell">
- <ng-template pTemplate="body" let-videoAbuse="rowData">
- <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a>
- </ng-template>
- </p-column>
- <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
- </p-dataTable>
-
- </div>
+<div class="admin-sub-header">
+ <div class="admin-sub-title">Video abuses list</div>
</div>
+
+<p-dataTable
+ [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+ sortField="id" (onLazyLoad)="loadLazy($event)"
+>
+ <p-column field="id" header="ID" [sortable]="true"></p-column>
+ <p-column field="reason" header="Reason"></p-column>
+ <p-column field="reporterServerHost" header="Reporter server host"></p-column>
+ <p-column field="reporterUsername" header="Reporter username"></p-column>
+ <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+ <p-column header="Video">
+ <ng-template pTemplate="body" let-videoAbuse="rowData">
+ <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoName }}</a>
+ </ng-template>
+ </p-column>
+</p-dataTable>
--- /dev/null
+/deep/ a {
+
+ &, &:hover, &:active, &:focus {
+ color: #000;
+ }
+}
@Component({
selector: 'my-video-abuse-list',
- templateUrl: './video-abuse-list.component.html'
+ templateUrl: './video-abuse-list.component.html',
+ styleUrls: [ './video-abuse-list.component.scss']
})
export class VideoAbuseListComponent extends RestTable implements OnInit {
videoAbuses: VideoAbuse[] = []
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
<p-column header="Delete" styleClass="action-cell">
<ng-template pTemplate="body" let-entry="rowData">
- <span (click)="removeVideoFromBlacklist(entry)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this video from blacklist"></span>
+ <my-delete-button (click)="removeVideoFromBlacklist(entry)"></my-delete-button>
</ng-template>
</p-column>
</p-dataTable>
return this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers })
.map(res => this.handleRefreshToken(res))
- .catch(res => {
- // The refresh token is invalid?
- if (res.status === 400 && res.error.error === 'invalid_grant') {
- console.error('Cannot refresh token -> logout...')
- this.logout()
- this.router.navigate(['/login'])
-
- return Observable.throw({
- error: 'You need to reconnect.'
- })
- }
-
- return this.restExtractor.handleError(res)
+ .catch(err => {
+ console.error(err)
+ console.log('Cannot refresh token -> logout...')
+ this.logout()
+ this.router.navigate(['/login'])
+
+ return Observable.throw({
+ error: 'You need to reconnect.'
+ })
})
}
observable.subscribe(
({ videos, totalVideos }) => {
// Paging is too high, return to the first one
- if (totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
+ if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
this.pagination.currentPage = 1
this.setNewRouteParams()
return this.reloadVideos()
}
protected hasMoreVideos () {
+ // No results
+ if (this.pagination.totalItems === 0) return false
+
+ // Not loaded yet
if (!this.pagination.totalItems) return true
const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
<textarea
[(ngModel)]="description" (ngModelChange)="onModelChange()"
- id="description" placeholder="My super video">
+ id="description" name="description">
</textarea>
<tabset #staticTabs class="previews">
padding: 5px 15px;
font-size: 15px;
height: 150px;
+ margin-bottom: 15px;
}
+
+/deep/ {
+ .nav-link {
+ display: flex !important;
+ align-items: center;
+ height: 30px !important;
+ padding: 0 15px !important;
+ }
+
+ .tab-content {
+ min-height: 75px;
+ padding: 15px;
+ font-size: 15px;
+ }
+}
+
}
private updateDescriptionPreviews () {
+ if (!this.description) return
+
this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 }))
this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
}
td {
border: 1px solid #E5E5E5 !important;
- padding: 15px;
+ padding-left: 15px !important;
}
tr {
&:first-child td {
border-top: none !important;
}
+
+ &:last-child td {
+ border-bottom: none !important;
+ }
}
th {
&.ui-state-active, &.ui-sortable-column:hover {
background-color: #f0f0f0 !important;
border: 1px solid #f0f0f0 !important;
+ border-width: 0 1px !important;
}
}
}
p-paginator {
- overflow: hidden;
- display: block;
- padding-top: 2px;
- border: 1px solid #f0f0f0 !important;
- border-top: none !important;
-
.ui-paginator-bottom {
position: relative;
border: none !important;
- border-top: 1px solid #f0f0f0 !important;
- box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.16);
+ border: 1px solid #f0f0f0 !important;
height: 40px;
display: flex;
justify-content: center;
font-weight: $font-semibold !important;
}
}
-
- .tab-content {
- min-height: 75px;
- padding: 15px;
- }
}
if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
- if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy)
+ if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10))
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
}
// Video is not private anymore, send a create action to remote servers
- if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) {
- await sendAddVideo(videoInstance, t)
- await shareVideoByServer(videoInstance, t)
+ if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
+ await sendAddVideo(videoInstanceUpdated, t)
+ await shareVideoByServer(videoInstanceUpdated, t)
}
})
isActivityPubVideoDurationValid(video.duration) &&
isUUIDValid(video.uuid) &&
setValidRemoteTags(video) &&
- isRemoteIdentifierValid(video.category) &&
- isRemoteIdentifierValid(video.licence) &&
+ (!video.category || isRemoteIdentifierValid(video.category)) &&
+ (!video.licence || isRemoteIdentifierValid(video.licence)) &&
(!video.language || isRemoteIdentifierValid(video.language)) &&
isVideoViewsValid(video.views) &&
isVideoNSFWValid(video.nsfw) &&
isDateValid(video.published) &&
isDateValid(video.updated) &&
- isRemoteVideoContentValid(video.mediaType, video.content) &&
+ (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
isRemoteVideoIconValid(video.icon) &&
setValidRemoteVideoUrls(video) &&
video.url.length !== 0
import { database as db } from '../../initializers/database'
import { VideoInstance } from '../../models/video/video-interface'
import { exists, isArray } from './misc'
+import isInt = require('validator/lib/isInt')
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
function isVideoCategoryValid (value: number) {
- return VIDEO_CATEGORIES[value] !== undefined
+ return value === null || VIDEO_CATEGORIES[value] !== undefined
}
function isVideoLicenceValid (value: number) {
- return VIDEO_LICENCES[value] !== undefined
+ return value === null || VIDEO_LICENCES[value] !== undefined
}
function isVideoLanguageValid (value: number) {
}
function isVideoDescriptionValid (value: string) {
- return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)
+ return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
}
function isVideoNameValid (value: string) {
}
function isVideoPrivacyValid (value: string) {
- return VIDEO_PRIVACIES[value] !== undefined
+ return validator.isInt(value + '') && VIDEO_PRIVACIES[value] !== undefined
}
function isVideoFileInfoHashValid (value: string) {
language = parseInt(videoObject.language.identifier, 10)
}
+ let category = null
+ if (videoObject.category) {
+ category = parseInt(videoObject.category.identifier, 10)
+ }
+
+ let licence = null
+ if (videoObject.licence) {
+ licence = parseInt(videoObject.licence.identifier, 10)
+ }
+
+ let description = null
+ if (videoObject.content) {
+ description = videoObject.content
+ }
+
const videoData: VideoAttributes = {
name: videoObject.name,
uuid: videoObject.uuid,
url: videoObject.id,
- category: parseInt(videoObject.category.identifier, 10),
- licence: parseInt(videoObject.licence.identifier, 10),
+ category,
+ licence,
language,
+ description,
nsfw: videoObject.nsfw,
- description: videoObject.content,
channelId: videoChannel.id,
duration: parseInt(duration, 10),
createdAt: new Date(videoObject.published),
}
}
+ let category
+ if (this.category) {
+ category = {
+ identifier: this.category + '',
+ name: this.getCategoryLabel()
+ }
+ }
+
+ let licence
+ if (this.licence) {
+ licence = {
+ identifier: this.licence + '',
+ name: this.getLicenceLabel()
+ }
+ }
+
let likesObject
let dislikesObject
duration: 'PT' + this.duration + 'S',
uuid: this.uuid,
tag,
- category: {
- identifier: this.category + '',
- name: this.getCategoryLabel()
- },
- licence: {
- identifier: this.licence + '',
- name: this.getLicenceLabel()
- },
+ category,
+ licence,
language,
views: this.views,
nsfw: this.nsfw,
import 'mocha'
import * as chai from 'chai'
+import { join } from "path"
+import * as request from 'supertest'
import {
dateIsValid,
})
})
+ describe('With minimum parameters', function () {
+ it('Should upload and propagate the video', async function () {
+ this.timeout(50000)
+
+ const path = '/api/v1/videos/upload'
+
+ const req = request(servers[1].url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + servers[1].accessToken)
+ .field('name', 'minimum parameters')
+ .field('privacy', '1')
+ .field('nsfw', 'false')
+ .field('channelId', '1')
+
+ const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm')
+
+ await req.attach('videofile', filePath)
+ .expect(200)
+
+ await wait(25000)
+
+ for (const server of servers) {
+ const res = await getVideosList(server.url)
+ const video = res.body.data.find(v => v.name === 'minimum parameters')
+
+ expect(video.name).to.equal('minimum parameters')
+ expect(video.category).to.equal(null)
+ expect(video.categoryLabel).to.equal('Misc')
+ expect(video.licence).to.equal(null)
+ expect(video.licenceLabel).to.equal('Unknown')
+ expect(video.language).to.equal(null)
+ expect(video.languageLabel).to.equal('Unknown')
+ expect(video.nsfw).to.not.be.ok
+ expect(video.description).to.equal(null)
+ expect(video.serverHost).to.equal('localhost:9002')
+ expect(video.accountName).to.equal('root')
+ expect(video.tags).to.deep.equal([ ])
+ expect(dateIsValid(video.createdAt)).to.be.true
+ expect(dateIsValid(video.updatedAt)).to.be.true
+ }
+ })
+ })
+
after(async function () {
killallServers(servers)
expect(video.dislikes).to.equal(1)
})
- it('Should upload a video with minimum parameters', async function () {
- const path = '/api/v1/videos/upload'
-
- const req = request(server.url)
- .post(path)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + server.accessToken)
- .field('name', 'minimum parameters')
- .field('privacy', '1')
- .field('nsfw', 'false')
- .field('channelId', '1')
-
- const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm')
-
- await req.attach('videofile', filePath)
- .expect(200)
-
- const res = await getVideosList(server.url)
- const video = res.body.data.find(v => v.name === 'minimum parameters')
-
- expect(video.name).to.equal('minimum parameters')
- expect(video.category).to.equal(null)
- expect(video.categoryLabel).to.equal('Misc')
- expect(video.licence).to.equal(null)
- expect(video.licenceLabel).to.equal('Unknown')
- expect(video.language).to.equal(null)
- expect(video.languageLabel).to.equal('Unknown')
- expect(video.nsfw).to.not.be.ok
- expect(video.description).to.equal(null)
- expect(video.serverHost).to.equal('localhost:9001')
- expect(video.accountName).to.equal('root')
- expect(video.isLocal).to.be.true
- expect(video.tags).to.deep.equal([ ])
- expect(dateIsValid(video.createdAt)).to.be.true
- expect(dateIsValid(video.updatedAt)).to.be.true
- })
-
after(async function () {
killallServers([ server ])