Skip to content

Commit dba1da2

Browse files
committed
Adding some code.
0 parents  commit dba1da2

14 files changed

+308
-0
lines changed

.gitignore

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Some project specifics
2+
.env
3+
spark-warehouse/
4+
metastore_db/
5+
experimental/
6+
NOTES.md
7+
s3.*
8+
per_*/
9+
*.jar
10+
*.zip
11+
data/
12+
creds.*
13+
dump/
14+
hack/
15+
16+
*.class
17+
*.log
18+
19+
dist/*
20+
target/
21+
lib_managed/
22+
src_managed/
23+
project/boot/
24+
project/plugins/project/
25+
26+
.history
27+
.cache
28+
.lib
29+
30+
.scala_dependencies
31+
.worksheet
32+
33+
!lib/*.jar

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# bencinmonitor / d2
2+
3+
High-performance API for [bencinmonitor.si].
4+
5+
## Docker
6+
7+
> TODO: Add instructions here.
8+
9+
10+
## REST Endpoints
11+
12+
### GET /stations
13+
14+
Returns list of stations and prices.
15+
16+
Supported `GET` parameters
17+
18+
- `near`[`String`] - Address or location "name". `near` is geo-coded into coordinates then are then used as starting point.
19+
- `at`[`Double,Double`] - Coordinates ther are used for starting point.
20+
- `maxDistance`[`Int`] - Maximal distance in meters around `near` or `at`. Default is `10000` meters.
21+
- `limit`[`Int`] - Maximum number of records that API returns. Default is `10`.
22+
23+
24+
# Author
25+
26+
- [Oto Brglez](https://github.com/otobrglez)
27+
28+
[bencinmonitor.si]: http://bencinmonitor.si

activator.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
name=d2
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package controllers
2+
3+
import javax.inject._
4+
5+
import models.Location
6+
import play.api._
7+
import play.api.mvc._
8+
import play.api.libs.json._
9+
import play.api.libs.json.Reads._
10+
// import play.api.libs.functional.syntax._
11+
import play.api.libs.ws._
12+
13+
import scala.concurrent.{ExecutionContext, Future}
14+
import scala.concurrent.duration._
15+
import play.modules.reactivemongo._
16+
import reactivemongo.play.json.collection._
17+
import akka.actor.ActorSystem
18+
import akka.stream.ActorMaterializer
19+
import akka.stream.scaladsl._
20+
import akka.util.ByteString
21+
import reactivemongo.play.json._
22+
import reactivemongo.bson._
23+
import reactivemongo.bson.{BSONDocument, BSONDocumentReader}
24+
import reactivemongo.api.collections.bson.BSONCollection
25+
26+
import models.Station
27+
import models.Station._
28+
29+
@Singleton
30+
class StationController @Inject()(val reactiveMongoApi: ReactiveMongoApi, val ws: WSClient)(implicit exec: ExecutionContext) extends Controller with MongoController with ReactiveMongoComponents {
31+
lazy val stations = database.map(_.collection("stations"))
32+
33+
def geocode(rawAddress: Option[String] = None, timeout: FiniteDuration = 2.seconds): Future[Seq[Double]] = {
34+
if (rawAddress.isEmpty) return Future(Seq.empty[Double])
35+
36+
val wsRequest = ws.url(s"http://maps.googleapis.com/maps/api/geocode/json")
37+
.withHeaders(ACCEPT -> "applicaton/json")
38+
.withQueryString("sensor" -> "false")
39+
.withQueryString("address" -> rawAddress.get)
40+
.withRequestTimeout(timeout)
41+
42+
wsRequest.get().map { wsResponse =>
43+
(wsResponse.json \ "status").as[String] match {
44+
case "OK" =>
45+
val location = (wsResponse.json \ "results") (0) \ "geometry" \ "location"
46+
Seq[Double]((location \ "lng").as[Double], (location \ "lat").as[Double])
47+
case _ => Seq.empty[Double]
48+
}
49+
}
50+
}
51+
52+
def geocode_it(address: String) = Action.async {
53+
for {location <- geocode(Some(address))} yield Ok(Json.toJson(BSONDocument("coordinates" -> location)))
54+
}
55+
56+
def listStations(at: Seq[Double] = Seq.empty[Double], limit: Int, maxDistance: Int): Future[List[Station]] = {
57+
stations.flatMap {
58+
var query = BSONDocument("company" -> "petrol")
59+
60+
if (at.nonEmpty) {
61+
query = query.add(BSONDocument("loc" -> BSONDocument("$near" -> BSONDocument(
62+
"$geometry" -> BSONDocument("$type" -> "Point", "coordinates" -> at),
63+
"$maxDistance" -> maxDistance)
64+
)))
65+
}
66+
67+
var projection = BSONDocument("key" -> 1, "address" -> 1, "loc" -> 1, "updated_at" -> 1, "scraped_url" -> 1)
68+
69+
projection = projection.add("prices" -> "1")
70+
71+
_.find(query, projection).cursor[Station]().collect[List](limit)
72+
}
73+
}
74+
75+
def index(near: Option[String] = None, at: Option[String], limit: Int, maxDistance: Int) = Action.async {
76+
val now = System.nanoTime
77+
val atLocation = at.fold(Seq.empty[Double])(_.split(",").map(_.toDouble))
78+
79+
val composition: Future[List[Station]] = if (near.isEmpty && atLocation.nonEmpty) {
80+
listStations(atLocation, limit, maxDistance)
81+
} else if (near.nonEmpty) {
82+
geocode(near).flatMap(listStations(_, limit, maxDistance))
83+
} else {
84+
listStations(limit = limit, maxDistance = maxDistance)
85+
}
86+
87+
composition.map { stations =>
88+
Ok(Json.toJson(BSONDocument(
89+
"stations" -> stations,
90+
"status" -> "ok",
91+
"executed_in" -> ((System.nanoTime - now).asInstanceOf[Double] / 1000000000)
92+
)))
93+
}
94+
}
95+
}

app/extra/ExtraBSONHandlers.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package extra
2+
3+
import reactivemongo.bson._
4+
5+
object ExtraBSONHandlers extends ExtraBSONHandlers
6+
7+
trait ExtraBSONHandlers {
8+
implicit def MapBSONReader[T](implicit reader: BSONReader[_ <: BSONValue, T]): BSONDocumentReader[Map[String, T]] =
9+
new BSONDocumentReader[Map[String, T]] {
10+
def read(doc: BSONDocument): Map[String, T] = {
11+
doc.elements.collect {
12+
case (key, value) => value.seeAsOpt[T](reader) map {
13+
ov => (key, ov)
14+
}
15+
}.flatten.toMap
16+
}
17+
}
18+
19+
implicit def MapBSONWriter[T](implicit writer: BSONWriter[T, _ <: BSONValue]): BSONDocumentWriter[Map[String, T]] = new BSONDocumentWriter[Map[String, T]] {
20+
def write(doc: Map[String, T]): BSONDocument = {
21+
BSONDocument(doc.toTraversable map (t => (t._1.replace("-", "_"), writer.write(t._2))))
22+
}
23+
}
24+
}

app/models/Location.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package models
2+
3+
import play.api.libs.json.{Reads, __}
4+
import play.api.libs.functional.syntax._
5+
6+
case class Location(locationType: String, coordinates: Seq[Double])
7+
8+
object Location {
9+
10+
import reactivemongo.bson._
11+
12+
implicit val locationReads: Reads[Location] = (
13+
(__ \ "type").read[String] and (__ \ "coordinates").read[Seq[Double]]
14+
) (Location.apply _)
15+
16+
implicit object LocationWriter extends BSONDocumentWriter[Location] {
17+
def write(location: Location): BSONDocument = BSONDocument(
18+
"type" -> location.locationType,
19+
"coordinates" -> location.coordinates
20+
)
21+
}
22+
}

app/models/Station.scala

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package models
2+
3+
import play.api.libs.json._
4+
import play.api.libs.json.Reads._
5+
import play.api.libs.functional.syntax._
6+
7+
case class Station(key: String, address: String, location: Location, prices: Map[String, Double])
8+
9+
object Station {
10+
11+
import reactivemongo.bson._
12+
import extra.ExtraBSONHandlers._
13+
14+
implicit val locationWrites = Location.LocationWriter
15+
16+
implicit val stationReads: Reads[Station] = (
17+
(__ \ "key").read[String] and
18+
(__ \ "address").read[String] and
19+
(__ \ "loc").read[Location] and
20+
(__ \ "prices").read[Map[String, Double]]
21+
) (Station.apply _)
22+
23+
implicit object StationWriter extends BSONDocumentWriter[Station] {
24+
def write(station: Station): BSONDocument = {
25+
BSONDocument(
26+
"key" -> station.key,
27+
"address" -> station.address,
28+
"loc" -> station.location,
29+
"prices" -> station.prices.map(pair => BSONDocument(
30+
"type" -> pair._1.replace("-", "_"), "price" -> pair._2)
31+
)
32+
)
33+
}
34+
}
35+
36+
}
37+

app/utils/Errors.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package utils
2+
3+
import play.api.data.validation.ValidationError
4+
import play.api.libs.json.JsPath
5+
6+
object Errors {
7+
8+
/**
9+
* Small utility to show the errors as a string
10+
*/
11+
def show(errors: Seq[(JsPath, Seq[ValidationError])]): String = {
12+
errors.map {
13+
case (path, e) => path.toString() + " : " + e.map(_.toString).mkString(" ")
14+
}.mkString("\n")
15+
}
16+
17+
}

