diff --git a/app/controllers/AuctionsController.scala b/app/controllers/AuctionsController.scala index e15d5b9..74a6b3a 100644 --- a/app/controllers/AuctionsController.scala +++ b/app/controllers/AuctionsController.scala @@ -3,23 +3,25 @@ package controllers import actors.auctions.AuctionPublisher import controllers.authentication.AuthenticatedAction import dao.{AuctionsDAO, AuthTokensDAO} -import models.AuctionStatus.Ready -import models.{Auction, User} import org.apache.pekko.actor.typed.ActorRef -import play.api.libs.functional.syntax.toFunctionalBuilderOps import play.api.libs.json.* import play.api.mvc.{BaseController, ControllerComponents} import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} +import models.Auction +import caching.{CheckFresh, DigestEtagable, Etagable} + +import java.io.DataOutputStream @Singleton class AuctionsController @Inject()(val controllerComponents: ControllerComponents, val auctionsDAO: AuctionsDAO, val authTokensDAO: AuthTokensDAO, authenticatedAction: AuthenticatedAction, - val auctionPublisher: ActorRef[AuctionPublisher.Command], - implicit val ec: ExecutionContext) extends BaseController { + val auctionPublisher: ActorRef[AuctionPublisher.Command]) + (implicit val ec: ExecutionContext) + extends BaseController with CheckFresh { def index = authenticatedAction.async { request => for { auctions <- auctionsDAO.list_available(request.user) @@ -27,6 +29,16 @@ class AuctionsController @Inject()(val controllerComponents: ControllerComponent } yield Ok(json) } + def show(id: Long) = authenticatedAction.async { implicit request => + auctionsDAO.showAvailable(request.user, id).map { auctionOpt => + auctionOpt.fold(NotFound) { auction => + checkFresh(auction) { + Ok(Json.toJson(auction)).withHeaders("etag" -> auction.etag) + } + } + } + } + def create = authenticatedAction.async(parse.json) { implicit request => val auctionCreateDataResult = request.body.validate[AuctionCreateData] auctionCreateDataResult.fold( @@ -44,4 +56,18 @@ class AuctionsController @Inject()(val controllerComponents: ControllerComponent private case class AuctionCreateData(name: String, startingPrice: BigDecimal) private given Reads[AuctionCreateData] = Json.reads[AuctionCreateData] + + private given DigestEtagable[Auction] with { + override protected def writeDigestData(a: Auction, s: DataOutputStream): Unit = { + s.writeLong(a.id.getOrElse(0)) + s.writeLong(a.authorId) + s.writeUTF(a.gid) + s.writeUTF(a.name) + s.writeLong(a.createdAt.getEpochSecond) + + s.writeLong(a.currentBidderId.getOrElse(0)) + a.currentBid.foreach(b => s.writeLong((b * 1000).toLong)) + a.currentBidAt.foreach(ba => s.writeLong(ba.getEpochSecond)) + } + } } diff --git a/app/dao/AuctionsDAO.scala b/app/dao/AuctionsDAO.scala index 33a893e..2d2735c 100644 --- a/app/dao/AuctionsDAO.scala +++ b/app/dao/AuctionsDAO.scala @@ -1,31 +1,35 @@ package dao -import models.{Auction, AuctionStatus} -import schemas.Auctions -import schemas.given import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.PostgresProfile import slick.jdbc.PostgresProfile.api.* - import java.time.Instant import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} import scala.util.Random +import models.{Auction, AuctionStatus, User} +import schemas.Auctions +import schemas.given + class AuctionsDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)(implicit val ec: ExecutionContext) extends HasDatabaseConfigProvider[PostgresProfile] { - def list_available(user: models.User): Future[Seq[Auction]] = { + def list_available(user: User): Future[Seq[Auction]] = { + db.run(availableScope(user).result) + } + + def showAvailable(user: User, id: Long): Future[Option[Auction]] = { db.run { - Auctions.filter(row => (row.status === AuctionStatus.Ready) && (row.authorId =!= user.id.get)).result + availableScope(user).filter(_.id === id).take(1).result.headOption } } - def list_created_by(user: models.User): Future[Seq[Auction]] = { + def list_created_by(user: User): Future[Seq[Auction]] = { db.run(Auctions.filter(_.authorId === user.id).result) } - def create(name: String, startingPrice: BigDecimal, author: models.User): Future[Auction] = { + def create(name: String, startingPrice: BigDecimal, author: User): Future[Auction] = { val auction = Auction( id = None, name = name, gid = Random.alphanumeric.take(24).mkString, @@ -45,6 +49,10 @@ class AuctionsDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid def changeStatus(auctionId: Long, status: AuctionStatus): Future[Boolean] = { db.run(Auctions.filter(_.id === auctionId).map(_.status).update(status)) - .map(_ == 1) + .map(_ == 1) + } + + private def availableScope(user: User) = { + Auctions.filter(row => (row.status === AuctionStatus.Ready) && (row.authorId =!= user.id.get)) } } diff --git a/conf/routes b/conf/routes index 20c586e..2508da5 100644 --- a/conf/routes +++ b/conf/routes @@ -15,6 +15,7 @@ GET /user_data controllers.HomeController.userData() GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) GET /auctions controllers.AuctionsController.index() +GET /auctions/:id controllers.AuctionsController.show(id: Long) POST /auctions controllers.AuctionsController.create() -> /-devel controllers.devel.DevRouter \ No newline at end of file