patron notifications

This commit is contained in:
Thibault Duplessis 2016-07-14 12:58:34 +02:00
parent 11c6339888
commit 572baa4b19
13 changed files with 145 additions and 31 deletions

View file

@ -14,9 +14,9 @@ object Plan extends LilaController {
ctx.me.fold(indexAnon) { me =>
import lila.plan.PlanApi.SyncResult._
Env.plan.api.sync(me) flatMap {
case ReloadUser => Redirect(routes.Plan.index).fuccess
case Synced(Some(patron)) => indexPatron(me, patron)
case Synced(None) => indexFreeUser(me)
case ReloadUser => Redirect(routes.Plan.index).fuccess
case Synced(Some(patron)) if patron.isDefined => indexPatron(me, patron)
case Synced(_) => indexFreeUser(me)
}
}
}

View file

@ -93,20 +93,22 @@ package timeline {
case class ReloadTimeline(user: String)
sealed abstract class Atom(val channel: String, val okForKid: Boolean)
case class Follow(u1: String, u2: String) extends Atom(s"follow", true)
case class TeamJoin(userId: String, teamId: String) extends Atom(s"teamJoin", false)
case class TeamCreate(userId: String, teamId: String) extends Atom(s"teamCreate", false)
case class Follow(u1: String, u2: String) extends Atom("follow", true)
case class TeamJoin(userId: String, teamId: String) extends Atom("teamJoin", false)
case class TeamCreate(userId: String, teamId: String) extends Atom("teamCreate", false)
case class ForumPost(userId: String, topicId: Option[String], topicName: String, postId: String) extends Atom(s"forum:${~topicId}", false)
case class NoteCreate(from: String, to: String) extends Atom(s"note", false)
case class TourJoin(userId: String, tourId: String, tourName: String) extends Atom(s"tournament", true)
case class QaQuestion(userId: String, id: Int, title: String) extends Atom(s"qa", true)
case class QaAnswer(userId: String, id: Int, title: String, answerId: Int) extends Atom(s"qa", true)
case class QaComment(userId: String, id: Int, title: String, commentId: String) extends Atom(s"qa", true)
case class GameEnd(playerId: String, opponent: Option[String], win: Option[Boolean], perf: String) extends Atom(s"gameEnd", true)
case class SimulCreate(userId: String, simulId: String, simulName: String) extends Atom(s"simulCreate", true)
case class SimulJoin(userId: String, simulId: String, simulName: String) extends Atom(s"simulJoin", true)
case class StudyCreate(userId: String, studyId: String, studyName: String) extends Atom(s"studyCreate", true)
case class StudyLike(userId: String, studyId: String, studyName: String) extends Atom(s"studyLike", true)
case class NoteCreate(from: String, to: String) extends Atom("note", false)
case class TourJoin(userId: String, tourId: String, tourName: String) extends Atom("tournament", true)
case class QaQuestion(userId: String, id: Int, title: String) extends Atom("qa", true)
case class QaAnswer(userId: String, id: Int, title: String, answerId: Int) extends Atom("qa", true)
case class QaComment(userId: String, id: Int, title: String, commentId: String) extends Atom("qa", true)
case class GameEnd(playerId: String, opponent: Option[String], win: Option[Boolean], perf: String) extends Atom("gameEnd", true)
case class SimulCreate(userId: String, simulId: String, simulName: String) extends Atom("simulCreate", true)
case class SimulJoin(userId: String, simulId: String, simulName: String) extends Atom("simulJoin", true)
case class StudyCreate(userId: String, studyId: String, studyName: String) extends Atom("studyCreate", true)
case class StudyLike(userId: String, studyId: String, studyName: String) extends Atom("studyLike", true)
// case class PlanStart(userId: String) extends Atom("planStart", true)
// case class PlanExpire(userId: String) extends Atom("planExpire", true)
object propagation {
sealed trait Propagation

View file

@ -51,6 +51,9 @@ private object BSONHandlers {
implicit val GameEndWinHandler = booleanAnyValHandler[GameEnd.Win](_.value, GameEnd.Win.apply)
implicit val GameEndHandler = Macros.handler[GameEnd]
implicit val PlanStartHandler = Macros.handler[PlanStart]
implicit val PlanExpireHandler = Macros.handler[PlanExpire]
implicit val ColorBSONHandler = new BSONHandler[BSONBoolean, chess.Color] {
def read(b: BSONBoolean) = chess.Color(b.value)
def write(c: chess.Color) = BSONBoolean(c.white)
@ -70,6 +73,8 @@ private object BSONHandlers {
case b: NewBlogPost => NewBlogPostHandler.write(b)
case LimitedTournamentInvitation => $empty
case x: GameEnd => GameEndHandler.write(x)
case x: PlanStart => PlanStartHandler.write(x)
case x: PlanExpire => PlanExpireHandler.write(x)
}
} ++ $doc("type" -> notificationContent.key)
@ -100,11 +105,11 @@ private object BSONHandlers {
case "newBlogPost" => NewBlogPostHandler read reader.doc
case "u" => LimitedTournamentInvitation
case "gameEnd" => GameEndHandler read reader.doc
case "planStart" => PlanStartHandler read reader.doc
case "planExpire" => PlanExpireHandler read reader.doc
}
def writes(writer: Writer, n: NotificationContent): dsl.Bdoc = {
writeNotificationContent(n)
}
def writes(writer: Writer, n: NotificationContent): dsl.Bdoc = writeNotificationContent(n)
}
implicit val NotificationBSONHandler = Macros.handler[Notification]

View file

@ -41,6 +41,8 @@ final class JSONHandlers(
"id" -> gameId.value,
"opponent" -> opponentId.map(_.value).flatMap(getLightUser),
"win" -> win.map(_.value))
case _: PlanStart => Json.obj()
case _: PlanExpire => Json.obj()
}
}
@ -57,9 +59,9 @@ final class JSONHandlers(
implicit val newNotificationWrites: Writes[NewNotification] = new Writes[NewNotification] {
def writes(newNotification: NewNotification) = {
Json.obj("notification" -> newNotification.notification, "unread" -> newNotification.unreadNotifications)
}
def writes(newNotification: NewNotification) = Json.obj(
"notification" -> newNotification.notification,
"unread" -> newNotification.unreadNotifications)
}
}

