aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-03-19 16:23:02 +0100
committerChocobozzz <me@florianbigard.com>2019-03-19 16:23:02 +0100
commit2ba92871319d7af63472c1380664a9f9eeb1c690 (patch)
treed593b2dfea29a8171b9f6afaaef076321f5edf71
parentd74d29ad9e35929491cf37223398d2535ab23de0 (diff)
downloadPeerTube-2ba92871319d7af63472c1380664a9f9eeb1c690.tar.gz
PeerTube-2ba92871319d7af63472c1380664a9f9eeb1c690.tar.zst
PeerTube-2ba92871319d7af63472c1380664a9f9eeb1c690.zip
Cleanup invalid rates/comments/shares
-rw-r--r--server/controllers/api/users/my-blocklist.ts4
-rw-r--r--server/controllers/api/videos/watching.ts1
-rw-r--r--server/helpers/audit-logger.ts5
-rw-r--r--server/lib/activitypub/crawl.ts9
-rw-r--r--server/lib/activitypub/share.ts7
-rw-r--r--server/lib/activitypub/video-comments.ts10
-rw-r--r--server/lib/activitypub/video-rates.ts21
-rw-r--r--server/lib/activitypub/videos.ts25
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-fetcher.ts13
-rw-r--r--server/middlewares/validators/users.ts2
-rw-r--r--server/models/account/account-video-rate.ts29
-rw-r--r--server/models/video/video-comment.ts14
-rw-r--r--server/models/video/video-share.ts14
-rw-r--r--server/models/video/video.ts2
-rw-r--r--server/tests/api/activitypub/refresher.ts2
-rw-r--r--server/tests/api/videos/multiple-servers.ts8
16 files changed, 117 insertions, 49 deletions
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
index 481e75139..713c16022 100644
--- a/server/controllers/api/users/my-blocklist.ts
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -17,11 +17,9 @@ import {
17 serversBlocklistSortValidator, 17 serversBlocklistSortValidator,
18 unblockServerByAccountValidator 18 unblockServerByAccountValidator
19} from '../../../middlewares/validators' 19} from '../../../middlewares/validators'
20import { AccountModel } from '../../../models/account/account'
21import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 20import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
22import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 21import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
23import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 22import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
24import { ServerModel } from '../../../models/server/server'
25 23
26const myBlocklistRouter = express.Router() 24const myBlocklistRouter = express.Router()
27 25
@@ -83,7 +81,7 @@ async function listBlockedAccounts (req: express.Request, res: express.Response)
83 81
84async function blockAccount (req: express.Request, res: express.Response) { 82async function blockAccount (req: express.Request, res: express.Response) {
85 const user = res.locals.oauth.token.User 83 const user = res.locals.oauth.token.User
86 const accountToBlock = res.locals.account 84 const accountToBlock = res.locals.account
87 85
88 await addAccountInBlocklist(user.Account.id, accountToBlock.id) 86 await addAccountInBlocklist(user.Account.id, accountToBlock.id)
89 87
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index 6bc60e045..dcd1f070d 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { UserWatchingVideo } from '../../../../shared'
3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' 3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares'
4import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 4import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
5import { UserModel } from '../../../models/account/user'
6 5
7const watchingRouter = express.Router() 6const watchingRouter = express.Router()
8 7
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index a121f0b8a..af37bce16 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -6,13 +6,12 @@ import * as flatten from 'flat'
6import * as winston from 'winston' 6import * as winston from 'winston'
7import { CONFIG } from '../initializers' 7import { CONFIG } from '../initializers'
8import { jsonLoggerFormat, labelFormatter } from './logger' 8import { jsonLoggerFormat, labelFormatter } from './logger'
9import { VideoDetails, User, VideoChannel, VideoAbuse, VideoImport } from '../../shared' 9import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared'
10import { VideoComment } from '../../shared/models/videos/video-comment.model' 10import { VideoComment } from '../../shared/models/videos/video-comment.model'
11import { CustomConfig } from '../../shared/models/server/custom-config.model' 11import { CustomConfig } from '../../shared/models/server/custom-config.model'
12import { UserModel } from '../models/account/user'
13 12
14function getAuditIdFromRes (res: express.Response) { 13function getAuditIdFromRes (res: express.Response) {
15 return (res.locals.oauth.token.User as UserModel).username 14 return res.locals.oauth.token.User.username
16} 15}
17 16
18enum AUDIT_TYPE { 17enum AUDIT_TYPE {
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts
index 2675524c6..9f4ca98ba 100644
--- a/server/lib/activitypub/crawl.ts
+++ b/server/lib/activitypub/crawl.ts
@@ -4,7 +4,10 @@ import { logger } from '../../helpers/logger'
4import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
6 6
7async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => (Promise<any> | Bluebird<any>)) { 7type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
8type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>)
9
10async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>, cleaner?: CleanerFunction) {
8 logger.info('Crawling ActivityPub data on %s.', uri) 11 logger.info('Crawling ActivityPub data on %s.', uri)
9 12
10 const options = { 13 const options = {
@@ -15,6 +18,8 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => (P
15 timeout: JOB_REQUEST_TIMEOUT 18 timeout: JOB_REQUEST_TIMEOUT
16 } 19 }
17 20
21 const startDate = new Date()
22
18 const response = await doRequest<ActivityPubOrderedCollection<T>>(options) 23 const response = await doRequest<ActivityPubOrderedCollection<T>>(options)
19 const firstBody = response.body 24 const firstBody = response.body
20 25
@@ -35,6 +40,8 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => (P
35 await handler(items) 40 await handler(items)
36 } 41 }
37 } 42 }
43
44 if (cleaner) await cleaner(startDate)
38} 45}
39 46
40export { 47export {
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index 1767df0ae..3bece0ff7 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -54,12 +54,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
54 url: shareUrl 54 url: shareUrl
55 } 55 }
56 56
57 await VideoShareModel.findOrCreate({ 57 await VideoShareModel.upsert(entry)
58 where: {
59 url: shareUrl
60 },
61 defaults: entry
62 })
63 } catch (err) { 58 } catch (err) {
64 logger.warn('Cannot add share %s.', shareUrl, { err }) 59 logger.warn('Cannot add share %s.', shareUrl, { err })
65 } 60 }
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index e87301fe7..3f9d8f0fc 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -34,8 +34,7 @@ async function videoCommentActivityObjectToDBAttributes (video: VideoModel, acto
34 accountId: actor.Account.id, 34 accountId: actor.Account.id,
35 inReplyToCommentId, 35 inReplyToCommentId,
36 originCommentId, 36 originCommentId,
37 createdAt: new Date(comment.published), 37 createdAt: new Date(comment.published)
38 updatedAt: new Date(comment.updated)
39 } 38 }
40} 39}
41 40
@@ -74,12 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
74 const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) 73 const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
75 if (!entry) return { created: false } 74 if (!entry) return { created: false }
76 75
77 const [ comment, created ] = await VideoCommentModel.findOrCreate({ 76 const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true })
78 where: {
79 url: body.id
80 },
81 defaults: entry
82 })
83 comment.Account = actor.Account 77 comment.Account = actor.Account
84 comment.Video = videoInstance 78 comment.Video = videoInstance
85 79
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts
index 7aac79118..ad7d81df6 100644
--- a/server/lib/activitypub/video-rates.ts
+++ b/server/lib/activitypub/video-rates.ts
@@ -38,19 +38,14 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa
38 38
39 const actor = await getOrCreateActorAndServerAndModel(actorUrl) 39 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
40 40
41 const [ , created ] = await AccountVideoRateModel 41 const entry = {
42 .findOrCreate({ 42 videoId: video.id,
43 where: { 43 accountId: actor.Account.id,
44 videoId: video.id, 44 type: rate,
45 accountId: actor.Account.id 45 url: body.id
46 }, 46 }
47 defaults: { 47
48 videoId: video.id, 48 const created = await AccountVideoRateModel.upsert(entry)
49 accountId: actor.Account.id,
50 type: rate,
51 url: body.id
52 }
53 })
54 49
55 if (created) rateCounts += 1 50 if (created) rateCounts += 1
56 } catch (err) { 51 } catch (err) {
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 66d0abf35..2c932371b 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -40,6 +40,9 @@ import { Notifier } from '../notifier'
40import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 40import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
41import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 41import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
42import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' 42import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
43import { AccountVideoRateModel } from '../../models/account/account-video-rate'
44import { VideoShareModel } from '../../models/video/video-share'
45import { VideoCommentModel } from '../../models/video/video-comment'
43 46
44async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { 47async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
45 // If the video is not private and published, we federate it 48 // If the video is not private and published, we federate it
@@ -134,31 +137,43 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid
134 const jobPayloads: ActivitypubHttpFetcherPayload[] = [] 137 const jobPayloads: ActivitypubHttpFetcherPayload[] = []
135 138
136 if (syncParam.likes === true) { 139 if (syncParam.likes === true) {
137 await crawlCollectionPage<string>(fetchedVideo.likes, items => createRates(items, video, 'like')) 140 const handler = items => createRates(items, video, 'like')
141 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate)
142
143 await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner)
138 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) 144 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err }))
139 } else { 145 } else {
140 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) 146 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' })
141 } 147 }
142 148
143 if (syncParam.dislikes === true) { 149 if (syncParam.dislikes === true) {
144 await crawlCollectionPage<string>(fetchedVideo.dislikes, items => createRates(items, video, 'dislike')) 150 const handler = items => createRates(items, video, 'dislike')
151 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate)
152
153 await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner)
145 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) 154 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err }))
146 } else { 155 } else {
147 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) 156 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' })
148 } 157 }
149 158
150 if (syncParam.shares === true) { 159 if (syncParam.shares === true) {
151 await crawlCollectionPage<string>(fetchedVideo.shares, items => addVideoShares(items, video)) 160 const handler = items => addVideoShares(items, video)
161 const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate)
162
163 await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner)
152 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) 164 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err }))
153 } else { 165 } else {
154 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) 166 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' })
155 } 167 }
156 168
157 if (syncParam.comments === true) { 169 if (syncParam.comments === true) {
158 await crawlCollectionPage<string>(fetchedVideo.comments, items => addVideoComments(items, video)) 170 const handler = items => addVideoComments(items, video)
171 const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate)
172
173 await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner)
159 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) 174 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err }))
160 } else { 175 } else {
161 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) 176 jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' })
162 } 177 }
163 178
164 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) 179 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }))
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
index 52225f64f..23d33c26f 100644
--- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts
@@ -1,4 +1,5 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import * as Bluebird from 'bluebird'
2import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
3import { processActivities } from '../../activitypub/process' 4import { processActivities } from '../../activitypub/process'
4import { addVideoComments } from '../../activitypub/video-comments' 5import { addVideoComments } from '../../activitypub/video-comments'
@@ -7,6 +8,9 @@ import { VideoModel } from '../../../models/video/video'
7import { addVideoShares, createRates } from '../../activitypub' 8import { addVideoShares, createRates } from '../../activitypub'
8import { createAccountPlaylists } from '../../activitypub/playlist' 9import { createAccountPlaylists } from '../../activitypub/playlist'
9import { AccountModel } from '../../../models/account/account' 10import { AccountModel } from '../../../models/account/account'
11import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
12import { VideoShareModel } from '../../../models/video/video-share'
13import { VideoCommentModel } from '../../../models/video/video-comment'
10 14
11type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' 15type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
12 16
@@ -37,7 +41,14 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
37 'account-playlists': items => createAccountPlaylists(items, account) 41 'account-playlists': items => createAccountPlaylists(items, account)
38 } 42 }
39 43
40 return crawlCollectionPage(payload.uri, fetcherType[payload.type]) 44 const cleanerType: { [ id in FetchType ]?: (crawlStartDate: Date) => Bluebird<any> } = {
45 'video-likes': crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate),
46 'video-dislikes': crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate),
47 'video-shares': crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate),
48 'video-comments': crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate)
49 }
50
51 return crawlCollectionPage(payload.uri, fetcherType[payload.type], cleanerType[payload.type])
41} 52}
42 53
43// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index e8ade0f97..4be446732 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -160,7 +160,7 @@ const usersUpdateMeValidator = [
160 .end() 160 .end()
161 } 161 }
162 162
163 const user= res.locals.oauth.token.User 163 const user = res.locals.oauth.token.User
164 if (await user.isPasswordMatch(req.body.currentPassword) !== true) { 164 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
165 return res.status(401) 165 return res.status(401)
166 .send({ error: 'currentPassword is invalid.' }) 166 .send({ error: 'currentPassword is invalid.' })
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index 18762f0c5..e5d39582b 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -1,5 +1,5 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import { Transaction } from 'sequelize' 2import { Transaction, Op } from 'sequelize'
3import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' 4import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
5import { VideoRateType } from '../../../shared/models/videos' 5import { VideoRateType } from '../../../shared/models/videos'
@@ -158,4 +158,31 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
158 158
159 return AccountVideoRateModel.findAndCountAll(query) 159 return AccountVideoRateModel.findAndCountAll(query)
160 } 160 }
161
162 static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
163 return AccountVideoRateModel.sequelize.transaction(async t => {
164 const query = {
165 where: {
166 updatedAt: {
167 [Op.lt]: beforeUpdatedAt
168 },
169 videoId,
170 type
171 },
172 transaction: t
173 }
174
175 const deleted = await AccountVideoRateModel.destroy(query)
176
177 const options = {
178 transaction: t,
179 where: {
180 id: videoId
181 }
182 }
183
184 if (type === 'like') await VideoModel.increment({ likes: -deleted }, options)
185 else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options)
186 })
187 }
161} 188}
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 1163f9a0e..e733138c1 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -1,4 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { Op } from 'sequelize'
2import { 3import {
3 AllowNull, 4 AllowNull,
4 BeforeDestroy, 5 BeforeDestroy,
@@ -453,6 +454,19 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
453 } 454 }
454 } 455 }
455 456
457 static cleanOldCommentsOf (videoId: number, beforeUpdatedAt: Date) {
458 const query = {
459 where: {
460 updatedAt: {
461 [Op.lt]: beforeUpdatedAt
462 },
463 videoId
464 }
465 }
466
467 return VideoCommentModel.destroy(query)
468 }
469
456 getCommentStaticPath () { 470 getCommentStaticPath () {
457 return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId() 471 return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId()
458 } 472 }
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 7df0ed18d..fb52b35d9 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,4 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { Op } from 'sequelize'
2import * as Bluebird from 'bluebird' 3import * as Bluebird from 'bluebird'
3import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 4import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
4import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
@@ -200,4 +201,17 @@ export class VideoShareModel extends Model<VideoShareModel> {
200 201
201 return VideoShareModel.findAndCountAll(query) 202 return VideoShareModel.findAndCountAll(query)
202 } 203 }
204
205 static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) {
206 const query = {
207 where: {
208 updatedAt: {
209 [Op.lt]: beforeUpdatedAt
210 },
211 videoId
212 }
213 }
214
215 return VideoShareModel.destroy(query)
216 }
203} 217}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index fb037c21a..b0d92b674 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1547,7 +1547,7 @@ export class VideoModel extends Model<VideoModel> {
1547 attributes: query.attributes, 1547 attributes: query.attributes,
1548 order: [ // Keep original order 1548 order: [ // Keep original order
1549 Sequelize.literal( 1549 Sequelize.literal(
1550 ids.map(id => `"VideoModel".id = ${id}`).join(', ') 1550 ids.map(id => `"VideoModel".id = ${id} DESC`).join(', ')
1551 ) 1551 )
1552 ] 1552 ]
1553 } 1553 }
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index ae4859076..665a9f9f0 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -8,7 +8,7 @@ import {
8 generateUserAccessToken, 8 generateUserAccessToken,
9 getVideo, 9 getVideo,
10 getVideoPlaylist, 10 getVideoPlaylist,
11 killallServers, 11 killallServers, rateVideo,
12 reRunServer, 12 reRunServer,
13 ServerInfo, 13 ServerInfo,
14 setAccessTokensToServers, 14 setAccessTokensToServers,
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 7e2fcb630..f91678140 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -579,15 +579,15 @@ describe('Test multiple servers', function () {
579 this.timeout(20000) 579 this.timeout(20000)
580 580
581 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') 581 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
582 await wait(200) 582 await wait(500)
583 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike') 583 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike')
584 await wait(200) 584 await wait(500)
585 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') 585 await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like')
586 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like') 586 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like')
587 await wait(200) 587 await wait(500)
588 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike') 588 await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike')
589 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike') 589 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike')
590 await wait(200) 590 await wait(500)
591 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like') 591 await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like')
592 592
593 await waitJobs(servers) 593 await waitJobs(servers)