diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-04-16 14:22:27 +0200 |
---|---|---|
committer | Rigel Kent <par@rigelk.eu> | 2020-05-01 16:41:02 +0200 |
commit | 68d19a0ace01cb7a3550da420d27663e2db1b98d (patch) | |
tree | fcdac5341001b9e6d15ddd0ca8239372ec2b3053 /server | |
parent | 165ee2929bc76fc7f9985ae81cc33736820c7865 (diff) | |
download | PeerTube-68d19a0ace01cb7a3550da420d27663e2db1b98d.tar.gz PeerTube-68d19a0ace01cb7a3550da420d27663e2db1b98d.tar.zst PeerTube-68d19a0ace01cb7a3550da420d27663e2db1b98d.zip |
Make sure a report doesn't get deleted upon the deletion of its video
Diffstat (limited to 'server')
-rw-r--r-- | server/helpers/middlewares/video-abuses.ts | 12 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/migrations/0490-abuse-video.ts | 28 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-abuses.ts | 6 | ||||
-rw-r--r-- | server/models/video/video-abuse.ts | 37 | ||||
-rw-r--r-- | server/models/video/video.ts | 33 | ||||
-rw-r--r-- | server/typings/models/video/video-abuse.ts | 2 |
7 files changed, 99 insertions, 21 deletions
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 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
3 | import { fetchVideo } from '../video' | ||
3 | 4 | ||
4 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoId: number, res: Response) { | 5 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { |
5 | const abuseId = parseInt(abuseIdArg + '', 10) | 6 | const abuseId = parseInt(abuseIdArg + '', 10) |
6 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 7 | let videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) |
8 | |||
9 | if (!videoAbuse) { | ||
10 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
11 | const video = await fetchVideo(videoUUID, 'all', userId) | ||
12 | |||
13 | if (video) videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, video.id) | ||
14 | } | ||
7 | 15 | ||
8 | if (videoAbuse === null) { | 16 | if (videoAbuse === null) { |
9 | res.status(404) | 17 | 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' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 485 | 17 | const LAST_MIGRATION_VERSION = 490 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | }): Promise<void> { | ||
8 | |||
9 | const deletedVideo = { | ||
10 | type: Sequelize.JSONB, | ||
11 | allowNull: true | ||
12 | } | ||
13 | await utils.queryInterface.addColumn('videoAbuse', 'deletedVideo', deletedVideo) | ||
14 | await utils.sequelize.query(`ALTER TABLE "videoAbsue" ALTER COLUMN "videoId" DROP NOT NULL;`) | ||
15 | await utils.sequelize.query(`ALTER TABLE "videoAbuse" DROP CONSTRAINT IF EXISTS "videoAbuse_videoId_fkey";`) | ||
16 | await utils.sequelize.query(`ALTER TABLE "videoAbuse" ADD CONSTRAINT "videoAbuse_videoId_fkey" | ||
17 | FOREIGN KEY ("videoId") REFERENCES video(id) ON UPDATE CASCADE ON DELETE SET NULL;`) | ||
18 | |||
19 | } | ||
20 | |||
21 | function down (options) { | ||
22 | throw new Error('Not implemented.') | ||
23 | } | ||
24 | |||
25 | export { | ||
26 | up, | ||
27 | down | ||
28 | } | ||
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 = [ | |||
32 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | 32 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) |
33 | 33 | ||
34 | if (areValidationErrors(req, res)) return | 34 | if (areValidationErrors(req, res)) return |
35 | if (!await doesVideoExist(req.params.videoId, res)) return | 35 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return |
36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return | ||
37 | 36 | ||
38 | return next() | 37 | return next() |
39 | } | 38 | } |
@@ -53,8 +52,7 @@ const videoAbuseUpdateValidator = [ | |||
53 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | 52 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) |
54 | 53 | ||
55 | if (areValidationErrors(req, res)) return | 54 | if (areValidationErrors(req, res)) return |
56 | if (!await doesVideoExist(req.params.videoId, res)) return | 55 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return |
57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return | ||
58 | 56 | ||
59 | return next() | 57 | return next() |
60 | } | 58 | } |
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 { | |||
9 | import { AccountModel } from '../account/account' | 9 | import { AccountModel } from '../account/account' |
10 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' | 10 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' |
11 | import { VideoModel } from './video' | 11 | import { VideoModel } from './video' |
12 | import { VideoAbuseState } from '../../../shared' | 12 | import { VideoAbuseState, Video } from '../../../shared' |
13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
14 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' | 14 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' |
15 | import * as Bluebird from 'bluebird' | 15 | import * as Bluebird from 'bluebird' |
@@ -46,6 +46,11 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
46 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) | 46 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) |
47 | moderationComment: string | 47 | moderationComment: string |
48 | 48 | ||
49 | @AllowNull(true) | ||
50 | @Default(null) | ||
51 | @Column(DataType.JSONB) | ||
52 | deletedVideo: Video | ||
53 | |||
49 | @CreatedAt | 54 | @CreatedAt |
50 | createdAt: Date | 55 | createdAt: Date |
51 | 56 | ||
@@ -58,9 +63,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
58 | 63 | ||
59 | @BelongsTo(() => AccountModel, { | 64 | @BelongsTo(() => AccountModel, { |
60 | foreignKey: { | 65 | foreignKey: { |
61 | allowNull: false | 66 | allowNull: true |
62 | }, | 67 | }, |
63 | onDelete: 'cascade' | 68 | onDelete: 'set null' |
64 | }) | 69 | }) |
65 | Account: AccountModel | 70 | Account: AccountModel |
66 | 71 | ||
@@ -70,17 +75,21 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
70 | 75 | ||
71 | @BelongsTo(() => VideoModel, { | 76 | @BelongsTo(() => VideoModel, { |
72 | foreignKey: { | 77 | foreignKey: { |
73 | allowNull: false | 78 | allowNull: true |
74 | }, | 79 | }, |
75 | onDelete: 'cascade' | 80 | onDelete: 'set null' |
76 | }) | 81 | }) |
77 | Video: VideoModel | 82 | Video: VideoModel |
78 | 83 | ||
79 | static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> { | 84 | static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> { |
85 | const videoAttributes = {} | ||
86 | if (videoId) videoAttributes['videoId'] = videoId | ||
87 | if (uuid) videoAttributes['deletedVideo'] = { uuid } | ||
88 | |||
80 | const query = { | 89 | const query = { |
81 | where: { | 90 | where: { |
82 | id, | 91 | id, |
83 | videoId | 92 | ...videoAttributes |
84 | } | 93 | } |
85 | } | 94 | } |
86 | return VideoAbuseModel.findOne(query) | 95 | return VideoAbuseModel.findOne(query) |
@@ -112,7 +121,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
112 | }, | 121 | }, |
113 | { | 122 | { |
114 | model: VideoModel, | 123 | model: VideoModel, |
115 | required: true | 124 | required: false |
116 | } | 125 | } |
117 | ] | 126 | ] |
118 | } | 127 | } |
@@ -124,6 +133,10 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
124 | } | 133 | } |
125 | 134 | ||
126 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { | 135 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { |
136 | const video = this.Video | ||
137 | ? this.Video | ||
138 | : this.deletedVideo | ||
139 | |||
127 | return { | 140 | return { |
128 | id: this.id, | 141 | id: this.id, |
129 | reason: this.reason, | 142 | reason: this.reason, |
@@ -134,9 +147,11 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
134 | }, | 147 | }, |
135 | moderationComment: this.moderationComment, | 148 | moderationComment: this.moderationComment, |
136 | video: { | 149 | video: { |
137 | id: this.Video.id, | 150 | id: video.id, |
138 | uuid: this.Video.uuid, | 151 | uuid: video.uuid, |
139 | name: this.Video.name | 152 | name: video.name, |
153 | nsfw: video.nsfw, | ||
154 | deleted: !this.Video | ||
140 | }, | 155 | }, |
141 | createdAt: this.createdAt | 156 | createdAt: this.createdAt |
142 | } | 157 | } |
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<VideoModel> { | |||
628 | @HasMany(() => VideoAbuseModel, { | 628 | @HasMany(() => VideoAbuseModel, { |
629 | foreignKey: { | 629 | foreignKey: { |
630 | name: 'videoId', | 630 | name: 'videoId', |
631 | allowNull: false | 631 | allowNull: true |
632 | }, | 632 | }, |
633 | onDelete: 'cascade' | 633 | onDelete: 'set null' |
634 | }) | 634 | }) |
635 | VideoAbuses: VideoAbuseModel[] | 635 | VideoAbuses: VideoAbuseModel[] |
636 | 636 | ||
@@ -798,6 +798,35 @@ export class VideoModel extends Model<VideoModel> { | |||
798 | ModelCache.Instance.invalidateCache('video', instance.id) | 798 | ModelCache.Instance.invalidateCache('video', instance.id) |
799 | } | 799 | } |
800 | 800 | ||
801 | @BeforeDestroy | ||
802 | static async saveEssentialDataToAbuses (instance: VideoModel, options) { | ||
803 | const tasks: Promise<any>[] = [] | ||
804 | |||
805 | logger.info('Saving video abuses details of video %s.', instance.url) | ||
806 | |||
807 | if (!Array.isArray(instance.VideoAbuses)) { | ||
808 | instance.VideoAbuses = await instance.$get('VideoAbuses') | ||
809 | |||
810 | if (instance.VideoAbuses.length === 0) return undefined | ||
811 | } | ||
812 | |||
813 | const details = instance.toFormattedJSON() | ||
814 | |||
815 | for (const abuse of instance.VideoAbuses) { | ||
816 | tasks.push((_ => { | ||
817 | abuse.deletedVideo = details | ||
818 | return abuse.save({ transaction: options.transaction }) | ||
819 | })()) | ||
820 | } | ||
821 | |||
822 | Promise.all(tasks) | ||
823 | .catch(err => { | ||
824 | logger.error('Some errors when saving details of video %s in its abuses before destroy hook.', instance.uuid, { err }) | ||
825 | }) | ||
826 | |||
827 | return undefined | ||
828 | } | ||
829 | |||
801 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { | 830 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
802 | const query = { | 831 | const query = { |
803 | where: { | 832 | 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 = | |||
31 | export type MVideoAbuseFormattable = | 31 | export type MVideoAbuseFormattable = |
32 | MVideoAbuse & | 32 | MVideoAbuse & |
33 | Use<'Account', MAccountFormattable> & | 33 | Use<'Account', MAccountFormattable> & |
34 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> | 34 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name' | 'nsfw'>> |