diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index c532b5f32..7455cdf2b 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html @@ -14,10 +14,10 @@ Follower handle - State - Score - Created - + State + Score + Created + diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index cb62d52dd..f3bb7216b 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html @@ -20,10 +20,10 @@ Host - State - Created - Redundancy allowed - + State + Created + Redundancy allowed + diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html index 07362b3b9..a8dcc69d2 100644 --- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html @@ -17,11 +17,11 @@ > - - Strategy + + Strategy Video name Video URL - Total size + Total size diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html index 2efdd2bc3..e40c29abf 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html @@ -8,8 +8,8 @@ Account - Muted at - + Muted at + diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html index cec703289..bf5c00918 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html @@ -16,8 +16,8 @@ Instance - Muted at - + Muted at + diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index 89e9b47d3..9af76d2dd 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss @@ -1,5 +1,6 @@ @import 'variables'; @import 'mixins'; +@import 'miniature'; .form-sub-title { flex-grow: 0; @@ -22,12 +23,28 @@ } } +.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; diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 4ecb395f8..3d356dc7c 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html @@ -48,9 +48,10 @@ - + + @@ -75,7 +76,12 @@
-
+
+
+ The video was {{ videoAbuse.video.deleted ? 'deleted' : 'blacklisted' }} +
+
+
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index 8a1d3d618..7553a5eb3 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts @@ -1,9 +1,17 @@ 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) diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index bc6c58b06..c8623a5d4 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 485 +const LAST_MIGRATION_VERSION = 490 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0490-abuse-video.ts b/server/initializers/migrations/0490-abuse-video.ts new file mode 100644 index 000000000..26333feb5 --- /dev/null +++ b/server/initializers/migrations/0490-abuse-video.ts @@ -0,0 +1,28 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction + queryInterface: Sequelize.QueryInterface + sequelize: Sequelize.Sequelize +}): Promise { + + 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 +} diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index a4aef4024..7c316fe13 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts @@ -32,8 +32,7 @@ const videoAbuseGetValidator = [ 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() } @@ -53,8 +52,7 @@ const videoAbuseUpdateValidator = [ 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() } diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index da8c1577c..ea9856213 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -9,7 +9,7 @@ import { 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' @@ -46,6 +46,11 @@ export class VideoAbuseModel extends Model { @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) moderationComment: string + @AllowNull(true) + @Default(null) + @Column(DataType.JSONB) + deletedVideo: Video + @CreatedAt createdAt: Date @@ -58,9 +63,9 @@ export class VideoAbuseModel extends Model { @BelongsTo(() => AccountModel, { foreignKey: { - allowNull: false + allowNull: true }, - onDelete: 'cascade' + onDelete: 'set null' }) Account: AccountModel @@ -70,17 +75,21 @@ export class VideoAbuseModel extends Model { @BelongsTo(() => VideoModel, { foreignKey: { - allowNull: false + allowNull: true }, - onDelete: 'cascade' + onDelete: 'set null' }) Video: VideoModel - static loadByIdAndVideoId (id: number, videoId: number): Bluebird { + static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird { + const videoAttributes = {} + if (videoId) videoAttributes['videoId'] = videoId + if (uuid) videoAttributes['deletedVideo'] = { uuid } + const query = { where: { id, - videoId + ...videoAttributes } } return VideoAbuseModel.findOne(query) @@ -112,7 +121,7 @@ export class VideoAbuseModel extends Model { }, { model: VideoModel, - required: true + required: false } ] } @@ -124,6 +133,10 @@ export class VideoAbuseModel extends Model { } toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { + const video = this.Video + ? this.Video + : this.deletedVideo + return { id: this.id, reason: this.reason, @@ -134,9 +147,11 @@ export class VideoAbuseModel extends Model { }, 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 } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 0e7505af5..2636ebd8e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -628,9 +628,9 @@ export class VideoModel extends Model { @HasMany(() => VideoAbuseModel, { foreignKey: { name: 'videoId', - allowNull: false + allowNull: true }, - onDelete: 'cascade' + onDelete: 'set null' }) VideoAbuses: VideoAbuseModel[] @@ -798,6 +798,35 @@ export class VideoModel extends Model { ModelCache.Instance.invalidateCache('video', instance.id) } + @BeforeDestroy + static async saveEssentialDataToAbuses (instance: VideoModel, options) { + const tasks: Promise[] = [] + + 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 { const query = { where: { diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts index 955ec4780..49bd1ff2e 100644 --- a/server/typings/models/video/video-abuse.ts +++ b/server/typings/models/video/video-abuse.ts @@ -31,4 +31,4 @@ export type MVideoAbuseAccountVideo = export type MVideoAbuseFormattable = MVideoAbuse & Use<'Account', MAccountFormattable> & - Use<'Video', Pick> + Use<'Video', Pick> diff --git a/shared/models/videos/abuse/video-abuse.model.ts b/shared/models/videos/abuse/video-abuse.model.ts index 4f668795a..b47ee05a0 100644 --- a/shared/models/videos/abuse/video-abuse.model.ts +++ b/shared/models/videos/abuse/video-abuse.model.ts @@ -14,6 +14,8 @@ export interface VideoAbuse { id: number name: string uuid: string + nsfw: boolean + deleted: boolean } createdAt: Date