ublog post like timeline notification
parent
8a076b0867
commit
d6be3c2583
|
@ -163,6 +163,14 @@ final class Ublog(env: Env) extends LilaController(env) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def redirect(id: String) = Open { implicit ctx =>
|
||||||
|
env.ublog.api.postPreview(UblogPost.Id(id)) flatMap {
|
||||||
|
_.fold(notFound) { post =>
|
||||||
|
Redirect(urlOfPost(post)).fuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def setTier(blogId: String) = SecureBody(_.ModerateBlog) { implicit ctx => me =>
|
def setTier(blogId: String) = SecureBody(_.ModerateBlog) { implicit ctx => me =>
|
||||||
UblogBlog.Id(blogId).??(env.ublog.api.getBlog) flatMap {
|
UblogBlog.Id(blogId).??(env.ublog.api.getBlog) flatMap {
|
||||||
_ ?? { blog =>
|
_ ?? { blog =>
|
||||||
|
|
|
@ -123,6 +123,11 @@ object timeline {
|
||||||
)
|
)
|
||||||
case BlogPost(id, slug, title) =>
|
case BlogPost(id, slug, title) =>
|
||||||
a(cls := "text", dataIcon := "", href := routes.Blog.show(id, slug))(title)
|
a(cls := "text", dataIcon := "", href := routes.Blog.show(id, slug))(title)
|
||||||
|
case UblogPostLike(userId, postId, postTitle) =>
|
||||||
|
trans.xLikesY(
|
||||||
|
userLink(userId),
|
||||||
|
a(href := routes.Ublog.redirect(postId))(postTitle)
|
||||||
|
)
|
||||||
case StreamStart(id, name) =>
|
case StreamStart(id, name) =>
|
||||||
views.html.streamer.bits
|
views.html.streamer.bits
|
||||||
.redirectLink(id)(cls := "text", dataIcon := "")(trans.xStartedStreaming(name))
|
.redirectLink(id)(cls := "text", dataIcon := "")(trans.xStartedStreaming(name))
|
||||||
|
|
|
@ -64,6 +64,7 @@ GET /@/:username/blog/drafts controllers.Ublog.drafts(username: String
|
||||||
GET /@/:username/blog/new controllers.Ublog.form(username: String)
|
GET /@/:username/blog/new controllers.Ublog.form(username: String)
|
||||||
GET /@/:username/blog.atom controllers.Ublog.userAtom(username: String)
|
GET /@/:username/blog.atom controllers.Ublog.userAtom(username: String)
|
||||||
POST /ublog/new controllers.Ublog.create
|
POST /ublog/new controllers.Ublog.create
|
||||||
|
GET /ublog/$id<\w{8}>/redirect controllers.Ublog.redirect(id: String)
|
||||||
GET /ublog/$id<\w{8}>/edit controllers.Ublog.edit(id: String)
|
GET /ublog/$id<\w{8}>/edit controllers.Ublog.edit(id: String)
|
||||||
POST /ublog/$id<\w{8}>/edit controllers.Ublog.update(id: String)
|
POST /ublog/$id<\w{8}>/edit controllers.Ublog.update(id: String)
|
||||||
POST /ublog/$id<\w{8}>/del controllers.Ublog.delete(id: String)
|
POST /ublog/$id<\w{8}>/del controllers.Ublog.delete(id: String)
|
||||||
|
|
|
@ -176,6 +176,9 @@ package timeline {
|
||||||
case class BlogPost(id: String, slug: String, title: String) extends Atom("blogPost", true) {
|
case class BlogPost(id: String, slug: String, title: String) extends Atom("blogPost", true) {
|
||||||
def userIds = Nil
|
def userIds = Nil
|
||||||
}
|
}
|
||||||
|
case class UblogPostLike(userId: String, id: String, title: String) extends Atom("ublogPostLike", true) {
|
||||||
|
def userIds = List(userId)
|
||||||
|
}
|
||||||
case class StreamStart(id: String, name: String) extends Atom("streamStart", true) {
|
case class StreamStart(id: String, name: String) extends Atom("streamStart", true) {
|
||||||
def userIds = List(id)
|
def userIds = List(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,37 +18,10 @@ case class Entry(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
import Entry._
|
import Entry._
|
||||||
import atomBsonHandlers._
|
|
||||||
|
|
||||||
def similarTo(other: Entry) = typ == other.typ && data == other.data
|
def similarTo(other: Entry) = typ == other.typ && data == other.data
|
||||||
|
|
||||||
case object Deprecated extends lila.base.LilaException {
|
lazy val decode: Option[Atom] = atomBsonHandlers.handlers.get(typ).flatMap(_ readOpt data)
|
||||||
val message = "Deprecated timeline entry"
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val decode: Option[Atom] = Try(typ match {
|
|
||||||
case "follow" => followHandler.readTry(data).get
|
|
||||||
case "team-join" => teamJoinHandler.readTry(data).get
|
|
||||||
case "team-create" => teamCreateHandler.readTry(data).get
|
|
||||||
case "forum-post" => forumPostHandler.readTry(data).get
|
|
||||||
case "ublog-post" => ublogPostHandler.readTry(data).get
|
|
||||||
case "tour-join" => tourJoinHandler.readTry(data).get
|
|
||||||
case "game-end" => gameEndHandler.readTry(data).get
|
|
||||||
case "simul-create" => simulCreateHandler.readTry(data).get
|
|
||||||
case "simul-join" => simulJoinHandler.readTry(data).get
|
|
||||||
case "study-like" => studyLikeHandler.readTry(data).get
|
|
||||||
case "plan-start" => planStartHandler.readTry(data).get
|
|
||||||
case "plan-renew" => planRenewHandler.readTry(data).get
|
|
||||||
case "blog-post" => blogPostHandler.readTry(data).get
|
|
||||||
case "stream-start" => streamStartHandler.readTry(data).get
|
|
||||||
case _ => sys error s"Unhandled atom type: $typ"
|
|
||||||
}) match {
|
|
||||||
case Success(atom) => Some(atom)
|
|
||||||
case Failure(Deprecated) => none
|
|
||||||
case Failure(err) =>
|
|
||||||
lila.log("timeline").warn(err.getMessage)
|
|
||||||
none
|
|
||||||
}
|
|
||||||
|
|
||||||
def userIds = decode.??(_.userIds)
|
def userIds = decode.??(_.userIds)
|
||||||
|
|
||||||
|
@ -64,20 +37,21 @@ object Entry {
|
||||||
private[timeline] def make(data: Atom): Entry = {
|
private[timeline] def make(data: Atom): Entry = {
|
||||||
import atomBsonHandlers._
|
import atomBsonHandlers._
|
||||||
data match {
|
data match {
|
||||||
case d: Follow => "follow" -> toBson(d)
|
case d: Follow => "follow" -> toBson(d)
|
||||||
case d: TeamJoin => "team-join" -> toBson(d)
|
case d: TeamJoin => "team-join" -> toBson(d)
|
||||||
case d: TeamCreate => "team-create" -> toBson(d)
|
case d: TeamCreate => "team-create" -> toBson(d)
|
||||||
case d: ForumPost => "forum-post" -> toBson(d)
|
case d: ForumPost => "forum-post" -> toBson(d)
|
||||||
case d: UblogPost => "ublog-post" -> toBson(d)
|
case d: UblogPost => "ublog-post" -> toBson(d)
|
||||||
case d: TourJoin => "tour-join" -> toBson(d)
|
case d: TourJoin => "tour-join" -> toBson(d)
|
||||||
case d: GameEnd => "game-end" -> toBson(d)
|
case d: GameEnd => "game-end" -> toBson(d)
|
||||||
case d: SimulCreate => "simul-create" -> toBson(d)
|
case d: SimulCreate => "simul-create" -> toBson(d)
|
||||||
case d: SimulJoin => "simul-join" -> toBson(d)
|
case d: SimulJoin => "simul-join" -> toBson(d)
|
||||||
case d: StudyLike => "study-like" -> toBson(d)(studyLikeHandler)
|
case d: StudyLike => "study-like" -> toBson(d)
|
||||||
case d: PlanStart => "plan-start" -> toBson(d)(planStartHandler)
|
case d: PlanStart => "plan-start" -> toBson(d)
|
||||||
case d: PlanRenew => "plan-renew" -> toBson(d)(planRenewHandler)
|
case d: PlanRenew => "plan-renew" -> toBson(d)
|
||||||
case d: BlogPost => "blog-post" -> toBson(d)(blogPostHandler)
|
case d: BlogPost => "blog-post" -> toBson(d)
|
||||||
case d: StreamStart => "stream-start" -> toBson(d)(streamStartHandler)
|
case d: UblogPostLike => "ublog-post-like" -> toBson(d)
|
||||||
|
case d: StreamStart => "stream-start" -> toBson(d)
|
||||||
}
|
}
|
||||||
} match {
|
} match {
|
||||||
case (typ, bson) =>
|
case (typ, bson) =>
|
||||||
|
@ -85,52 +59,73 @@ object Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
object atomBsonHandlers {
|
object atomBsonHandlers {
|
||||||
implicit val followHandler = Macros.handler[Follow]
|
implicit val followHandler = Macros.handler[Follow]
|
||||||
implicit val teamJoinHandler = Macros.handler[TeamJoin]
|
implicit val teamJoinHandler = Macros.handler[TeamJoin]
|
||||||
implicit val teamCreateHandler = Macros.handler[TeamCreate]
|
implicit val teamCreateHandler = Macros.handler[TeamCreate]
|
||||||
implicit val forumPostHandler = Macros.handler[ForumPost]
|
implicit val forumPostHandler = Macros.handler[ForumPost]
|
||||||
implicit val ublogPostHandler = Macros.handler[UblogPost]
|
implicit val ublogPostHandler = Macros.handler[UblogPost]
|
||||||
implicit val tourJoinHandler = Macros.handler[TourJoin]
|
implicit val tourJoinHandler = Macros.handler[TourJoin]
|
||||||
implicit val gameEndHandler = Macros.handler[GameEnd]
|
implicit val gameEndHandler = Macros.handler[GameEnd]
|
||||||
implicit val simulCreateHandler = Macros.handler[SimulCreate]
|
implicit val simulCreateHandler = Macros.handler[SimulCreate]
|
||||||
implicit val simulJoinHandler = Macros.handler[SimulJoin]
|
implicit val simulJoinHandler = Macros.handler[SimulJoin]
|
||||||
implicit val studyLikeHandler = Macros.handler[StudyLike]
|
implicit val studyLikeHandler = Macros.handler[StudyLike]
|
||||||
implicit val planStartHandler = Macros.handler[PlanStart]
|
implicit val planStartHandler = Macros.handler[PlanStart]
|
||||||
implicit val planRenewHandler = Macros.handler[PlanRenew]
|
implicit val planRenewHandler = Macros.handler[PlanRenew]
|
||||||
implicit val blogPostHandler = Macros.handler[BlogPost]
|
implicit val blogPostHandler = Macros.handler[BlogPost]
|
||||||
implicit val streamStartHandler = Macros.handler[StreamStart]
|
implicit val ublogPostLikeHandler = Macros.handler[UblogPostLike]
|
||||||
|
implicit val streamStartHandler = Macros.handler[StreamStart]
|
||||||
|
|
||||||
|
val handlers = Map(
|
||||||
|
"follow" -> followHandler,
|
||||||
|
"team-join" -> teamJoinHandler,
|
||||||
|
"team-create" -> teamCreateHandler,
|
||||||
|
"forum-post" -> forumPostHandler,
|
||||||
|
"ublog-post" -> ublogPostHandler,
|
||||||
|
"tour-join" -> tourJoinHandler,
|
||||||
|
"game-end" -> gameEndHandler,
|
||||||
|
"simul-create" -> simulCreateHandler,
|
||||||
|
"simul-join" -> simulJoinHandler,
|
||||||
|
"study-like" -> studyLikeHandler,
|
||||||
|
"plan-start" -> planStartHandler,
|
||||||
|
"plan-renew" -> planRenewHandler,
|
||||||
|
"blog-post" -> blogPostHandler,
|
||||||
|
"ublog-post-like" -> ublogPostLikeHandler,
|
||||||
|
"stream-start" -> streamStartHandler
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
object atomJsonWrite {
|
object atomJsonWrite {
|
||||||
implicit val followWrite = Json.writes[Follow]
|
val followWrite = Json.writes[Follow]
|
||||||
implicit val teamJoinWrite = Json.writes[TeamJoin]
|
val teamJoinWrite = Json.writes[TeamJoin]
|
||||||
implicit val teamCreateWrite = Json.writes[TeamCreate]
|
val teamCreateWrite = Json.writes[TeamCreate]
|
||||||
implicit val forumPostWrite = Json.writes[ForumPost]
|
val forumPostWrite = Json.writes[ForumPost]
|
||||||
implicit val ublogPostWrite = Json.writes[UblogPost]
|
val ublogPostWrite = Json.writes[UblogPost]
|
||||||
implicit val tourJoinWrite = Json.writes[TourJoin]
|
val tourJoinWrite = Json.writes[TourJoin]
|
||||||
implicit val gameEndWrite = Json.writes[GameEnd]
|
val gameEndWrite = Json.writes[GameEnd]
|
||||||
implicit val simulCreateWrite = Json.writes[SimulCreate]
|
val simulCreateWrite = Json.writes[SimulCreate]
|
||||||
implicit val simulJoinWrite = Json.writes[SimulJoin]
|
val simulJoinWrite = Json.writes[SimulJoin]
|
||||||
implicit val studyLikeWrite = Json.writes[StudyLike]
|
val studyLikeWrite = Json.writes[StudyLike]
|
||||||
implicit val planStartWrite = Json.writes[PlanStart]
|
val planStartWrite = Json.writes[PlanStart]
|
||||||
implicit val planRenewWrite = Json.writes[PlanRenew]
|
val planRenewWrite = Json.writes[PlanRenew]
|
||||||
implicit val blogPostWrite = Json.writes[BlogPost]
|
val blogPostWrite = Json.writes[BlogPost]
|
||||||
implicit val streamStartWrite = Json.writes[StreamStart]
|
val ublogPostLikeWrite = Json.writes[UblogPostLike]
|
||||||
|
val streamStartWrite = Json.writes[StreamStart]
|
||||||
implicit val atomWrite = Writes[Atom] {
|
implicit val atomWrite = Writes[Atom] {
|
||||||
case d: Follow => followWrite writes d
|
case d: Follow => followWrite writes d
|
||||||
case d: TeamJoin => teamJoinWrite writes d
|
case d: TeamJoin => teamJoinWrite writes d
|
||||||
case d: TeamCreate => teamCreateWrite writes d
|
case d: TeamCreate => teamCreateWrite writes d
|
||||||
case d: ForumPost => forumPostWrite writes d
|
case d: ForumPost => forumPostWrite writes d
|
||||||
case d: UblogPost => ublogPostWrite writes d
|
case d: UblogPost => ublogPostWrite writes d
|
||||||
case d: TourJoin => tourJoinWrite writes d
|
case d: TourJoin => tourJoinWrite writes d
|
||||||
case d: GameEnd => gameEndWrite writes d
|
case d: GameEnd => gameEndWrite writes d
|
||||||
case d: SimulCreate => simulCreateWrite writes d
|
case d: SimulCreate => simulCreateWrite writes d
|
||||||
case d: SimulJoin => simulJoinWrite writes d
|
case d: SimulJoin => simulJoinWrite writes d
|
||||||
case d: StudyLike => studyLikeWrite writes d
|
case d: StudyLike => studyLikeWrite writes d
|
||||||
case d: PlanStart => planStartWrite writes d
|
case d: PlanStart => planStartWrite writes d
|
||||||
case d: PlanRenew => planRenewWrite writes d
|
case d: PlanRenew => planRenewWrite writes d
|
||||||
case d: BlogPost => blogPostWrite writes d
|
case d: BlogPost => blogPostWrite writes d
|
||||||
case d: StreamStart => streamStartWrite writes d
|
case d: UblogPostLike => ublogPostLikeWrite writes d
|
||||||
|
case d: StreamStart => streamStartWrite writes d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,9 @@ final class UblogApi(
|
||||||
.cursor[UblogPost.PreviewPost](ReadPreference.secondaryPreferred)
|
.cursor[UblogPost.PreviewPost](ReadPreference.secondaryPreferred)
|
||||||
.list(nb)
|
.list(nb)
|
||||||
|
|
||||||
|
def postPreview(id: UblogPost.Id) =
|
||||||
|
colls.post.byId[UblogPost.PreviewPost](id.value, previewPostProjection)
|
||||||
|
|
||||||
private def imageRel(post: UblogPost) = s"ublog:${post.id}"
|
private def imageRel(post: UblogPost) = s"ublog:${post.id}"
|
||||||
|
|
||||||
def uploadImage(user: User, post: UblogPost, picture: PicfitApi.FilePart): Fu[UblogPost] =
|
def uploadImage(user: User, post: UblogPost, picture: PicfitApi.FilePart): Fu[UblogPost] =
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package lila.ublog
|
package lila.ublog
|
||||||
|
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import reactivemongo.api._
|
|
||||||
import scala.concurrent.ExecutionContext
|
|
||||||
import lila.db.dsl._
|
|
||||||
import lila.user.User
|
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import play.api.i18n.Lang
|
import play.api.i18n.Lang
|
||||||
|
import reactivemongo.api._
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
final class UblogRank(colls: UblogColls)(implicit ec: ExecutionContext) {
|
import lila.db.dsl._
|
||||||
|
import lila.hub.actorApi.timeline.{ Propagate, UblogPostLike }
|
||||||
|
import lila.user.User
|
||||||
|
|
||||||
|
final class UblogRank(colls: UblogColls, timeline: lila.hub.actors.Timeline)(implicit ec: ExecutionContext) {
|
||||||
|
|
||||||
import UblogBsonHandlers._
|
import UblogBsonHandlers._
|
||||||
|
|
||||||
|
@ -22,16 +24,16 @@ final class UblogRank(colls: UblogColls)(implicit ec: ExecutionContext) {
|
||||||
.aggregateOne() { framework =>
|
.aggregateOne() { framework =>
|
||||||
import framework._
|
import framework._
|
||||||
Match($id(postId)) -> List(
|
Match($id(postId)) -> List(
|
||||||
PipelineOperator($lookup.simple(colls.blog, "blog", "blog", "_id")),
|
PipelineOperator($lookup.simple(from = colls.blog, as = "blog", local = "blog", foreign = "_id")),
|
||||||
UnwindField("blog"),
|
UnwindField("blog"),
|
||||||
Project(
|
Project(
|
||||||
$doc(
|
$doc(
|
||||||
"_id" -> false,
|
|
||||||
"tier" -> "$blog.tier",
|
"tier" -> "$blog.tier",
|
||||||
"likes" -> $doc("$size" -> "$likers"),
|
"likes" -> $doc("$size" -> "$likers"),
|
||||||
"topics" -> "$topics",
|
"topics" -> "$topics",
|
||||||
|
"at" -> "$lived.at",
|
||||||
"language" -> true,
|
"language" -> true,
|
||||||
"at" -> "$lived.at"
|
"title" -> true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -39,25 +41,31 @@ final class UblogRank(colls: UblogColls)(implicit ec: ExecutionContext) {
|
||||||
.map { docOption =>
|
.map { docOption =>
|
||||||
for {
|
for {
|
||||||
doc <- docOption
|
doc <- docOption
|
||||||
|
id <- doc.getAsOpt[UblogPost.Id]("_id")
|
||||||
likes <- doc.getAsOpt[UblogPost.Likes]("likes")
|
likes <- doc.getAsOpt[UblogPost.Likes]("likes")
|
||||||
topics <- doc.getAsOpt[List[UblogTopic]]("topics")
|
topics <- doc.getAsOpt[List[UblogTopic]]("topics")
|
||||||
liveAt <- doc.getAsOpt[DateTime]("at")
|
liveAt <- doc.getAsOpt[DateTime]("at")
|
||||||
language <- doc.getAsOpt[Lang]("language")
|
|
||||||
tier <- doc int "tier"
|
tier <- doc int "tier"
|
||||||
} yield (topics, likes, liveAt, language, tier)
|
language <- doc.getAsOpt[Lang]("language")
|
||||||
|
title <- doc string "title"
|
||||||
|
} yield (id, topics, likes, liveAt, tier, language, title)
|
||||||
}
|
}
|
||||||
.flatMap {
|
.flatMap {
|
||||||
_.fold(fuccess(UblogPost.Likes(v ?? 1))) { case (topics, prevLikes, liveAt, language, tier) =>
|
_.fold(fuccess(UblogPost.Likes(v ?? 1))) {
|
||||||
val likes = UblogPost.Likes(prevLikes.value + (if (v) 1 else -1))
|
case (id, topics, prevLikes, liveAt, tier, language, title) =>
|
||||||
colls.post.update.one(
|
val likes = UblogPost.Likes(prevLikes.value + (if (v) 1 else -1))
|
||||||
$id(postId),
|
colls.post.update.one(
|
||||||
$set(
|
$id(postId),
|
||||||
"likes" -> likes,
|
$set(
|
||||||
"rank" -> computeRank(topics, likes, liveAt, language, tier)
|
"likes" -> likes,
|
||||||
) ++ {
|
"rank" -> computeRank(topics, likes, liveAt, language, tier)
|
||||||
if (v) $addToSet("likers" -> user.id) else $pull("likers" -> user.id)
|
) ++ {
|
||||||
}
|
if (v) $addToSet("likers" -> user.id) else $pull("likers" -> user.id)
|
||||||
) inject likes
|
}
|
||||||
|
) >>- {
|
||||||
|
if (v && tier >= UblogBlog.Tier.NORMAL)
|
||||||
|
timeline ! (Propagate(UblogPostLike(user.id, id.value, title)) toFollowersOf user.id)
|
||||||
|
} inject likes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue