ublog post like timeline notification

pull/9799/head
Thibault Duplessis 2021-09-13 21:27:19 +02:00
parent 8a076b0867
commit d6be3c2583
7 changed files with 128 additions and 105 deletions

View File

@ -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 =>
UblogBlog.Id(blogId).??(env.ublog.api.getBlog) flatMap {
_ ?? { blog =>

View File

@ -123,6 +123,11 @@ object timeline {
)
case BlogPost(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) =>
views.html.streamer.bits
.redirectLink(id)(cls := "text", dataIcon := "")(trans.xStartedStreaming(name))

View File

@ -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.atom controllers.Ublog.userAtom(username: String)
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)
POST /ublog/$id<\w{8}>/edit controllers.Ublog.update(id: String)
POST /ublog/$id<\w{8}>/del controllers.Ublog.delete(id: String)

View File

@ -176,6 +176,9 @@ package timeline {
case class BlogPost(id: String, slug: String, title: String) extends Atom("blogPost", true) {
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) {
def userIds = List(id)
}

View File

@ -18,37 +18,10 @@ case class Entry(
) {
import Entry._
import atomBsonHandlers._
def similarTo(other: Entry) = typ == other.typ && data == other.data
case object Deprecated extends lila.base.LilaException {
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
}
lazy val decode: Option[Atom] = atomBsonHandlers.handlers.get(typ).flatMap(_ readOpt data)
def userIds = decode.??(_.userIds)
@ -64,20 +37,21 @@ object Entry {
private[timeline] def make(data: Atom): Entry = {
import atomBsonHandlers._
data match {
case d: Follow => "follow" -> toBson(d)
case d: TeamJoin => "team-join" -> toBson(d)
case d: TeamCreate => "team-create" -> toBson(d)
case d: ForumPost => "forum-post" -> toBson(d)
case d: UblogPost => "ublog-post" -> toBson(d)
case d: TourJoin => "tour-join" -> toBson(d)
case d: GameEnd => "game-end" -> toBson(d)
case d: SimulCreate => "simul-create" -> toBson(d)
case d: SimulJoin => "simul-join" -> toBson(d)
case d: StudyLike => "study-like" -> toBson(d)(studyLikeHandler)
case d: PlanStart => "plan-start" -> toBson(d)(planStartHandler)
case d: PlanRenew => "plan-renew" -> toBson(d)(planRenewHandler)
case d: BlogPost => "blog-post" -> toBson(d)(blogPostHandler)
case d: StreamStart => "stream-start" -> toBson(d)(streamStartHandler)
case d: Follow => "follow" -> toBson(d)
case d: TeamJoin => "team-join" -> toBson(d)
case d: TeamCreate => "team-create" -> toBson(d)
case d: ForumPost => "forum-post" -> toBson(d)
case d: UblogPost => "ublog-post" -> toBson(d)
case d: TourJoin => "tour-join" -> toBson(d)
case d: GameEnd => "game-end" -> toBson(d)
case d: SimulCreate => "simul-create" -> toBson(d)
case d: SimulJoin => "simul-join" -> toBson(d)
case d: StudyLike => "study-like" -> toBson(d)
case d: PlanStart => "plan-start" -> toBson(d)
case d: PlanRenew => "plan-renew" -> toBson(d)
case d: BlogPost => "blog-post" -> toBson(d)
case d: UblogPostLike => "ublog-post-like" -> toBson(d)
case d: StreamStart => "stream-start" -> toBson(d)
}
} match {
case (typ, bson) =>
@ -85,52 +59,73 @@ object Entry {
}
object atomBsonHandlers {
implicit val followHandler = Macros.handler[Follow]
implicit val teamJoinHandler = Macros.handler[TeamJoin]
implicit val teamCreateHandler = Macros.handler[TeamCreate]
implicit val forumPostHandler = Macros.handler[ForumPost]
implicit val ublogPostHandler = Macros.handler[UblogPost]
implicit val tourJoinHandler = Macros.handler[TourJoin]
implicit val gameEndHandler = Macros.handler[GameEnd]
implicit val simulCreateHandler = Macros.handler[SimulCreate]
implicit val simulJoinHandler = Macros.handler[SimulJoin]
implicit val studyLikeHandler = Macros.handler[StudyLike]
implicit val planStartHandler = Macros.handler[PlanStart]
implicit val planRenewHandler = Macros.handler[PlanRenew]
implicit val blogPostHandler = Macros.handler[BlogPost]
implicit val streamStartHandler = Macros.handler[StreamStart]
implicit val followHandler = Macros.handler[Follow]
implicit val teamJoinHandler = Macros.handler[TeamJoin]
implicit val teamCreateHandler = Macros.handler[TeamCreate]
implicit val forumPostHandler = Macros.handler[ForumPost]
implicit val ublogPostHandler = Macros.handler[UblogPost]
implicit val tourJoinHandler = Macros.handler[TourJoin]
implicit val gameEndHandler = Macros.handler[GameEnd]
implicit val simulCreateHandler = Macros.handler[SimulCreate]
implicit val simulJoinHandler = Macros.handler[SimulJoin]
implicit val studyLikeHandler = Macros.handler[StudyLike]
implicit val planStartHandler = Macros.handler[PlanStart]
implicit val planRenewHandler = Macros.handler[PlanRenew]
implicit val blogPostHandler = Macros.handler[BlogPost]
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 {
implicit val followWrite = Json.writes[Follow]
implicit val teamJoinWrite = Json.writes[TeamJoin]
implicit val teamCreateWrite = Json.writes[TeamCreate]
implicit val forumPostWrite = Json.writes[ForumPost]
implicit val ublogPostWrite = Json.writes[UblogPost]
implicit val tourJoinWrite = Json.writes[TourJoin]
implicit val gameEndWrite = Json.writes[GameEnd]
implicit val simulCreateWrite = Json.writes[SimulCreate]
implicit val simulJoinWrite = Json.writes[SimulJoin]
implicit val studyLikeWrite = Json.writes[StudyLike]
implicit val planStartWrite = Json.writes[PlanStart]
implicit val planRenewWrite = Json.writes[PlanRenew]
implicit val blogPostWrite = Json.writes[BlogPost]
implicit val streamStartWrite = Json.writes[StreamStart]
val followWrite = Json.writes[Follow]
val teamJoinWrite = Json.writes[TeamJoin]
val teamCreateWrite = Json.writes[TeamCreate]
val forumPostWrite = Json.writes[ForumPost]
val ublogPostWrite = Json.writes[UblogPost]
val tourJoinWrite = Json.writes[TourJoin]
val gameEndWrite = Json.writes[GameEnd]
val simulCreateWrite = Json.writes[SimulCreate]
val simulJoinWrite = Json.writes[SimulJoin]
val studyLikeWrite = Json.writes[StudyLike]
val planStartWrite = Json.writes[PlanStart]
val planRenewWrite = Json.writes[PlanRenew]
val blogPostWrite = Json.writes[BlogPost]
val ublogPostLikeWrite = Json.writes[UblogPostLike]
val streamStartWrite = Json.writes[StreamStart]
implicit val atomWrite = Writes[Atom] {
case d: Follow => followWrite writes d
case d: TeamJoin => teamJoinWrite writes d
case d: TeamCreate => teamCreateWrite writes d
case d: ForumPost => forumPostWrite writes d
case d: UblogPost => ublogPostWrite writes d
case d: TourJoin => tourJoinWrite writes d
case d: GameEnd => gameEndWrite writes d
case d: SimulCreate => simulCreateWrite writes d
case d: SimulJoin => simulJoinWrite writes d
case d: StudyLike => studyLikeWrite writes d
case d: PlanStart => planStartWrite writes d
case d: PlanRenew => planRenewWrite writes d
case d: BlogPost => blogPostWrite writes d
case d: StreamStart => streamStartWrite writes d
case d: Follow => followWrite writes d
case d: TeamJoin => teamJoinWrite writes d
case d: TeamCreate => teamCreateWrite writes d
case d: ForumPost => forumPostWrite writes d
case d: UblogPost => ublogPostWrite writes d
case d: TourJoin => tourJoinWrite writes d
case d: GameEnd => gameEndWrite writes d
case d: SimulCreate => simulCreateWrite writes d
case d: SimulJoin => simulJoinWrite writes d
case d: StudyLike => studyLikeWrite writes d
case d: PlanStart => planStartWrite writes d
case d: PlanRenew => planRenewWrite writes d
case d: BlogPost => blogPostWrite writes d
case d: UblogPostLike => ublogPostLikeWrite writes d
case d: StreamStart => streamStartWrite writes d
}
}

View File

@ -103,6 +103,9 @@ final class UblogApi(
.cursor[UblogPost.PreviewPost](ReadPreference.secondaryPreferred)
.list(nb)
def postPreview(id: UblogPost.Id) =
colls.post.byId[UblogPost.PreviewPost](id.value, previewPostProjection)
private def imageRel(post: UblogPost) = s"ublog:${post.id}"
def uploadImage(user: User, post: UblogPost, picture: PicfitApi.FilePart): Fu[UblogPost] =

View File

@ -1,14 +1,16 @@
package lila.ublog
import cats.implicits._
import reactivemongo.api._
import scala.concurrent.ExecutionContext
import lila.db.dsl._
import lila.user.User
import org.joda.time.DateTime
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._
@ -22,16 +24,16 @@ final class UblogRank(colls: UblogColls)(implicit ec: ExecutionContext) {
.aggregateOne() { framework =>
import framework._
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"),
Project(
$doc(
"_id" -> false,
"tier" -> "$blog.tier",
"likes" -> $doc("$size" -> "$likers"),
"topics" -> "$topics",
"at" -> "$lived.at",
"language" -> true,
"at" -> "$lived.at"
"title" -> true
)
)
)
@ -39,25 +41,31 @@ final class UblogRank(colls: UblogColls)(implicit ec: ExecutionContext) {
.map { docOption =>
for {
doc <- docOption
id <- doc.getAsOpt[UblogPost.Id]("_id")
likes <- doc.getAsOpt[UblogPost.Likes]("likes")
topics <- doc.getAsOpt[List[UblogTopic]]("topics")
liveAt <- doc.getAsOpt[DateTime]("at")
language <- doc.getAsOpt[Lang]("language")
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 {
_.fold(fuccess(UblogPost.Likes(v ?? 1))) { case (topics, prevLikes, liveAt, language, tier) =>
val likes = UblogPost.Likes(prevLikes.value + (if (v) 1 else -1))
colls.post.update.one(
$id(postId),
$set(
"likes" -> likes,
"rank" -> computeRank(topics, likes, liveAt, language, tier)
) ++ {
if (v) $addToSet("likers" -> user.id) else $pull("likers" -> user.id)
}
) inject likes
_.fold(fuccess(UblogPost.Likes(v ?? 1))) {
case (id, topics, prevLikes, liveAt, tier, language, title) =>
val likes = UblogPost.Likes(prevLikes.value + (if (v) 1 else -1))
colls.post.update.one(
$id(postId),
$set(
"likes" -> likes,
"rank" -> computeRank(topics, likes, liveAt, language, tier)
) ++ {
if (v) $addToSet("likers" -> user.id) else $pull("likers" -> user.id)
}
) >>- {
if (v && tier >= UblogBlog.Tier.NORMAL)
timeline ! (Propagate(UblogPostLike(user.id, id.value, title)) toFollowersOf user.id)
} inject likes
}
}