Forum topic creation
This commit is contained in:
parent
bb346f64f5
commit
1f2073fc48
|
@ -2,7 +2,22 @@ package controllers
|
|||
|
||||
import lila._
|
||||
import views._
|
||||
import security._
|
||||
import http.Context
|
||||
|
||||
object Forum extends LilaController {
|
||||
import play.api.mvc._
|
||||
import play.api.mvc.Results._
|
||||
|
||||
trait Forum {
|
||||
|
||||
def CategGrant[A <: Result](categSlug: String)(a: ⇒ A)(implicit ctx: Context): Result =
|
||||
isGranted(categSlug)(ctx).fold(
|
||||
a,
|
||||
Forbidden("You cannot access to this category")
|
||||
)
|
||||
|
||||
def isGranted(categSlug: String)(ctx: Context) =
|
||||
(categSlug == "staff").fold(
|
||||
ctx.me exists { u ⇒ Granter(Permission.StaffForum)(u) },
|
||||
true)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package controllers
|
|||
import lila._
|
||||
import views._
|
||||
|
||||
object ForumCateg extends LilaController {
|
||||
object ForumCateg extends LilaController with Forum {
|
||||
|
||||
def categApi = env.forum.categApi
|
||||
|
||||
|
@ -12,8 +12,10 @@ object ForumCateg extends LilaController {
|
|||
}
|
||||
|
||||
def show(slug: String, page: Int) = Open { implicit ctx ⇒
|
||||
IOptionOk(categApi.show(slug, page)) {
|
||||
case (categ, topics) ⇒ html.forum.categ.show(categ, topics)
|
||||
CategGrant(slug) {
|
||||
IOptionOk(categApi.show(slug, page)) {
|
||||
case (categ, topics) ⇒ html.forum.categ.show(categ, topics)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,24 +3,26 @@ package controllers
|
|||
import lila._
|
||||
import views._
|
||||
|
||||
object ForumPost extends LilaController {
|
||||
object ForumPost extends LilaController with Forum {
|
||||
|
||||
def topicApi = env.forum.topicApi
|
||||
def postApi = env.forum.postApi
|
||||
def forms = forum.DataForm
|
||||
|
||||
def create(categSlug: String, slug: String, page: Int) = OpenBody { implicit ctx ⇒
|
||||
implicit val req = ctx.body
|
||||
IOptionResult(topicApi.show(categSlug, slug, page)) {
|
||||
case (categ, topic, posts) ⇒ forms.post.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.show(categ, topic, posts, err.some)),
|
||||
data ⇒ (for {
|
||||
post ← postApi.makePost(categ, topic, data, ctx.me)
|
||||
} yield Redirect("%s#%d".format(
|
||||
routes.ForumTopic.show(categ.slug, topic.slug, postApi pageOf post),
|
||||
post.number)
|
||||
)).unsafePerformIO
|
||||
)
|
||||
CategGrant(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
IOptionResult(topicApi.show(categSlug, slug, page)) {
|
||||
case (categ, topic, posts) ⇒ forms.post.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.show(categ, topic, posts, err.some)),
|
||||
data ⇒ (for {
|
||||
post ← postApi.makePost(categ, topic, data, ctx.me)
|
||||
} yield Redirect("%s#%d".format(
|
||||
routes.ForumTopic.show(categ.slug, topic.slug, postApi pageOf post),
|
||||
post.number)
|
||||
)).unsafePerformIO
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,42 @@ package controllers
|
|||
import lila._
|
||||
import views._
|
||||
|
||||
object ForumTopic extends LilaController {
|
||||
object ForumTopic extends LilaController with Forum {
|
||||
|
||||
def categApi = env.forum.topicApi
|
||||
def topicApi = env.forum.topicApi
|
||||
def categRepo = env.forum.categRepo
|
||||
def forms = forum.DataForm
|
||||
|
||||
def create(categ: String) = Open { implicit ctx =>
|
||||
BadRequest
|
||||
def form(categSlug: String) = Open { implicit ctx ⇒
|
||||
CategGrant(categSlug) {
|
||||
IOptionOk(categRepo bySlug categSlug) { categ ⇒
|
||||
html.forum.topic.form(categ, forms.topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def create(categSlug: String) = OpenBody { implicit ctx ⇒
|
||||
CategGrant(categSlug) {
|
||||
implicit val req = ctx.body
|
||||
IOptionResult(categRepo bySlug categSlug) { categ ⇒
|
||||
forms.topic.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.form(categ, err)),
|
||||
data ⇒ (for {
|
||||
topic ← topicApi.makeTopic(categ, data, ctx.me)
|
||||
} yield Redirect(routes.ForumCateg.show(categ.slug))
|
||||
).unsafePerformIO
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def show(categSlug: String, slug: String, page: Int) = Open { implicit ctx ⇒
|
||||
IOptionOk(topicApi.show(categSlug, slug, page)) {
|
||||
case (categ, topic, posts) ⇒ html.forum.topic.show(categ, topic, posts,
|
||||
postForm = (!posts.hasNextPage) option forms.post)
|
||||
CategGrant(categSlug) {
|
||||
IOptionOk(topicApi.show(categSlug, slug, page)) {
|
||||
case (categ, topic, posts) ⇒ html.forum.topic.show(categ, topic, posts,
|
||||
postForm = (!posts.hasNextPage) option forms.post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,19 @@ object DataForm {
|
|||
text: String,
|
||||
author: Option[String])
|
||||
|
||||
val post = Form(mapping(
|
||||
lazy val postMapping = mapping(
|
||||
"text" -> text(minLength = 3),
|
||||
"author" -> optional(text)
|
||||
)(PostData.apply)(PostData.unapply))
|
||||
)(PostData.apply)(PostData.unapply)
|
||||
|
||||
lazy val post = Form(postMapping)
|
||||
|
||||
case class TopicData(
|
||||
name: String,
|
||||
post: PostData)
|
||||
|
||||
lazy val topic = Form(mapping(
|
||||
"name" -> text(minLength = 3),
|
||||
"post" -> postMapping
|
||||
)(TopicData.apply)(TopicData.unapply))
|
||||
}
|
||||
|
|
11
app/forum/ForumHelper.scala
Normal file
11
app/forum/ForumHelper.scala
Normal file
|
@ -0,0 +1,11 @@
|
|||
package lila
|
||||
package forum
|
||||
|
||||
import user.UserHelper
|
||||
|
||||
trait ForumHelper { self: UserHelper ⇒
|
||||
|
||||
def showAuthorName(post: Post) =
|
||||
post.userId.fold(userIdToUsername, post.showAuthor)
|
||||
|
||||
}
|
|
@ -3,11 +3,12 @@ package forum
|
|||
|
||||
import org.joda.time.DateTime
|
||||
import com.novus.salat.annotations.Key
|
||||
import ornicar.scalalib.OrnicarRandom
|
||||
|
||||
case class Topic(
|
||||
@Key("_id") id: String,
|
||||
slug: String,
|
||||
categId: String,
|
||||
slug: String,
|
||||
name: String,
|
||||
views: Int,
|
||||
createdAt: DateTime,
|
||||
|
@ -15,3 +16,20 @@ case class Topic(
|
|||
nbPosts: Int = 0,
|
||||
lastPostId: String = "") {
|
||||
}
|
||||
|
||||
object Topic {
|
||||
|
||||
val idSize = 8
|
||||
|
||||
def apply(
|
||||
categId: String,
|
||||
slug: String,
|
||||
name: String): Topic = Topic(
|
||||
id = OrnicarRandom nextAsciiString idSize,
|
||||
categId = categId,
|
||||
slug = slug,
|
||||
name = name,
|
||||
views = 0,
|
||||
createdAt = DateTime.now,
|
||||
updatedAt = DateTime.now)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,41 @@
|
|||
package lila
|
||||
package forum
|
||||
|
||||
import user.User
|
||||
|
||||
import scalaz.effects._
|
||||
import com.github.ornicar.paginator._
|
||||
|
||||
final class TopicApi(env: ForumEnv, maxPerPage: Int) {
|
||||
|
||||
def show(categSlug: String, slug: String, page: Int): IO[Option[(Categ, Topic, Paginator[Post])]] =
|
||||
get(categSlug, slug) map {
|
||||
get(categSlug, slug) map {
|
||||
_ map {
|
||||
case (categ, topic) ⇒ (categ, topic, env.postApi.paginator(topic, page))
|
||||
}
|
||||
}
|
||||
|
||||
def makeTopic(
|
||||
categ: Categ,
|
||||
data: DataForm.TopicData,
|
||||
user: Option[User]): IO[Topic] = for {
|
||||
slug ← env.topicRepo.nextSlug(categ, data.name)
|
||||
topic = Topic(
|
||||
categId = categ.slug,
|
||||
slug = slug,
|
||||
name = data.name)
|
||||
post = Post(
|
||||
topicId = topic.id,
|
||||
author = data.post.author,
|
||||
user = user map env.userDbRef,
|
||||
text = data.post.text,
|
||||
number = 1)
|
||||
_ ← env.topicRepo saveIO topic
|
||||
_ ← env.postRepo saveIO post
|
||||
_ ← env.topicApi denormalize topic
|
||||
_ ← env.categApi denormalize categ
|
||||
} yield topic
|
||||
|
||||
def get(categSlug: String, slug: String) = for {
|
||||
categOption ← env.categRepo bySlug categSlug
|
||||
topicOption ← env.topicRepo.byTree(categSlug, slug)
|
||||
|
|
|
@ -9,8 +9,7 @@ import com.mongodb.casbah.Imports._
|
|||
import scalaz.effects._
|
||||
|
||||
final class TopicRepo(
|
||||
collection: MongoCollection
|
||||
) extends SalatDAO[Topic, String](collection) {
|
||||
collection: MongoCollection) extends SalatDAO[Topic, String](collection) {
|
||||
|
||||
def byId(id: String): IO[Option[Topic]] = io {
|
||||
findOneByID(id)
|
||||
|
@ -27,6 +26,23 @@ final class TopicRepo(
|
|||
))
|
||||
}
|
||||
|
||||
def nextSlug(categ: Categ, name: String, it: Int = 1): IO[String] = {
|
||||
val slug = slugify(name) + (it == 1).fold("", "-" + it)
|
||||
byTree(categ.slug, slug) flatMap {
|
||||
_.isDefined.fold(
|
||||
nextSlug(categ, name, it + 1),
|
||||
io(slug))
|
||||
}
|
||||
}
|
||||
|
||||
def slugify(input: String) = {
|
||||
import java.text.Normalizer
|
||||
val nowhitespace = input.replace(" ", "-")
|
||||
val normalized = Normalizer.normalize(nowhitespace, Normalizer.Form.NFD)
|
||||
val slug = """[^\w-]""".r.replaceAllIn(normalized, "")
|
||||
slug.toLowerCase
|
||||
}
|
||||
|
||||
val all: IO[List[Topic]] = io {
|
||||
find(DBObject()).toList
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ object Environment
|
|||
with round.RoundHelper
|
||||
with game.GameHelper
|
||||
with user.UserHelper
|
||||
with forum.ForumHelper
|
||||
with security.SecurityHelper
|
||||
with i18n.I18nHelper {
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import play.api.templates.Html
|
|||
trait FormHelper {
|
||||
|
||||
private val errNames = Map(
|
||||
"error.minLength" -> "Text is too short."
|
||||
"error.minLength" -> "Text is too short.",
|
||||
"error.required" -> "Required."
|
||||
)
|
||||
|
||||
def errMsg(form: Field): Html = Html {
|
||||
|
|
|
@ -28,7 +28,7 @@ title = trans.forum.str()) {
|
|||
<td class="last_post">
|
||||
@categ.lastPost.map {
|
||||
case (topic, post) => {
|
||||
<a href="@routes.ForumTopic.show(categ.slug, topic.slug, categ pageOf post)#@post.number">@showDate(post.createdAt)</a><br />by @userIdToUsername(post.userId)
|
||||
<a href="@routes.ForumTopic.show(categ.slug, topic.slug, categ pageOf post)#@post.number">@showDate(post.createdAt)</a><br />by @showAuthorName(post)
|
||||
}
|
||||
}
|
||||
</td>
|
||||
|
|
|
@ -13,7 +13,7 @@ title = categ.name) {
|
|||
<div class="pagination">
|
||||
@forum.pagination(routes.ForumCateg.show(categ.slug, 1), topics)
|
||||
</div>
|
||||
<a href="@routes.ForumTopic.create(categ.slug)" class="action button">Create a new topic</a>
|
||||
<a href="@routes.ForumTopic.form(categ.slug)" class="action button">Create a new topic</a>
|
||||
</div>
|
||||
|
||||
<table class="forum_topics_list">
|
||||
|
@ -33,7 +33,7 @@ title = categ.name) {
|
|||
<td class="right">@(topic.nbPosts - 1)</td>
|
||||
<td class="last_post">
|
||||
@topic.lastPost.map { post =>
|
||||
<a href="@routes.ForumTopic.show(categ.slug, topic.slug, topic pageOf post)#@post.number">@showDate(post.createdAt)</a><br />by @userIdToUsername(post.userId)
|
||||
<a href="@routes.ForumTopic.show(categ.slug, topic.slug, topic pageOf post)#@post.number">@showDate(post.createdAt)</a><br />by @showAuthorName(post)
|
||||
}
|
||||
</td>
|
||||
@if(isGranted(Permission.ModerateForum)) {
|
||||
|
|
12
app/views/forum/post/formFields.scala.html
Normal file
12
app/views/forum/post/formFields.scala.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
@(text: Field, author: Field)(implicit ctx: Context)
|
||||
<label>
|
||||
<span class="required">Message</span>
|
||||
<textarea name="@text.name" id="@text.id"></textarea>
|
||||
@errMsg(text)
|
||||
</label>
|
||||
@if(!ctx.isAuth) {
|
||||
<label>
|
||||
<span>Author</span>
|
||||
<input class="author" type="text" name="@author.name" id="@author.id">
|
||||
</label>
|
||||
}
|
24
app/views/forum/topic/form.scala.html
Normal file
24
app/views/forum/topic/form.scala.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
@(categ: lila.forum.Categ, form: Form[lila.forum.DataForm.TopicData])(implicit ctx: Context)
|
||||
|
||||
@forum.layout(
|
||||
title = "New forum topic") {
|
||||
<ol class="crumbs">
|
||||
<li><a href="@routes.ForumCateg.index">Forum</a></li>
|
||||
<li><h1>@categ.name</h1></li>
|
||||
</ol>
|
||||
<br />
|
||||
<h1>New topic</h1>
|
||||
@form.globalError.map { error =>
|
||||
<p class="error">@error.message</p>
|
||||
}
|
||||
<form action="@routes.ForumTopic.create(categ.slug)" method="POST" novalidate>
|
||||
<label>
|
||||
<span class="required">Subject</span>
|
||||
<input class="subject" type="text" name="@form("name").name" id="@form("name").id">
|
||||
@errMsg(form("name"))
|
||||
</label>
|
||||
@forum.post.formFields(form("post")("text"), form("post")("author"))
|
||||
<button class="submit button" type="submit">Create the topic</button>
|
||||
<a href="@routes.ForumCateg.show(categ.slug)" style="margin-left:20px">Cancel</a>
|
||||
</form>
|
||||
}
|
|
@ -21,7 +21,7 @@ title = topic.name) {
|
|||
@post.userId.map { userId =>
|
||||
<span class="author authenticated">@userIdLink(userId.some)</span>
|
||||
}.getOrElse {
|
||||
<span class="author">@post.showAuthor</span>
|
||||
<span class="author">@showAuthorName(post)</span>
|
||||
}
|
||||
<span class="createdAt">@showDate(post.createdAt)</span>
|
||||
<a class="anchor" href="routes.ForumTopic.show(categ.slug, topic.slug, posts.page)">#@post.number</a>
|
||||
|
@ -41,17 +41,7 @@ title = topic.name) {
|
|||
<p class="error">@error.message</p>
|
||||
}
|
||||
<form action="@routes.ForumPost.create(categ.slug, topic.slug, posts.currentPage)#reply" method="POST" novalidate>
|
||||
<label>
|
||||
<span class="required">Message</span>
|
||||
<textarea name="@form("text").name" id="@form("text").id"></textarea>
|
||||
@errMsg(form("text"))
|
||||
</label>
|
||||
@if(!ctx.isAuth) {
|
||||
<label>
|
||||
<span>Author</span>
|
||||
<input class="author" type="text" name="@form("author").name" id="@form("author").id">
|
||||
</label>
|
||||
}
|
||||
@forum.post.formFields(form("text"), form("author"))
|
||||
<input type="submit" class="submit button" value="Reply" />
|
||||
<a href="@routes.ForumCateg.show(categ.slug)" style="margin-left:20px">Cancel</a>
|
||||
</form>
|
||||
|
|
|
@ -91,6 +91,7 @@ GET /lobby/socket controllers.Lobby.socket
|
|||
# Forum
|
||||
GET /forum controllers.ForumCateg.index
|
||||
GET /forum/:slug controllers.ForumCateg.show(slug: String, page: Int ?= 1)
|
||||
GET /forum/:categSlug/form controllers.ForumTopic.form(categSlug: String)
|
||||
POST /forum/:categSlug/new controllers.ForumTopic.create(categSlug: String)
|
||||
GET /forum/:categSlug/:slug controllers.ForumTopic.show(categSlug: String, slug: String, page: Int ?= 1)
|
||||
POST /forum/:categSlug/:slug/new controllers.ForumPost.create(categSlug: String, slug: String, page: Int ?= 1)
|
||||
|
|
Loading…
Reference in a new issue