Compare commits
2
Commits
98fa797a05
...
621fcf90fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621fcf90fa | ||
|
|
c45889838b |
@@ -22,7 +22,7 @@ object AuctionPublisher extends ActorModule with Logging {
|
|||||||
sealed trait Command
|
sealed trait Command
|
||||||
private case object Ready extends Command
|
private case object Ready extends Command
|
||||||
case class Publish(auction: Auction, author: User) extends Command
|
case class Publish(auction: Auction, author: User) extends Command
|
||||||
private case class AuctionConfirmed(id: Int, responseTopic: String) extends Command
|
private case class AuctionConfirmed(id: Long, responseTopic: String) extends Command
|
||||||
|
|
||||||
private final val topic = "auctions/created"
|
private final val topic = "auctions/created"
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import javax.inject._
|
import javax.inject.*
|
||||||
import play.api._
|
import play.api.*
|
||||||
import play.api.mvc._
|
import play.api.mvc.*
|
||||||
|
import authentication.{AuthenticatedAction, AuthenticatedRequest}
|
||||||
|
import caching.{Etagable, checkFresh}
|
||||||
|
import models.User
|
||||||
|
import play.api.libs.json.Json
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller creates an `Action` to handle HTTP requests to the
|
* This controller creates an `Action` to handle HTTP requests to the
|
||||||
* application's home page.
|
* application's home page.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
|
class HomeController @Inject()(val controllerComponents: ControllerComponents,
|
||||||
|
authenticatedAction: AuthenticatedAction)
|
||||||
|
extends BaseController with Logging {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an Action to render an HTML page.
|
* Create an Action to render an HTML page.
|
||||||
@@ -21,4 +32,26 @@ class HomeController @Inject()(val controllerComponents: ControllerComponents) e
|
|||||||
def index() = Action { implicit request: Request[AnyContent] =>
|
def index() = Action { implicit request: Request[AnyContent] =>
|
||||||
Ok(views.html.index())
|
Ok(views.html.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def userData = authenticatedAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
|
val user = request.user
|
||||||
|
request.checkFresh(user) {
|
||||||
|
// TODO: put the max-age somewhere into config
|
||||||
|
Ok(Json.toJson(user)).withHeaders("Etag" -> user.etag, "Cache-Control" -> "private, max-age=15, must-revalidate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private given Etagable[models.User] with {
|
||||||
|
private val base64 = Base64.getUrlEncoder
|
||||||
|
|
||||||
|
extension (a: User)
|
||||||
|
override def etag: String = {
|
||||||
|
val md = MessageDigest.getInstance("SHA-1")
|
||||||
|
md.update(ByteBuffer.allocate(8).putLong(a.id.getOrElse(0)))
|
||||||
|
md.update(a.gid.getBytes(UTF_8))
|
||||||
|
md.update(a.username.getBytes(UTF_8))
|
||||||
|
|
||||||
|
base64.encodeToString(md.digest())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package controllers.caching
|
||||||
|
|
||||||
|
import play.api.mvc.Results.NotModified
|
||||||
|
import play.api.mvc.{Request, Result}
|
||||||
|
|
||||||
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
|
trait Etagable[A]:
|
||||||
|
extension (a: A) def etag: String
|
||||||
|
|
||||||
|
|
||||||
|
extension [A, R[X] <: Request[X], E: Etagable](request: R[A]) {
|
||||||
|
def checkFreshAsync(entity: E)(onStale: => Future[Result])(using ec: ExecutionContext): Future[Result] = {
|
||||||
|
compareEtag(entity).fold(onStale)(
|
||||||
|
etag => Future.successful(NotModified.withHeaders("ETag" -> etag))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkFresh(entity: E)(onStale: => Result): Result = {
|
||||||
|
compareEtag(entity).fold(onStale)(
|
||||||
|
etag => NotModified.withHeaders("ETag" -> etag)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def compareEtag(entity: E) = request.headers.get("If-None-Match").filter(e => e == entity.etag)
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ class AuctionsDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid
|
|||||||
db.run((Auctions returning Auctions) += auction)
|
db.run((Auctions returning Auctions) += auction)
|
||||||
}
|
}
|
||||||
|
|
||||||
def changeStatus(auctionId: Int, status: AuctionStatus): Future[Boolean] = {
|
def changeStatus(auctionId: Long, status: AuctionStatus): Future[Boolean] = {
|
||||||
db.run(Auctions.filter(_.id === auctionId).map(_.status).update(status))
|
db.run(Auctions.filter(_.id === auctionId).map(_.status).update(status))
|
||||||
.map(_ == 1)
|
.map(_ == 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,15 @@ import play.api.libs.json.{JsString, JsValue, Json, OWrites, Writes}
|
|||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
case class User(id: Option[Int], username: String, gid: String, passwordDigest: Option[String])
|
case class User(id: Option[Long], username: String, gid: String, passwordDigest: Option[String])
|
||||||
object User {
|
object User {
|
||||||
given writes: Writes[User] = new Writes[User] {
|
given writes: Writes[User] = (o: User) => Json.obj(
|
||||||
override def writes(o: User): JsValue = Json.obj(
|
"gid" -> o.gid,
|
||||||
"gid" -> o.gid,
|
"username" -> o.username
|
||||||
"username" -> o.username
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class AuthToken(id: Option[Int], userId: Int, token: String, createdAt: Instant, expiresAt: Instant)
|
case class AuthToken(id: Option[Long], userId: Long, token: String, createdAt: Instant, expiresAt: Instant)
|
||||||
object AuthToken {
|
object AuthToken {
|
||||||
given writes: OWrites[AuthToken] = (a: AuthToken) => Json.obj(
|
given writes: OWrites[AuthToken] = (a: AuthToken) => Json.obj(
|
||||||
"token" -> a.token,
|
"token" -> a.token,
|
||||||
@@ -36,7 +34,7 @@ object AuctionStatus {
|
|||||||
implicit val writes: Writes[AuctionStatus] = (o: AuctionStatus) => JsString(o.toString)
|
implicit val writes: Writes[AuctionStatus] = (o: AuctionStatus) => JsString(o.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Auction(id: Option[Int], name: String, status: AuctionStatus, gid: String, authorId: Int,
|
case class Auction(id: Option[Long], name: String, status: AuctionStatus, gid: String, authorId: Long,
|
||||||
startingPrice: BigDecimal, currentBid: Option[BigDecimal],
|
startingPrice: BigDecimal, currentBid: Option[BigDecimal],
|
||||||
currentBidderId: Option[Int], currentBidAt: Option[Instant],
|
currentBidderId: Option[Int], currentBidAt: Option[Instant],
|
||||||
createdAt: Instant)
|
createdAt: Instant)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import slick.model.ForeignKeyAction.{Cascade, Restrict}
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class UsersTable(tag: Tag) extends Table[User](tag, "users") {
|
class UsersTable(tag: Tag) extends Table[User](tag, "users") {
|
||||||
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
|
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
|
||||||
def gid = column[String]("gid", O.Unique)
|
def gid = column[String]("gid", O.Unique)
|
||||||
def username = column[String]("username", O.Unique)
|
def username = column[String]("username", O.Unique)
|
||||||
def passwordDigest = column[Option[String]]("password_digest")
|
def passwordDigest = column[Option[String]]("password_digest")
|
||||||
@@ -20,8 +20,8 @@ lazy val Users = TableQuery[UsersTable]
|
|||||||
|
|
||||||
class AuthTokensTable(tag: Tag) extends Table[AuthToken](tag, "auth_tokens") {
|
class AuthTokensTable(tag: Tag) extends Table[AuthToken](tag, "auth_tokens") {
|
||||||
|
|
||||||
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
|
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
|
||||||
def userId = column[Int]("user_id")
|
def userId = column[Long]("user_id")
|
||||||
def token = column[String]("token")
|
def token = column[String]("token")
|
||||||
def createdAt = column[Instant]("created_at")
|
def createdAt = column[Instant]("created_at")
|
||||||
def expiresAt = column[Instant]("expires_at")
|
def expiresAt = column[Instant]("expires_at")
|
||||||
@@ -40,11 +40,11 @@ implicit val auctionStatusColumnType: BaseColumnType[AuctionStatus] =
|
|||||||
)
|
)
|
||||||
|
|
||||||
class AuctionsTable(tag: Tag) extends Table[Auction](tag, "auctions") {
|
class AuctionsTable(tag: Tag) extends Table[Auction](tag, "auctions") {
|
||||||
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
|
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
|
||||||
def name = column[String]("name")
|
def name = column[String]("name")
|
||||||
def status = column[AuctionStatus]("status")
|
def status = column[AuctionStatus]("status")
|
||||||
def gid = column[String]("gid", O.Unique)
|
def gid = column[String]("gid", O.Unique)
|
||||||
def authorId = column[Int]("author_id")
|
def authorId = column[Long]("author_id")
|
||||||
def startingPrice = column[BigDecimal]("starting_price", O.SqlType("DECIMAL(20, 3)"))
|
def startingPrice = column[BigDecimal]("starting_price", O.SqlType("DECIMAL(20, 3)"))
|
||||||
def currentBid = column[Option[BigDecimal]]("current_bid", O.SqlType("DECIMAL(20, 3)"))
|
def currentBid = column[Option[BigDecimal]]("current_bid", O.SqlType("DECIMAL(20, 3)"))
|
||||||
def currentBidderId = column[Option[Int]]("current_bidder_id")
|
def currentBidderId = column[Option[Int]]("current_bidder_id")
|
||||||
|
|||||||
@@ -8,3 +8,8 @@ services:
|
|||||||
- db.env
|
- db.env
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- "db:/var/lib/postgresql"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db:
|
||||||
@@ -8,6 +8,7 @@ play.filters.csrf.header.bypassHeaders {
|
|||||||
}
|
}
|
||||||
play.filters.cors {
|
play.filters.cors {
|
||||||
allowedOrigins = ["http://localhost:4321"]
|
allowedOrigins = ["http://localhost:4321"]
|
||||||
|
exposedHeaders = ["ETag"]
|
||||||
}
|
}
|
||||||
|
|
||||||
play.modules.enabled += "mqttClient.Module"
|
play.modules.enabled += "mqttClient.Module"
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ GET / controllers.HomeController.index()
|
|||||||
POST /login controllers.authentication.SessionsController.login()
|
POST /login controllers.authentication.SessionsController.login()
|
||||||
DELETE /logout controllers.authentication.SessionsController.logout()
|
DELETE /logout controllers.authentication.SessionsController.logout()
|
||||||
|
|
||||||
|
GET /user_data controllers.HomeController.userData()
|
||||||
|
|
||||||
# Map static resources from the /public folder to the /assets URL path
|
# Map static resources from the /public folder to the /assets URL path
|
||||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user