Skip to content
This repository has been archived by the owner on May 9, 2019. It is now read-only.

Commit

Permalink
Add search routes, controller, view, messages, and wiring
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhanks committed Mar 30, 2019
1 parent 041e256 commit 3f10ffb
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 3 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ lazy val userImpl = (project in file("user-impl"))
lazy val webGateway = (project in file("web-gateway"))
.settings(commonSettings: _*)
.enablePlugins(PlayScala, LagomPlay, SbtReactiveAppPlugin)
.dependsOn(biddingApi, itemApi, userApi)
.dependsOn(biddingApi, itemApi, searchApi, userApi)
.settings(
libraryDependencies ++= Seq(
lagomScaladslServer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.example.elasticsearch.IndexedItem
import com.example.elasticsearch.request._
import com.example.elasticsearch.response._
import com.lightbend.lagom.scaladsl.api.ServiceCall
import play.api.libs.json.Json

import scala.concurrent.ExecutionContext.Implicits.global

Expand Down
5 changes: 4 additions & 1 deletion web-gateway/app/Loader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package loader

import com.example.auction.bidding.api.BiddingService
import com.example.auction.item.api.ItemService
import com.example.auction.search.api.SearchService
import com.example.auction.user.api.UserService
import com.lightbend.lagom.scaladsl.api.{LagomConfigComponent, ServiceAcl, ServiceInfo}
import com.lightbend.lagom.scaladsl.client.LagomServiceClientComponents
import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents
import com.lightbend.rp.servicediscovery.lagom.scaladsl.LagomServiceLocatorComponents
import com.softwaremill.macwire._
import controllers.{AssetsComponents, ItemController, Main, ProfileController}
import controllers.{AssetsComponents, ItemController, Main, ProfileController, SearchController}
import play.api.ApplicationLoader.Context
import play.api.libs.ws.ahc.AhcWSComponents
import play.api.{ApplicationLoader, BuiltInComponentsFromContext, Mode}
Expand Down Expand Up @@ -41,10 +42,12 @@ abstract class WebGateway(context: Context) extends BuiltInComponentsFromContext
lazy val userService = serviceClient.implement[UserService]
lazy val itemService = serviceClient.implement[ItemService]
lazy val biddingService = serviceClient.implement[BiddingService]
lazy val searchService = serviceClient.implement[SearchService]

lazy val main = wire[Main]
lazy val itemController = wire[ItemController]
lazy val profileController = wire[ProfileController]
lazy val searchController = wire[SearchController]
}

class WebGatewayLoader extends ApplicationLoader {
Expand Down
78 changes: 78 additions & 0 deletions web-gateway/app/controllers/SearchController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package controllers

import com.example.auction.search.api.{SearchRequest, SearchService}
import com.example.auction.user.api.UserService
import com.example.auction.utils.PaginatedSequence
import com.typesafe.config.Config
import play.api.data.Forms.{nonEmptyText, _}
import play.api.data.{Form, Mapping}
import play.api.mvc.{ControllerComponents, _}

import scala.concurrent.{ExecutionContext, Future}

class SearchController(config: Config,
searchService: SearchService,
userService: UserService,
controllerComponents: ControllerComponents)(implicit ec: ExecutionContext)
extends AbstractAuctionController(userService, controllerComponents) {

private val showInlineInstruction: Boolean = config.getBoolean("online-auction.instruction.show")
private val pageSize: Int = config.getInt("items-search.page-size")

def searchForm(): Action[AnyContent] = Action.async { implicit request =>
requireUser(loadNav(_).map { implicit nav =>
Ok(views.html.searchItems(showInlineInstruction = showInlineInstruction,
form = SearchItemsForm.form.fill(SearchItemsForm()),
optionalSearchItemPaginatedSequence = None))
})
}

def search(): Action[AnyContent] = Action.async { implicit request =>
requireUser(user =>
loadNav(user).flatMap { implicit nav =>
SearchItemsForm.form.bindFromRequest().fold(
errorForm => {
Future.successful(Ok(views.html.searchItems(
showInlineInstruction = showInlineInstruction,
form = errorForm,
optionalSearchItemPaginatedSequence = None)))
},
searchItemsForm => {
searchService.search(searchItemsForm.pageNumber, pageSize)
.invoke(SearchRequest(
if (searchItemsForm.keywords.isEmpty) None else Some(searchItemsForm.keywords),
Some(searchItemsForm.maximumPrice.intValue()),
Some(searchItemsForm.currency.name)))
.map(searchResponse => {
Ok(views.html.searchItems(
showInlineInstruction = showInlineInstruction,
form = SearchItemsForm.form.fill(searchItemsForm),
optionalSearchItemPaginatedSequence = Some(PaginatedSequence(
searchResponse.items,
searchResponse.pageNo,
searchResponse.pageSize,
searchResponse.numResults))))
})
})
})
}
}


case class SearchItemsForm(keywords: String = "",
maximumPrice: BigDecimal = 0.0,
currency: Currency = Currency.USD,
pageNumber: Int = 0)

object SearchItemsForm {
val currency: Mapping[Currency] = nonEmptyText
.verifying("invalid.currency", c => Currency.isDefined(c))
.transform[Currency](Currency.valueOf, _.name)
val form = Form(mapping(
"keywords" -> text,
"maximumPrice" -> bigDecimal,
"currency" -> currency,
"pageNumber" -> number(min = 0)
)(SearchItemsForm.apply)(SearchItemsForm.unapply))
}

1 change: 1 addition & 0 deletions web-gateway/app/views/main.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
</div>
<div class="top-bar-right">
<ul class="menu dropdown" data-dropdown-menu>
<li><a href="@routes.SearchController.searchForm()">@Messages("search")</a></li>
<li>
<a href="@routes.ProfileController.myItems(ItemStatus.Created.toString.toLowerCase(Locale.ENGLISH), None)">
@nav.user.map(_.name).getOrElse(Messages("logInAs"))</a>
Expand Down
122 changes: 122 additions & 0 deletions web-gateway/app/views/searchItems.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
@import com.example.auction.item.api.ItemStatus
@import com.example.auction.item.api.ItemSummary
@import com.example.auction.search.api.SearchItem
@import com.example.auction.utils
@import controllers.Currency
@import helper._
@import controllers.{Currency => Currencies}

@import java.util.Locale

@import com.example.auction.utils.PaginatedSequence
@(showInlineInstruction: Boolean,
form: Form[SearchItemsForm],
optionalSearchItemPaginatedSequence: Option[PaginatedSequence[SearchItem]]
)(implicit nav: Nav, request: RequestHeader)

@main(Messages("searchItems")) {

<h2>@Messages("searchItems")</h2>

@if(showInlineInstruction) {
<p>@Messages("instruction.search")</p>

<p>@Html(Messages("instruction.enableSearch", "<a href=\"https://github.com/lagom/online-auction-scala/blob/master/README.md\">README.md</a>"))</p>
}

@foundationForm(form, routes.SearchController.search()) {
<div class="row">
<div class="medium-6 columns">
@inputText(form("keywords"))
</div>
</div>
<div class="column row">
@inputText(form("maximumPrice"), 'type -> "number", 'min -> 0, 'value -> form("maximumPrice").value)
@select(
field = form("currency"),
options = Currencies.values.map(c => c.name -> c.getDisplayName).sortBy(_._2)
)
</div>
<input type="hidden" id="pageNumber" value="0" name="@form("pageNumber").name"/>

<input class="button" type="submit" value="@Messages("searchItems")"/>
}
@if(optionalSearchItemPaginatedSequence.isDefined) {
@defining(optionalSearchItemPaginatedSequence.get) { searchItemPaginatedSequence: PaginatedSequence[SearchItem] =>

@if(!searchItemPaginatedSequence.isEmpty) {
<script>
var updatePage = function (pageNum) {
document.getElementById("pageNumber").value = pageNum;
document.getElementById("searchForm").submit();
}
</script>

@if(searchItemPaginatedSequence.count < searchItemPaginatedSequence.pageSize) {
<div>@Messages("searchFoundCount", searchItemPaginatedSequence.count)</div>
} else {
<div>@Messages("searchFoundCountWithPagination",
searchItemPaginatedSequence.page * searchItemPaginatedSequence.pageSize + 1,
searchItemPaginatedSequence.page * searchItemPaginatedSequence.pageSize + searchItemPaginatedSequence.items.size,
searchItemPaginatedSequence.count)</div>
}

<table>

<thead>
<tr>
<th>@Messages("title")</th>
<th>@Messages("status")</th>
<th>@Messages("currentPrice")</th>
</tr>
</thead>

<tbody>
@for(item <- searchItemPaginatedSequence.items) {
<tr>
<td>
<a href="@routes.ItemController.getItem(item.id)">@item.title</a>
</td>
<td>@item.itemStatus</td>
<td>
@if(
item.itemStatus.equals(ItemStatus.Auction.toString)
&& item.price.isDefined
&& item.price.get > 0) {
@Currency.valueOf(item.currencyId).format(item.price.get)
} else {
-
}
</td>
</tr>
<tr>
<td colspan="4">@item.description</td>
</tr>
}
</tbody>
</table>
@if(searchItemPaginatedSequence.count > searchItemPaginatedSequence.pageSize * (searchItemPaginatedSequence.page + 1)) {
@foundationForm(form, routes.SearchController.search()) {
@defining(form("keywords")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("maximumPrice")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("currency")) { field: Field =>
<input type="hidden" id="@field.id" value="@field.value" name="@field.name"/>
}
@defining(form("pageNumber")) { field: Field =>
<input type="hidden" id="@field.id" value="@(searchItemPaginatedSequence.page + 1)" name="@field.name"/>
}

<input class="button" type="submit" value="@Messages("nextResultPage")"/>
}
}

}
}
}
}


4 changes: 4 additions & 0 deletions web-gateway/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

lagom.circuit-breaker.default.call-timeout = 5s

online-auction.instruction.show = false

play.application.loader = loader.WebGatewayLoader
play.filters.headers.contentSecurityPolicy = "img-src 'self' data:; default-src 'self'"

items-search.page-size = 3
12 changes: 12 additions & 0 deletions web-gateway/conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ createItem=Create item

invalid.increment=The increment must be a multiple of 0.5.
invalid.reserve=The reserve must be a multiple of 0.5.
invalid.currency=Please enter a valid currency

seller=Seller
auctionStart=Auction start
Expand Down Expand Up @@ -69,3 +70,14 @@ items.caption={0} ({1})

pagination.loadMore=Load more
pagination.reload=Reload

search=Search
searchItems=Search Items
instruction.search=Here you can search for auctioned items. You can filter your search by specifying the maximum price of items to search for and the currency to search in.
instruction.enableSearch=Search functionality can be enabled by downloading and running a local instance of elasticsearch. Steps for doing this can be found in {0}. If the lagom system is up and running, you don''t need to restart the system after installing and running elasticsearch; the search service becomes functional once it starts.
keywords=Exact terms:
maximumPrice=Maximum Price (set to '0' to disable filter)
currency=
searchFoundCount={0} items found
searchFoundCountWithPagination=Displaying {0} - {1} of {2} items found
nextResultPage=More
2 changes: 2 additions & 0 deletions web-gateway/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ GET /item/:id controllers.ItemController.getItem(id: j
POST /item/:id/auction controllers.ItemController.startAuction(id: java.util.UUID)
POST /item/:id/bid controllers.ItemController.placeBid(id: java.util.UUID)

GET /search controllers.SearchController.searchForm
POST /search controllers.SearchController.search
GET /assets/*file controllers.Assets.at(path = "/public", file)

0 comments on commit 3f10ffb

Please sign in to comment.