Implement captcha non-empty cache with rotation

This commit is contained in:
Thibault Duplessis 2012-05-27 12:18:39 +02:00
parent 0701c287a5
commit db22087faa
4 changed files with 55 additions and 21 deletions

View file

@ -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(

View file

@ -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 {

View file

@ -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
}

View file

@ -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
for {
game gameOption toValid "No checkmate available in db"
challenge get(game)
} yield challenge
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
}
}
def get(id: String): IO[Valid[Challenge]] =
gameRepo game id map { gameOption
for {
game gameOption toValid "No such game: " + id
challenge get(game)
} yield challenge
private def add(c: Challenge) {
find(c.gameId) ifNone { challenges = nel(c, challenges.list take history) }
date = DateTime.now
}
private def get(game: DbGame): Valid[Challenge] = for {
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 makeChallenge(game)
} yield challenge
}
private def getFromDb(id: String): Valid[Challenge] = {
val gameOption = (gameRepo game id).unsafePerformIO
for {
game gameOption toValid "No such game: " + id
challenge makeChallenge(game)
} yield challenge
}
private def makeChallenge(game: DbGame): Valid[Challenge] = for {
rewinded rewind(game)
solutions solve(rewinded)
} yield Challenge(game.id, fen(rewinded), rewinded.player, solutions)