membership-attribute-service/app/models/Attributes.scala (133 lines of code) (raw):

package models import com.github.nscala_time.time.OrderingImplicits._ import json._ import models.FeastApp.{getFeastAndroidOfferTags, getFeastIosSubscriptionGroup} import org.joda.time.LocalDate import org.joda.time.LocalDate.now import play.api.libs.functional.syntax._ import play.api.libs.json._ import scalaz.syntax.std.boolean._ import scala.language.implicitConversions case class ContentAccess( member: Boolean, paidMember: Boolean, recurringContributor: Boolean, supporterPlus: Boolean, feast: Boolean, digitalPack: Boolean, paperSubscriber: Boolean, guardianWeeklySubscriber: Boolean, guardianPatron: Boolean, guardianAdLite: Boolean, ) object ContentAccess { implicit val jsWrite = Json.writes[ContentAccess] } case class Attributes( UserId: String, Tier: Option[String] = None, RecurringContributionPaymentPlan: Option[String] = None, OneOffContributionDate: Option[LocalDate] = None, MembershipJoinDate: Option[LocalDate] = None, SupporterPlusExpiryDate: Option[LocalDate] = None, DigitalSubscriptionExpiryDate: Option[LocalDate] = None, PaperSubscriptionExpiryDate: Option[LocalDate] = None, GuardianWeeklySubscriptionExpiryDate: Option[LocalDate] = None, LiveAppSubscriptionExpiryDate: Option[LocalDate] = None, GuardianPatronExpiryDate: Option[LocalDate] = None, AlertAvailableFor: Option[String] = None, RecurringContributionAcquisitionDate: Option[LocalDate] = None, GuardianAdLiteExpiryDate: Option[LocalDate] = None, ) { lazy val isSupporterTier = Tier.exists(_.equalsIgnoreCase("supporter")) lazy val isPartnerTier = Tier.exists(_.equalsIgnoreCase("partner")) lazy val isPatronTier = Tier.exists(_.equalsIgnoreCase("patron")) lazy val isPaidTier: Boolean = isSupporterTier || isPartnerTier || isPatronTier lazy val isRecurringContributor = RecurringContributionPaymentPlan.isDefined lazy val isRecentOneOffContributor = OneOffContributionDate.exists(_.isAfter(now.minusMonths(3))) lazy val isSupporterPlus = SupporterPlusExpiryDate.exists(_.isAfter(now)) lazy val staffDigitalSubscriptionExpiryDate: Option[LocalDate] = Tier.exists(_.equalsIgnoreCase("staff")).option(now.plusDays(1)) lazy val latestDigitalSubscriptionExpiryDate = Some(Set(staffDigitalSubscriptionExpiryDate, DigitalSubscriptionExpiryDate).flatten).filter(_.nonEmpty).map(_.max) lazy val digitalSubscriberHasActivePlan = latestDigitalSubscriptionExpiryDate.exists(_.isAfter(now)) lazy val isPaperSubscriber = PaperSubscriptionExpiryDate.exists(_.isAfter(now)) lazy val isGuardianWeeklySubscriber = GuardianWeeklySubscriptionExpiryDate.exists(_.isAfter(now)) lazy val isPremiumLiveAppSubscriber = LiveAppSubscriptionExpiryDate.exists(_.isAfter(now)) lazy val isGuardianPatron = GuardianPatronExpiryDate.exists(_.isAfter(now)) lazy val isGuardianAdLite = GuardianAdLiteExpiryDate.exists(_.isAfter(now)) lazy val contentAccess = ContentAccess( member = isPaidTier, paidMember = isPaidTier, recurringContributor = isRecurringContributor, supporterPlus = isSupporterPlus, digitalPack = digitalSubscriberHasActivePlan || isPaperSubscriber || isGuardianPatron || isSupporterPlus, paperSubscriber = isPaperSubscriber, guardianWeeklySubscriber = isGuardianWeeklySubscriber, guardianPatron = isGuardianPatron, feast = FeastApp.shouldGetFeastAccess(this), guardianAdLite = isGuardianAdLite, ) // show support messaging (in app & on dotcom) if they do NOT have any active products // TODO in future this could become more sophisticated (e.g. two weeks before their products expire) lazy val showSupportMessaging = !( isPaidTier || isSupporterPlus || isRecurringContributor || isRecentOneOffContributor || digitalSubscriberHasActivePlan || isPaperSubscriber || isGuardianWeeklySubscriber || isPremiumLiveAppSubscriber ) } object Attributes { implicit val jsAttributesWrites: OWrites[Attributes] = ( (__ \ "userId").write[String] and (__ \ "tier").writeNullable[String] and (__ \ "recurringContributionPaymentPlan").writeNullable[String] and (__ \ "oneOffContributionDate").writeNullable[LocalDate] and (__ \ "membershipJoinDate").writeNullable[LocalDate] and JsPath.writeNullable[LocalDate].contramap[Option[LocalDate]](_ => None) and // do not serialize supporterPlusExpiryDate (__ \ "digitalSubscriptionExpiryDate").writeNullable[LocalDate] and (__ \ "paperSubscriptionExpiryDate").writeNullable[LocalDate] and (__ \ "guardianWeeklyExpiryDate").writeNullable[LocalDate] and (__ \ "liveAppSubscriptionExpiryDate").writeNullable[LocalDate] and (__ \ "guardianPatronExpiryDate").writeNullable[LocalDate] and (__ \ "alertAvailableFor").writeNullable[String] and (__ \ "recurringContributionAcquisitionDate").writeNullable[LocalDate] and (__ \ "guardianAdLiteExpiryDate").writeNullable[LocalDate] )(unlift(Attributes.unapply)) .addNullableField("digitalSubscriptionExpiryDate", _.latestDigitalSubscriptionExpiryDate) .addField("showSupportMessaging", _.showSupportMessaging) .addNullableField("feastIosSubscriptionGroup", getFeastIosSubscriptionGroup) .addNullableField("feastAndroidOfferTags", getFeastAndroidOfferTags) .addField("contentAccess", _.contentAccess) } case class MembershipAttributes( UserId: String, Tier: String, AdFree: Option[Boolean] = None, ContentAccess: MembershipContentAccess, ) object MembershipAttributes { implicit val jsWrite: OWrites[MembershipAttributes] = ( (__ \ "userId").write[String] and (__ \ "tier").write[String] and (__ \ "adFree").writeNullable[Boolean] and (__ \ "contentAccess").write[MembershipContentAccess](MembershipContentAccess.jsWrite) )(unlift(MembershipAttributes.unapply)) def fromAttributes(attr: Attributes): Option[MembershipAttributes] = for { tier <- attr.Tier } yield { MembershipAttributes( UserId = attr.UserId, Tier = tier, ContentAccess = MembershipContentAccess( member = attr.contentAccess.member, paidMember = attr.contentAccess.paidMember, ), ) } } case class MembershipContentAccess(member: Boolean, paidMember: Boolean) object MembershipContentAccess { implicit val jsWrite = Json.writes[MembershipContentAccess] }