build.sbt

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name := """d2"""
2+
3+
version := "1.0-SNAPSHOT"
4+
5+
lazy val root = (project in file(".")).enablePlugins(PlayScala)
6+
7+
scalaVersion := "2.11.8"
8+
9+
// routesGenerator := InjectedRoutesGenerator
10+
11+
libraryDependencies ++= Seq(
12+
ws,
13+
"org.reactivemongo" %% "play2-reactivemongo" % "0.11.14"
14+
)
15+
16+
resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases"
17+
resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
18+
19+
scalacOptions in ThisBuild ++= Seq(
20+
"-feature",
21+
"-language:postfixOps",
22+
"-Xfuture",
23+
"-language:_",
24+
"-deprecation",
25+
"-unchecked"
26+
)

conf/application.conf

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
mongodb.uri = "mongodb://0.0.0.0:27017/bm"
2+
3+
play.modules.enabled += "play.modules.reactivemongo.ReactiveMongoModule"
4+
5+
mongo-async-driver {
6+
akka {
7+
loglevel = WARNING
8+
}
9+
}
10+

conf/routes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
GET /stations controllers.StationController.index(near: Option[String], at: Option[String], limit: Int ?= 10, maxDistance: Int ?= 10000)
2+
GET /geocode controllers.StationController.geocode_it(address: String)

etc/mongod.conf

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Store data in /usr/local/var/mongodb instead of the default /data/db
2+
dbpath = tmp/mongodb
3+
4+
# Append logs to /usr/local/var/log/mongodb/mongo.log
5+
# logpath = tmp/mongo.log
6+
# logappend = true
7+
8+
# Only accept local connections
9+
bind_ip = 127.0.0.1

project/build.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=0.13.11

project/plugins.sbt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// The Play plugin
2+
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.1")
3+

0 commit comments

Comments
 (0)