Fix missing transactions

pull/4161/head
Chocobozzz 2021-06-15 09:17:19 +02:00
parent 51f636ad0f
commit eae0365b5c
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
14 changed files with 65 additions and 69 deletions

View File

@ -176,7 +176,7 @@ async function removeOrRejectFollower (req: express.Request, res: express.Respon
async function acceptFollower (req: express.Request, res: express.Response) { async function acceptFollower (req: express.Request, res: express.Response) {
const follow = res.locals.follow const follow = res.locals.follow
await sendAccept(follow) sendAccept(follow)
follow.state = 'accepted' follow.state = 'accepted'
await follow.save() await follow.save()

View File

@ -105,9 +105,9 @@ function acceptOwnership (req: express.Request, res: express.Response) {
const channel = res.locals.videoChannel const channel = res.locals.videoChannel
// We need more attributes for federation // We need more attributes for federation
const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id) const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id, t)
const oldVideoChannel = await VideoChannelModel.loadAndPopulateAccount(targetVideo.channelId) const oldVideoChannel = await VideoChannelModel.loadAndPopulateAccount(targetVideo.channelId, t)
targetVideo.channelId = channel.id targetVideo.channelId = channel.id

View File

@ -1,12 +1,13 @@
import { MActorFollowActors } from '../../types/models' import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application'
import { logger } from '../../helpers/logger'
import { CONFIG } from '../../initializers/config' import { CONFIG } from '../../initializers/config'
import { SERVER_ACTOR_NAME } from '../../initializers/constants' import { SERVER_ACTOR_NAME } from '../../initializers/constants'
import { JobQueue } from '../job-queue'
import { logger } from '../../helpers/logger'
import { ServerModel } from '../../models/server/server' import { ServerModel } from '../../models/server/server'
import { getServerActor } from '@server/models/application/application' import { MActorFollowActors } from '../../types/models'
import { JobQueue } from '../job-queue'
async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors, transaction?: Transaction) {
if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return
const follower = actorFollow.ActorFollower const follower = actorFollow.ActorFollower
@ -16,7 +17,7 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) {
const me = await getServerActor() const me = await getServerActor()
const server = await ServerModel.load(follower.serverId) const server = await ServerModel.load(follower.serverId, transaction)
const host = server.host const host = server.host
const payload = { const payload = {

View File

@ -16,7 +16,6 @@ import {
MChannelActor, MChannelActor,
MCommentOwnerVideo MCommentOwnerVideo
} from '../../../types/models' } from '../../../types/models'
import { markCommentAsDeleted } from '../../video-comment'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
@ -139,11 +138,9 @@ function processDeleteVideoComment (byActor: MActorSignature, videoComment: MCom
throw new Error(`Account ${byActor.url} does not own video comment ${videoComment.url} or video ${videoComment.Video.url}`) throw new Error(`Account ${byActor.url} does not own video comment ${videoComment.url} or video ${videoComment.Video.url}`)
} }
await sequelizeTypescript.transaction(async t => { videoComment.markAsDeleted()
markCommentAsDeleted(videoComment)
await videoComment.save() await videoComment.save({ transaction: t })
})
if (videoComment.Video.isOwned()) { if (videoComment.Video.isOwned()) {
// Don't resend the activity to the sender // Don't resend the activity to the sender

View File

@ -43,7 +43,7 @@ async function processFollow (byActor: MActorSignature, activityId: string, targ
if (isFollowingInstance && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) { if (isFollowingInstance && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) {
logger.info('Rejecting %s because instance followers are disabled.', targetActor.url) logger.info('Rejecting %s because instance followers are disabled.', targetActor.url)
await sendReject(activityId, byActor, targetActor) sendReject(activityId, byActor, targetActor)
return { actorFollow: undefined as MActorFollowActors } return { actorFollow: undefined as MActorFollowActors }
} }
@ -84,8 +84,9 @@ async function processFollow (byActor: MActorSignature, activityId: string, targ
// Target sends to actor he accepted the follow request // Target sends to actor he accepted the follow request
if (actorFollow.state === 'accepted') { if (actorFollow.state === 'accepted') {
await sendAccept(actorFollow) sendAccept(actorFollow)
await autoFollowBackIfNeeded(actorFollow)
await autoFollowBackIfNeeded(actorFollow, t)
} }
return { actorFollow, created, isFollowingInstance, targetActor } return { actorFollow, created, isFollowingInstance, targetActor }

View File

@ -106,7 +106,7 @@ async function processUndoCacheFile (byActor: MActorSignature, activity: Activit
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object }) const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {
const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id) const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
if (!cacheFile) { if (!cacheFile) {
logger.debug('Cannot undo unknown video cache %s.', cacheFileObject.id) logger.debug('Cannot undo unknown video cache %s.', cacheFileObject.id)
return return
@ -114,7 +114,7 @@ async function processUndoCacheFile (byActor: MActorSignature, activity: Activit
if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.') if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.')
await cacheFile.destroy() await cacheFile.destroy({ transaction: t })
if (video.isOwned()) { if (video.isOwned()) {
// Don't resend the activity to the sender // Don't resend the activity to the sender

View File

@ -151,35 +151,31 @@ async function onVideoFileOptimizer (
// Outside the transaction (IO on disk) // Outside the transaction (IO on disk)
const { videoFileResolution, isPortraitMode } = await videoArg.getMaxQualityResolution() const { videoFileResolution, isPortraitMode } = await videoArg.getMaxQualityResolution()
const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { // Maybe the video changed in database, refresh it
// Maybe the video changed in database, refresh it const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid)
const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) // Video does not exist anymore
// Video does not exist anymore if (!videoDatabase) return undefined
if (!videoDatabase) return undefined
let videoPublished = false let videoPublished = false
// Generate HLS version of the original file // Generate HLS version of the original file
const originalFileHLSPayload = Object.assign({}, payload, { const originalFileHLSPayload = Object.assign({}, payload, {
isPortraitMode, isPortraitMode,
resolution: videoDatabase.getMaxQualityFile().resolution, resolution: videoDatabase.getMaxQualityFile().resolution,
// If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues
copyCodecs: transcodeType !== 'quick-transcode', copyCodecs: transcodeType !== 'quick-transcode',
isMaxQuality: true isMaxQuality: true
})
const hasHls = await createHlsJobIfEnabled(user, originalFileHLSPayload)
const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode, 'webtorrent')
if (!hasHls && !hasNewResolutions) {
// No transcoding to do, it's now published
videoPublished = await videoDatabase.publishIfNeededAndSave(t)
}
await federateVideoIfNeeded(videoDatabase, payload.isNewVideo, t)
return { videoDatabase, videoPublished }
}) })
const hasHls = await createHlsJobIfEnabled(user, originalFileHLSPayload)
const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode, 'webtorrent')
if (!hasHls && !hasNewResolutions) {
// No transcoding to do, it's now published
videoPublished = await videoDatabase.publishIfNeededAndSave(undefined)
}
await federateVideoIfNeeded(videoDatabase, payload.isNewVideo)
if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoDatabase) if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoDatabase)
if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)

View File

@ -221,7 +221,7 @@ async function createAbuse (options: {
const { isOwned } = await associateFun(abuseInstance) const { isOwned } = await associateFun(abuseInstance)
if (isOwned === false) { if (isOwned === false) {
await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction)
} }
const abuseJSON = abuseInstance.toFormattedAdminJSON() const abuseJSON = abuseInstance.toFormattedAdminJSON()

View File

@ -44,11 +44,11 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
displayName: userDisplayName, displayName: userDisplayName,
userId: userCreated.id, userId: userCreated.id,
applicationId: null, applicationId: null,
t: t t
}) })
userCreated.Account = accountCreated userCreated.Account = accountCreated
const channelAttributes = await buildChannelAttributes(userCreated, channelNames) const channelAttributes = await buildChannelAttributes(userCreated, t, channelNames)
const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t) const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t)
const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
@ -203,13 +203,13 @@ function createDefaultUserNotificationSettings (user: MUserId, t: Transaction |
return UserNotificationSettingModel.create(values, { transaction: t }) return UserNotificationSettingModel.create(values, { transaction: t })
} }
async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) { async function buildChannelAttributes (user: MUser, transaction?: Transaction, channelNames?: ChannelNames) {
if (channelNames) return channelNames if (channelNames) return channelNames
let channelName = user.username + '_channel' let channelName = user.username + '_channel'
// Conflict, generate uuid instead // Conflict, generate uuid instead
const actor = await ActorModel.loadLocalByName(channelName) const actor = await ActorModel.loadLocalByName(channelName, transaction)
if (actor) channelName = uuidv4() if (actor) channelName = uuidv4()
const videoChannelDisplayName = `Main ${user.username} channel` const videoChannelDisplayName = `Main ${user.username} channel`

View File

@ -18,9 +18,9 @@ async function removeComment (videoCommentInstance: MCommentOwnerVideo) {
await sendDeleteVideoComment(videoCommentInstance, t) await sendDeleteVideoComment(videoCommentInstance, t)
} }
markCommentAsDeleted(videoCommentInstance) videoCommentInstance.markAsDeleted()
await videoCommentInstance.save() await videoCommentInstance.save({ transaction: t })
}) })
logger.info('Video comment %d deleted.', videoCommentInstance.id) logger.info('Video comment %d deleted.', videoCommentInstance.id)
@ -95,17 +95,10 @@ function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>):
return thread return thread
} }
function markCommentAsDeleted (comment: MComment): void {
comment.text = ''
comment.deletedAt = new Date()
comment.accountId = null
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
removeComment, removeComment,
createVideoComment, createVideoComment,
buildFormattedCommentTree, buildFormattedCommentTree
markCommentAsDeleted
} }

