<ng-template pTemplate="header">
<tr>
<th i18n>Follower handle</th>
- <th i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
- <th i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
- <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th></th>
+ <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
+ <th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
+ <th style="width: 200px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th style="width: 100px;"></th>
</tr>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th i18n>Host</th>
- <th i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
- <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th>
- <th></th>
+ <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
+ <th style="width: 200px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th style="width: 160px;" i18n pSortableColumn="redundancyAllowed">Redundancy allowed <p-sortIcon field="redundancyAllowed"></p-sortIcon></th>
+ <th style="width: 100px;"></th>
</tr>
</ng-template>
>
<ng-template pTemplate="header">
<tr>
- <th style="width: 40px"></th>
- <th i18n *ngIf="isDisplayingRemoteVideos()">Strategy</th>
+ <th style="width: 40px;"></th>
+ <th style="width: 160px;" i18n *ngIf="isDisplayingRemoteVideos()">Strategy</th>
<th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
<th i18n>Video URL</th>
- <th i18n *ngIf="isDisplayingRemoteVideos()">Total size</th>
+ <th style="width: 100px;" i18n *ngIf="isDisplayingRemoteVideos()">Total size</th>
<th style="width: 80px;"></th>
</tr>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th i18n>Account</th>
- <th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th></th> <!-- column for action buttons -->
+ <th style="width: 200px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th style="width: 100px;"></th> <!-- column for action buttons -->
</tr>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th i18n>Instance</th>
- <th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
- <th></th> <!-- column for action buttons -->
+ <th style="width: 200px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
+ <th style="width: 100px;"></th> <!-- column for action buttons -->
</tr>
</ng-template>
@import 'variables';
@import 'mixins';
+@import 'miniature';
.form-sub-title {
flex-grow: 0;
}
}
+.video-abuse-states {
+ & > :not(:first-child) {
+ margin-left: .4rem;
+ }
+}
+
.screenratio {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56%;
+ div {
+ @include miniature-thumbnail;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ }
+
::ng-deep iframe {
position: absolute;
width: 100% !important;
</a>
</td>
- <td class="c-hand" [pRowToggler]="videoAbuse">
+ <td class="c-hand video-abuse-states" [pRowToggler]="videoAbuse">
<span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span>
<span *ngIf="isVideoAbuseRejected(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-remove"></span>
+ <span *ngIf="videoAbuse.moderationComment" [title]="videoAbuse.moderationComment" class="glyphicon glyphicon-comment"></span>
</td>
<td class="action-cell">
</div>
<div class="col-4">
- <div class="screenratio" [innerHTML]="videoAbuse.embedHtml"></div>
+ <div class="screenratio">
+ <div *ngIf="videoAbuse.video.deleted">
+ <span i18n>The video was {{ videoAbuse.video.deleted ? 'deleted' : 'blacklisted' }}</span>
+ </div>
+ <div *ngIf="!videoAbuse.video.deleted" [innerHTML]="videoAbuse.embedHtml"></div>
+ </div>
</div>
</div>
</td>
import { Response } from 'express'
import { VideoAbuseModel } from '../../models/video/video-abuse'
+import { fetchVideo } from '../video'
-async function doesVideoAbuseExist (abuseIdArg: number | string, videoId: number, res: Response) {
+async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) {
const abuseId = parseInt(abuseIdArg + '', 10)
- const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
+ let videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID)
+
+ if (!videoAbuse) {
+ const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
+ const video = await fetchVideo(videoUUID, 'all', userId)
+
+ if (video) videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, video.id)
+ }
if (videoAbuse === null) {
res.status(404)
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 485
+const LAST_MIGRATION_VERSION = 490
// ---------------------------------------------------------------------------
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction
+ queryInterface: Sequelize.QueryInterface
+ sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+ const deletedVideo = {
+ type: Sequelize.JSONB,
+ allowNull: true
+ }
+ await utils.queryInterface.addColumn('videoAbuse', 'deletedVideo', deletedVideo)
+ await utils.sequelize.query(`ALTER TABLE "videoAbsue" ALTER COLUMN "videoId" DROP NOT NULL;`)
+ await utils.sequelize.query(`ALTER TABLE "videoAbuse" DROP CONSTRAINT IF EXISTS "videoAbuse_videoId_fkey";`)
+ await utils.sequelize.query(`ALTER TABLE "videoAbuse" ADD CONSTRAINT "videoAbuse_videoId_fkey"
+ FOREIGN KEY ("videoId") REFERENCES video(id) ON UPDATE CASCADE ON DELETE SET NULL;`)
+
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
- if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
+ if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return
return next()
}
logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
- if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
+ if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return
return next()
}
import { AccountModel } from '../account/account'
import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
-import { VideoAbuseState } from '../../../shared'
+import { VideoAbuseState, Video } from '../../../shared'
import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
import * as Bluebird from 'bluebird'
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max))
moderationComment: string
+ @AllowNull(true)
+ @Default(null)
+ @Column(DataType.JSONB)
+ deletedVideo: Video
+
@CreatedAt
createdAt: Date
@BelongsTo(() => AccountModel, {
foreignKey: {
- allowNull: false
+ allowNull: true
},
- onDelete: 'cascade'
+ onDelete: 'set null'
})
Account: AccountModel
@BelongsTo(() => VideoModel, {
foreignKey: {
- allowNull: false
+ allowNull: true
},
- onDelete: 'cascade'
+ onDelete: 'set null'
})
Video: VideoModel
- static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> {
+ static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> {
+ const videoAttributes = {}
+ if (videoId) videoAttributes['videoId'] = videoId
+ if (uuid) videoAttributes['deletedVideo'] = { uuid }
+
const query = {
where: {
id,
- videoId
+ ...videoAttributes
}
}
return VideoAbuseModel.findOne(query)
},
{
model: VideoModel,
- required: true
+ required: false
}
]
}
}
toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse {
+ const video = this.Video
+ ? this.Video
+ : this.deletedVideo
+
return {
id: this.id,
reason: this.reason,
},
moderationComment: this.moderationComment,
video: {
- id: this.Video.id,
- uuid: this.Video.uuid,
- name: this.Video.name
+ id: video.id,
+ uuid: video.uuid,
+ name: video.name,
+ nsfw: video.nsfw,
+ deleted: !this.Video
},
createdAt: this.createdAt
}
@HasMany(() => VideoAbuseModel, {
foreignKey: {
name: 'videoId',
- allowNull: false
+ allowNull: true
},
- onDelete: 'cascade'
+ onDelete: 'set null'
})
VideoAbuses: VideoAbuseModel[]
ModelCache.Instance.invalidateCache('video', instance.id)
}
+ @BeforeDestroy
+ static async saveEssentialDataToAbuses (instance: VideoModel, options) {
+ const tasks: Promise<any>[] = []
+
+ logger.info('Saving video abuses details of video %s.', instance.url)
+
+ if (!Array.isArray(instance.VideoAbuses)) {
+ instance.VideoAbuses = await instance.$get('VideoAbuses')
+
+ if (instance.VideoAbuses.length === 0) return undefined
+ }
+
+ const details = instance.toFormattedJSON()
+
+ for (const abuse of instance.VideoAbuses) {
+ tasks.push((_ => {
+ abuse.deletedVideo = details
+ return abuse.save({ transaction: options.transaction })
+ })())
+ }
+
+ Promise.all(tasks)
+ .catch(err => {
+ logger.error('Some errors when saving details of video %s in its abuses before destroy hook.', instance.uuid, { err })
+ })
+
+ return undefined
+ }
+
static listLocal (): Bluebird<MVideoWithAllFiles[]> {
const query = {
where: {
export type MVideoAbuseFormattable =
MVideoAbuse &
Use<'Account', MAccountFormattable> &
- Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>
+ Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name' | 'nsfw'>>
id: number
name: string
uuid: string
+ nsfw: boolean
+ deleted: boolean
}
createdAt: Date