View file

@ -113,3 +113,6 @@ object GameEnd {
case class OpponentId(value: String) extends AnyVal
case class Win(value: Boolean) extends AnyVal
}
case class PlanStart(userId: String) extends NotificationContent("planStart")
case class PlanExpire(userId: String) extends NotificationContent("planExpire")

View file

@ -9,6 +9,8 @@ import lila.common.PimpedConfig._
final class Env(
config: Config,
db: lila.db.Env,
hub: lila.hub.Env,
notifyApi: lila.notify.NotifyApi,
bus: lila.common.Bus,
scheduler: lila.common.Scheduler) {
@ -24,15 +26,20 @@ final class Env(
publicKey = stripePublicKey,
secretKey = config getString "stripe.keys.secret"))
private lazy val notifier = new PlanNotifier(
notifyApi = notifyApi,
timeline = hub.actor.timeline)
lazy val api = new PlanApi(
stripeClient,
patronColl = patronColl,
chargeColl = db(CollectionCharge),
notifier = notifier,
bus)
private lazy val webhookHandler = new WebhookHandler(api)
private lazy val expiration = new Expiration(patronColl)
private lazy val expiration = new Expiration(patronColl, notifier)
scheduler.future(10 seconds, "Expire patron plans") {
expiration.run
@ -46,6 +53,8 @@ object Env {
lazy val current: Env = "plan" boot new Env(
config = lila.common.PlayApp loadConfig "plan",
db = lila.db.Env.current,
hub = lila.hub.Env.current,
notifyApi = lila.notify.Env.current.api,
bus = lila.common.PlayApp.system.lilaBus,
scheduler = lila.common.PlayApp.scheduler)
}

View file

@ -5,7 +5,7 @@ import lila.user.{ User, UserRepo }
import org.joda.time.DateTime
private final class Expiration(patronColl: Coll) {
private final class Expiration(patronColl: Coll, notifier: PlanNotifier) {
import BsonHandlers._
import PatronHandlers._
@ -21,7 +21,8 @@ private final class Expiration(patronColl: Coll) {
private def disableUserPlanOf(patron: Patron): Funit =
UserRepo byId patron.userId flatMap {
_ ?? { user =>
UserRepo.setPlan(user, user.plan.disable)
UserRepo.setPlan(user, user.plan.disable) >>
notifier.onExpire(user)
}
}

View file

@ -32,6 +32,8 @@ case class Patron(
def removePayPal = copy(
payPal = none,
expiresAt = none)
def isDefined = stripe.isDefined || payPal.isDefined
}
object Patron {

View file

@ -9,6 +9,7 @@ final class PlanApi(
stripeClient: StripeClient,
patronColl: Coll,
chargeColl: Coll,
notifier: PlanNotifier,
bus: lila.common.Bus) {
import BsonHandlers._
@ -90,7 +91,8 @@ final class PlanApi(
payPal = Patron.PayPal(email, subId, DateTime.now).some,
lastLevelUp = DateTime.now
).expireInOneMonth) >>
UserRepo.setPlan(user, lila.user.Plan.start)
UserRepo.setPlan(user, lila.user.Plan.start) >>
notifier.onStart(user)
case Some(patron) if patron.canLevelUp =>
patronColl.update($id(patron.id), patron.levelUpNow.expireInOneMonth) >>
UserRepo.setPlan(user, user.plan.incMonths)
@ -206,7 +208,8 @@ final class PlanApi(
private def createCustomer(user: User, data: Checkout, plan: StripePlan): Fu[StripeCustomer] =
stripeClient.createCustomer(user, data, plan) flatMap { customer =>
saveStripePatron(user, customer.id, data.isMonthly) >>
UserRepo.setPlan(user, lila.user.Plan.start) >>-
UserRepo.setPlan(user, lila.user.Plan.start) >>
notifier.onStart(user) >>-
logger.info(s"Create ${user.username} customer $customer") inject customer
}

View file

@ -0,0 +1,60 @@
package lila.plan
import lila.hub.actorApi.timeline.{ Propagate }
import lila.notify.Notification.Notifies
import lila.notify.{ Notification, NotifyApi }
import lila.user.User
import akka.actor.ActorSelection
private[plan] final class PlanNotifier(
notifyApi: NotifyApi,
timeline: ActorSelection) {
def onStart(user: User) =
notifyApi.addNotification(Notification(
Notifies(user.id),
lila.notify.PlanStart(user.id)
))
def onExpire(user: User) =
notifyApi.addNotification(Notification(
Notifies(user.id),
lila.notify.PlanExpire(user.id)
))
// private[qa] def createQuestion(q: Question, u: User) {
// val msg = Propagate(QaQuestion(u.id, q.id, q.title))
// timeline ! (msg toFollowersOf u.id)
// }
// private[qa] def createAnswer(q: Question, a: Answer, u: User) {
// val msg = Propagate(QaAnswer(u.id, q.id, q.title, a.id))
// timeline ! (msg toFollowersOf u.id toUser q.userId exceptUser u.id)
// if (u.id != q.userId) notifyAsker(q, a)
// }
// private[qa] def notifyAsker(q: Question, a: Answer) = {
// import lila.notify.QaAnswer
// import lila.common.String.shorten
// val answererId = QaAnswer.AnswererId(a.userId)
// val question = QaAnswer.Question(id = q.id, slug = q.slug, title = shorten(q.title, 80))
// val answerId = QaAnswer.AnswerId(a.id)
// val notificationContent = QaAnswer(answererId, question, answerId)
// val notification = Notification(Notifies(q.userId), notificationContent)
// notifyApi.addNotification(notification)
// }
// private[qa] def createQuestionComment(q: Question, c: Comment, u: User) {
// val msg = Propagate(QaComment(u.id, q.id, q.title, c.id))
// timeline ! (msg toFollowersOf u.id toUser q.userId exceptUser u.id)
// }
// private[qa] def createAnswerComment(q: Question, a: Answer, c: Comment, u: User) {
// val msg = Propagate(QaComment(u.id, q.id, q.title, c.id))
// timeline ! (msg toFollowersOf u.id toUser a.userId exceptUser u.id)
// }
}

View file

@ -3,14 +3,13 @@ package lila.qa
import com.typesafe.config.Config
import lila.common.DetectLanguage
import lila.common.PimpedConfig._
import lila.notify.NotifyApi
final class Env(
config: Config,
hub: lila.hub.Env,
detectLanguage: DetectLanguage,
mongoCache: lila.memo.MongoCache.Builder,
notifyApi : NotifyApi,
notifyApi: lila.notify.NotifyApi,
db: lila.db.Env) {
private val CollectionQuestion = config getString "collection.question"

View file

@ -258,7 +258,7 @@ object ApplicationBuild extends Build {
libraryDependencies ++= provided(play.api, RM)
)
lazy val plan = project("plan", Seq(common, user)).settings(
lazy val plan = project("plan", Seq(common, user, notifyModule)).settings(
libraryDependencies ++= provided(play.api, RM)
)

View file

@ -184,7 +184,35 @@ var handlers = {
}
return result + ' vs ' + userFullName(n.content.opponent);
}
}
},
planStart: {
html: function(notification) {
return genericNotification(notification, '/patron', '', [
m('span', [
m('strong', 'Thank you!'),
drawTime(notification)
]),
m('span', 'You just became a lichess patron.')
]);
},
text: function(n) {
return 'You just became a lichess patron.';
}
},
planExpire: {
html: function(notification) {
return genericNotification(notification, '/patron', '', [
m('span', [
m('strong', 'Patron account expired'),
drawTime(notification)
]),
m('span', 'Please consider renewing it!')
]);
},
text: function(n) {
return 'Patron account expired';
}
},
};
function drawNotification(notification) {