user blog & picfit WIP

pull/9705/head
Thibault Duplessis 2021-08-30 12:32:06 +02:00
parent 2b497c8699
commit 3fe1736fa5
11 changed files with 149 additions and 5 deletions

View File

@ -79,4 +79,46 @@ final class Ublog(env: Env) extends LilaController(env) {
}
}
}
// private val handleFilePartAsFile: play.core.parsers.Multipart.FilePartHandler[java.io.File] = {
// case Multipart.FileInfo(partName, filename, contentType, dispositionType) =>
// val perms = java.util.EnumSet.of(OWNER_READ, OWNER_WRITE)
// val attr = PosixFilePermissions.asFileAttribute(perms)
// val path = JFiles.createTempFile("multipartBody", "tempFile", attr)
// val file = path.toFile
// val fileSink = FileIO.toPath(path)
// val accumulator = Accumulator(fileSink)
// accumulator.map { case IOResult(count, status) =>
// FilePart(partName, filename, contentType, file, count, dispositionType)
// }(ec)
// }
import play.api.mvc.BodyParser
import akka.stream.scaladsl.Source
import akka.util.ByteString
import play.api.libs.streams.Accumulator
def verbatimBodyParser: BodyParser[Source[ByteString, _]] = BodyParser { _ =>
// Return the source directly. We need to return
// an Accumulator[Either[Result, T]], so if we were
// handling any errors we could map to something like
// a Left(BadRequest("error")). Since we're not
// we just wrap the source in a Right(...)
Accumulator.source[ByteString].map(Right.apply)
}
def image(unusedUsername: String, id: String) =
AuthBody(parse.multipartFormData(handleFilePartAsFile)) { implicit ctx => me =>
env.ublog.api.findByAuthor(UblogPost.Id(id), me) flatMap {
_ ?? { post =>
ctx.body.body.file("image") match {
case FilePart(key, filename, contentType, file, fileSize, dispositionType) =>
case Some(image) =>
env.ublog.api.uploadImage(post, image) recover { case e: Exception =>
BadRequest(html.ublog.form.edit(me, post, env.ublog.form.edit(post)))
.flashFailure(e.getMessage)
} inject Redirect(routes.Streamer.edit)
case None => fuccess(Redirect(routes.Streamer.edit))
}
}
}
}
}

View File

