Implement captcha non-empty cache with rotation
This commit is contained in:
parent
0701c287a5
commit
db22087faa
|
@ -19,7 +19,7 @@ object ForumPost extends LilaController with Forum {
|
|||
IOptionResult(topicApi.show(categSlug, slug, page)) {
|
||||
case (categ, topic, posts) ⇒ forms.post.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.show(
|
||||
categ, topic, posts, Some(err.pp -> forms.captchaCreate))),
|
||||
categ, topic, posts, Some(err -> forms.captchaCreate))),
|
||||
data ⇒ (for {
|
||||
post ← postApi.makePost(categ, topic, data, ctx.me)
|
||||
} yield Redirect("%s#%d".format(
|
||||
|
|
|
@ -31,11 +31,9 @@ final class DataForm(captcher: Captcha) {
|
|||
|
||||
def topicWithCaptcha = topic -> captchaCreate
|
||||
|
||||
def captchaCreate: Captcha.Challenge =
|
||||
(captcher.create).unsafePerformIO.err
|
||||
def captchaCreate: Captcha.Challenge = captcher.create
|
||||
|
||||
def captchaGet(gameId: String): Captcha.Challenge =
|
||||
(captcher get gameId).unsafePerformIO.err
|
||||
def captchaGet(gameId: String): Captcha.Challenge = captcher get gameId
|
||||
}
|
||||
|
||||
object DataForm {
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.mongodb.casbah.Imports._
|
|||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
import java.util.Date
|
||||
import scala.util.Random
|
||||
import scalaz.effects._
|
||||
|
||||
class GameRepo(collection: MongoCollection)
|
||||
|
@ -94,13 +95,14 @@ class GameRepo(collection: MongoCollection)
|
|||
)
|
||||
}
|
||||
|
||||
val findOneStandardCheckmate: IO[Option[DbGame]] = io {
|
||||
def findRandomStandardCheckmate(distribution: Int): IO[Option[DbGame]] = io {
|
||||
find(DBObject(
|
||||
"status" -> Status.Mate.id,
|
||||
"v" -> Variant.Standard.id
|
||||
))
|
||||
.sort(DBObject("createdAt" -> -1))
|
||||
.limit(1)
|
||||
.skip(Random nextInt distribution)
|
||||
.toList.map(_.decode).flatten.headOption
|
||||
}
|
||||
|
||||
|
|
|
@ -4,31 +4,65 @@ package site
|
|||
import game._
|
||||
import chess.{ Game, Color }
|
||||
import chess.format.{ Forsyth, PgnReader }
|
||||
import scalaz.effects._
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import scalaz.{ NonEmptyList, NonEmptyLists }
|
||||
import scala.collection.mutable
|
||||
import scala.util.Random
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
|
||||
// only works with standard chess (not chess960)
|
||||
final class Captcha(gameRepo: GameRepo) {
|
||||
|
||||
import Captcha._
|
||||
|
||||
val create: IO[Valid[Challenge]] =
|
||||
gameRepo.findOneStandardCheckmate map { gameOption ⇒
|
||||
def create: Challenge = Cache.create
|
||||
|
||||
def get(id: String): Challenge = Cache get id
|
||||
|
||||
private object Cache extends NonEmptyLists {
|
||||
val timeout = 10 seconds
|
||||
val history = 1000
|
||||
private var challenges: NonEmptyList[Challenge] = nel(createFromDb.err)
|
||||
private var date = DateTime.now
|
||||
|
||||
def create = { refresh; current }
|
||||
|
||||
def get(id: String) = find(id) | { getFromDb(id).err ~ add }
|
||||
|
||||
private def current = challenges.head
|
||||
|
||||
private def refresh {
|
||||
if (date < DateTime.now - timeout) {
|
||||
createFromDb.toOption foreach add
|
||||
}
|
||||
}
|
||||
|
||||
private def add(c: Challenge) {
|
||||
find(c.gameId) ifNone { challenges = nel(c, challenges.list take history) }
|
||||
date = DateTime.now
|
||||
}
|
||||
|
||||
private def find(id: String) = challenges.list.find(_.gameId == id)
|
||||
}
|
||||
|
||||
private def createFromDb: Valid[Challenge] = {
|
||||
val gameOption = gameRepo.findRandomStandardCheckmate(100).unsafePerformIO
|
||||
for {
|
||||
game ← gameOption toValid "No checkmate available in db"
|
||||
challenge ← get(game)
|
||||
challenge ← makeChallenge(game)
|
||||
} yield challenge
|
||||
}
|
||||
|
||||
def get(id: String): IO[Valid[Challenge]] =
|
||||
gameRepo game id map { gameOption ⇒
|
||||
private def getFromDb(id: String): Valid[Challenge] = {
|
||||
val gameOption = (gameRepo game id).unsafePerformIO
|
||||
for {
|
||||
game ← gameOption toValid "No such game: " + id
|
||||
challenge ← get(game)
|
||||
challenge ← makeChallenge(game)
|
||||
} yield challenge
|
||||
}
|
||||
|
||||
private def get(game: DbGame): Valid[Challenge] = for {
|
||||
private def makeChallenge(game: DbGame): Valid[Challenge] = for {
|
||||
rewinded ← rewind(game)
|
||||
solutions ← solve(rewinded)
|
||||
} yield Challenge(game.id, fen(rewinded), rewinded.player, solutions)
|
||||
|
|
Loading…
Reference in a new issue