View File

@ -1,3 +1,4 @@
import { Transaction } from 'sequelize'
import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { MServer, MServerFormattable } from '@server/types/models/server' import { MServer, MServerFormattable } from '@server/types/models/server'
import { AttributesOnly } from '@shared/core-utils' import { AttributesOnly } from '@shared/core-utils'
@ -51,11 +52,12 @@ export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> {
}) })
BlockedByAccounts: ServerBlocklistModel[] BlockedByAccounts: ServerBlocklistModel[]
static load (id: number): Promise<MServer> { static load (id: number, transaction?: Transaction): Promise<MServer> {
const query = { const query = {
where: { where: {
id id
} },
transaction
} }
return ServerModel.findOne(query) return ServerModel.findOne(query)

View File

@ -91,9 +91,9 @@ export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaption
Video: VideoModel Video: VideoModel
@BeforeDestroy @BeforeDestroy
static async removeFiles (instance: VideoCaptionModel) { static async removeFiles (instance: VideoCaptionModel, options) {
if (!instance.Video) { if (!instance.Video) {
instance.Video = await instance.$get('Video') instance.Video = await instance.$get('Video', { transaction: options.transaction })
} }
if (instance.isOwned()) { if (instance.isOwned()) {
@ -113,8 +113,7 @@ export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaption
const videoInclude = { const videoInclude = {
model: VideoModel.unscoped(), model: VideoModel.unscoped(),
attributes: [ 'id', 'remote', 'uuid' ], attributes: [ 'id', 'remote', 'uuid' ],
where: buildWhereIdOrUUID(videoId), where: buildWhereIdOrUUID(videoId)
transaction
} }
const query = { const query = {
@ -123,7 +122,8 @@ export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaption
}, },
include: [ include: [
videoInclude videoInclude
] ],
transaction
} }
return VideoCaptionModel.findOne(query) return VideoCaptionModel.findOne(query)

View File

@ -522,10 +522,10 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
}) })
} }
static loadAndPopulateAccount (id: number): Promise<MChannelBannerAccountDefault> { static loadAndPopulateAccount (id: number, transaction?: Transaction): Promise<MChannelBannerAccountDefault> {
return VideoChannelModel.unscoped() return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ]) .scope([ ScopeNames.WITH_ACTOR_BANNER, ScopeNames.WITH_ACCOUNT ])
.findByPk(id) .findByPk(id, { transaction })
} }
static loadByUrlAndPopulateAccount (url: string): Promise<MChannelBannerAccountDefault> { static loadByUrlAndPopulateAccount (url: string): Promise<MChannelBannerAccountDefault> {

View File

@ -739,6 +739,12 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment
return this.Account.isOwned() return this.Account.isOwned()
} }
markAsDeleted () {
this.text = ''
this.deletedAt = new Date()
this.accountId = null
}
isDeleted () { isDeleted () {
return this.deletedAt !== null return this.deletedAt !== null
} }