@ -152,7 +152,7 @@ lazy val db = smallModule("db",
lazy val memo = smallModule("memo",
Seq(common, db),
Seq(scaffeine, autoconfig, scalatest, akka.testkit) ++ reactivemongo.bundle ++ macwire.bundle
Seq(scaffeine, autoconfig, scalatest, akka.testkit) ++ reactivemongo.bundle ++ macwire.bundle ++ playWs.bundle
)
lazy val search = smallModule("search",

View File

@ -385,6 +385,10 @@ memo {
cache = cache
config = flag
}
picfit {
collection = picfit_image
endpoint = "127.0.0.1:3001"
}
}
redis {
uri = "redis://127.0.0.1"

View File

@ -64,6 +64,7 @@ GET /@/:username/blog/new controllers.Ublog.form(username: String)
POST /@/:username/blog/new controllers.Ublog.create(username: String)
GET /@/:username/blog/$id<\w{8}>/edit controllers.Ublog.edit(username: String, id: String)
POST /@/:username/blog/$id<\w{8}>/edit controllers.Ublog.update(username: String, id: String)
POST /@/:username/blog/$id<\w{8}>/image controllers.Ublog.image(username: String, id: String)
GET /@/:username/blog/:slug/:id controllers.Ublog.post(username: String, slug: String, id: String)
# User

View File

@ -8,14 +8,17 @@ import lila.common.config._
final class MemoConfig(
@ConfigName("collection.cache") val cacheColl: CollName,
@ConfigName("collection.config") val configColl: CollName
@ConfigName("collection.config") val configColl: CollName,
@ConfigName("picfit.collection") val picfitColl: CollName,
@ConfigName("picfit.endpoint") val picfitEndpoint: String
)
@Module
final class Env(
appConfig: Configuration,
mode: play.api.Mode,
db: lila.db.Db
db: lila.db.Db,
ws: play.api.libs.ws.StandaloneWSClient
)(implicit ec: scala.concurrent.ExecutionContext, system: akka.actor.ActorSystem) {
private val config = appConfig.get[MemoConfig]("memo")(AutoConfig.loader)
@ -29,4 +32,6 @@ final class Env(
lazy val mongoCacheApi = wire[MongoCache.Api]
lazy val mongoRateLimitApi = wire[MongoRateLimitApi]
lazy val picfitApi = new PicfitApi(db(config.picfitColl), ws, endpoint = config.picfitEndpoint)
}

View File

@ -0,0 +1,80 @@
package lila.memo
import java.nio.file.Path
import org.joda.time.DateTime
import reactivemongo.api.bson.Macros
import scala.concurrent.ExecutionContext
import play.api.libs.ws.StandaloneWSClient
import lila.db.dsl._
case class PicfitImage(
_id: PicfitImage.Id,
user: String,
scope: String, // like blog, streamer, coach, ...
name: String,
contentType: Option[String],
size: Int, // in bytes
createdAt: DateTime
) {
def id = _id
}
object PicfitImage {
case class Id(value: String) extends AnyVal with StringValue
implicit val imageIdBSONHandler = stringAnyValHandler[PicfitImage.Id](_.value, PicfitImage.Id.apply)
implicit val imageBSONHandler = Macros.handler[PicfitImage]
}
final class PicfitApi(coll: Coll, ws: StandaloneWSClient, endpoint: String)(implicit ec: ExecutionContext) {
import PicfitApi._
private val uploadMaxBytes = uploadMaxMb * 1024 * 1024
def upload(scope: String, uploaded: Uploaded, userId: String): Fu[PicfitImage] =
if (uploaded.fileSize > uploadMaxBytes)
fufail(s"File size must not exceed ${uploadMaxMb}MB.")
else {
val image = PicfitImage(
_id = PicfitImage.Id(lila.common.ThreadLocalRandom nextString 8),
user = userId,
scope = scope,
name = sanitizeName(uploaded.filename),
contentType = uploaded.contentType,
size = uploaded.fileSize.toInt,
createdAt = DateTime.now
)
// ws.url(s"$endpoint/upload")
// .post(
// Source(
// FilePart(
// "File",
// fileName,
// Option("application/pdf"),
// FileIO.fromPath(Paths.get(pathToFile))
// ) :: List()
// )
// )
// .post(body(source)) flatMap {
// case res if res.status != 200 =>
// fufail(res.statusText)
// case res => funit
// } >>
coll.insert.one(image) inject image
}
private def sanitizeName(name: String) = {
// the char `^` breaks play, even URL encoded
java.net.URLEncoder.encode(name, "UTF-8").replaceIf('%', "")
}
}
object PicfitApi {
val uploadMaxMb = 4
type Uploaded = play.api.mvc.MultipartFormData.FilePart[play.api.libs.Files.TemporaryFile]
}

View File

@ -7,10 +7,12 @@ import lila.common.config._
@Module
final class Env(
db: lila.db.Db,
userRepo: lila.user.UserRepo
userRepo: lila.user.UserRepo,
picfit: lila.memo.PicfitApi
)(implicit
ec: scala.concurrent.ExecutionContext
) {
private val postColl = db(CollName("ublog_post"))
val api = wire[UblogApi]

View File

@ -7,9 +7,10 @@ import lila.common.config.MaxPerPage
import lila.common.paginator.Paginator
import lila.db.dsl._
import lila.db.paginator.Adapter
import lila.memo.PicfitApi
import lila.user.User
final class UblogApi(coll: Coll)(implicit ec: ExecutionContext) {
final class UblogApi(coll: Coll, picfitApi: PicfitApi)(implicit ec: ExecutionContext) {
import UblogBsonHandlers._
@ -34,6 +35,11 @@ final class UblogApi(coll: Coll)(implicit ec: ExecutionContext) {
def draftByUser(user: User, page: Int): Fu[Paginator[UblogPost]] =
paginatorByUser(user, false, page)
def uploadImage(post: UblogPost, picture: PicfitApi.Uploaded) =
picfitApi.upload("ublog", picture, userId = post.user).flatMap { image =>
coll.update.one($id(post.id), $set("image" -> image.id)).void
}
private def paginatorByUser(user: User, live: Boolean, page: Int): Fu[Paginator[UblogPost]] =
Paginator(
adapter = new Adapter[UblogPost](

View File

@ -5,6 +5,7 @@ import reactivemongo.api.bson._
private[ublog] object UblogBsonHandlers {
import lila.memo.PicfitImage.imageIdBSONHandler
implicit val postIdBSONHandler = stringAnyValHandler[UblogPost.Id](_.value, UblogPost.Id.apply)
implicit val postBSONHandler = Macros.handler[UblogPost]
}

View File

@ -38,6 +38,7 @@ object UblogForm {
title = title,
intro = intro,
markdown = markdown,
image = none,
live = false,
createdAt = now,
updatedAt = now,

View File

@ -2,6 +2,7 @@ package lila.ublog
import org.joda.time.DateTime
import lila.memo.PicfitImage
import lila.user.User
case class UblogPost(
@ -10,6 +11,7 @@ case class UblogPost(
title: String,
intro: String,
markdown: String,
image: Option[PicfitImage.Id],
live: Boolean,
createdAt: DateTime,
updatedAt: DateTime,