app/model/commands/UpdateAtomCommand.scala (121 lines of code) (raw):
package model.commands
import java.util.Date
import com.gu.atom.data.{AtomSerializer, VersionConflictError}
import com.gu.contentatom.thrift.{Atom, ContentAtomEvent, EventType, ChangeRecord => ThriftChangeRecord}
import com.gu.media.logging.Logging
import com.gu.media.model.{AtomAssignedProjectMessage, AuditMessage, ChangeRecord, MediaAtom}
import com.gu.media.upload.PlutoUploadActions
import com.gu.media.util.MediaAtomImplicits
import com.gu.pandomainauth.model.{User => PandaUser}
import data.DataStores
import model.commands.CommandExceptions._
import model.commands.UpdateAtomCommand.createDiffString
import org.joda.time.DateTime
import util.AWSConfig
import scala.util.{Failure, Success}
case class UpdateAtomCommand(id: String, atom: MediaAtom, override val stores: DataStores, user: PandaUser, awsConfig: AWSConfig)
extends Command
with MediaAtomImplicits
with Logging {
type T = MediaAtom
def getDateIfNotPublished(dateRecord: Option[ChangeRecord], published: Option[ThriftChangeRecord]): Option[DateTime] =
published match {
case Some(_) => None
case None => dateRecord.map(_.date)
}
def process(): T = {
log.info(s"Request to update atom ${atom.id}")
if (id != atom.id) {
AtomIdConflict
}
val existingAtom = getPreviewAtom(atom.id)
val diffString = createDiffString(MediaAtom.fromThrift(existingAtom), atom)
log.info(s"Update atom changes ${atom.id}: $diffString")
val changeRecord = ChangeRecord.now(user)
val atomIsPublished = existingAtom.contentChangeDetails.published
val scheduledLaunchDate: Option[DateTime] = getDateIfNotPublished(atom.contentChangeDetails.scheduledLaunch, atomIsPublished)
val embargo: Option[DateTime] = getDateIfNotPublished(atom.contentChangeDetails.embargo, atomIsPublished)
val expiry: Option[DateTime] = atom.expiryDate.map(expiry => new DateTime(expiry))
def updateIfChanged(newDate: Option[DateTime], changeRecord: Option[ThriftChangeRecord]): Option[ChangeRecord] = {
if (changeRecord.map(_.date) == newDate.map(_.getMillis)) {
changeRecord.map(ChangeRecord.fromThrift)
} else {
newDate.map(ChangeRecord.build(_, user))
}
}
val existingChangeDetails = existingAtom.contentChangeDetails
val details = atom.contentChangeDetails.copy(
revision = atom.contentChangeDetails.revision + 1,
lastModified = Some(changeRecord),
scheduledLaunch = updateIfChanged(scheduledLaunchDate, existingChangeDetails.scheduledLaunch),
embargo = updateIfChanged(embargo, existingChangeDetails.embargo),
expiry = updateIfChanged(expiry, existingChangeDetails.expiry)
)
val newAtom = atom.copy(contentChangeDetails = details)
val thrift: Atom = newAtom.asThrift
val newAtomAsJson = AtomSerializer.toJson(thrift)
log.info(s"Attempting to update atom ${atom.id} in ${awsConfig.dynamoTableName} to new atom: $newAtomAsJson")
previewDataStore.updateAtom(thrift).fold(
{
case err: VersionConflictError =>
log.warn(s"Unable to update atom due to version conflict with id ${atom.id} in ${awsConfig.dynamoTableName} table to new content: $newAtomAsJson", err)
AtomUpdateConflictError(err.msg)
case err =>
log.error(s"Unable to update atom with id ${atom.id} in ${awsConfig.dynamoTableName} table to new content: $newAtomAsJson", err)
AtomUpdateFailed(err.msg)
},
_ => {
val event = ContentAtomEvent(thrift, EventType.Update, new Date().getTime)
previewPublisher.publishAtomEvent(event) match {
case Success(_) => {
val existingMediaAtom = MediaAtom.fromThrift(existingAtom)
val updatedMediaAtom = MediaAtom.fromThrift(thrift)
processPlutoData(existingMediaAtom, updatedMediaAtom)
AuditMessage(atom.id, "Update", getUsername(user), Some(diffString)).logMessage()
log.info(s"atom with id ${atom.id} updated successfully in ${awsConfig.dynamoTableName} table to new content: $newAtomAsJson")
updatedMediaAtom
}
case Failure(err) =>
log.error(s"Unable to publish updated atom id=${atom.id} new_content=$newAtom", err)
AtomPublishFailed(s"could not publish: ${err.toString}")
}
}
)
}
private def processPlutoData(oldAtom: MediaAtom, newAtom: MediaAtom) = {
(oldAtom.plutoData.flatMap(_.projectId), newAtom.plutoData.flatMap(_.projectId)) match {
case (Some(oldProject), Some(newProject)) if oldProject != newProject => notifyPluto(newAtom)
case (None, Some(_)) => notifyPluto(newAtom)
case (_, _) => None
}
}
private def notifyPluto(newAtom: MediaAtom) = {
val plutoActions = new PlutoUploadActions(awsConfig)
val message = AtomAssignedProjectMessage.build(newAtom)
plutoActions.sendToPluto(message)
}
}
object UpdateAtomCommand {
private val interestingFields = Seq[(String, MediaAtom => Option[Any])](
("title", a => Some(a.title)),
("category", a => Some(a.category)),
("description", _.description),
("duration", _.duration),
("source", _.source),
("youtubeCategoryId", _.youtubeCategoryId),
("license", _.license),
("commentsEnabled", _.composerCommentsEnabled),
("channelId", _.channelId),
("legallySensitive", _.legallySensitive)
)
// We don't use HTTP patch so diffing has to be done manually
def createDiffString(before: MediaAtom, after: MediaAtom): String = {
val changedFields = for {
(name, extractor) <- interestingFields
distinctValues = Seq(extractor(before), extractor(after)).distinct
if distinctValues.size > 1
} yield s"$name: ${distinctValues.map(_.getOrElse("[NONE]")).mkString(" -> ")}"
if (changedFields.isEmpty) { // There's a change, but in some field we're not interested in (or rather, unable to format nicely)
"Updated atom fields"
} else s"Updated atom fields (${changedFields.mkString(", ")})"
}
}