Fix missing transactions
This commit is contained in:
parent
51f636ad0f
commit
eae0365b5c
14 changed files with 65 additions and 69 deletions
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue