Skip to content

Commit

Permalink
Merge pull request #27 from troger/message-rating
Browse files Browse the repository at this point in the history
Message rating API

Waiting for clients to use it
  • Loading branch information
akervern committed Nov 17, 2011
2 parents 38dbcea + 020ea86 commit bb779f3
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 21 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@ The administration interface is done using [Play 2.0](http://www.playframework.o

The Web services used by the mobile applications are exposed through [Unfiltered](https://github.com/unfiltered/unfiltered).

### Administration

#### Install mongoDB
### MongoDB

To install it on your system, see [here](http://www.mongodb.org/display/DOCS/Quickstart).

#### Casbah

We are using the non released version 3.0.0-SNAPSHOT due to some bugs fixed in this version.

Before running the Administration or API app, you need to install on you local repository the 3.0.0-SNAPSHOT version of Casbah.

$ git clone git://github.com/mongodb/casbah.git
$ cd casbah
$ sbt publish-local

### Administration

#### Install Play 2.0

See the **Building from sources** section [here](https://github.com/playframework/Play20/wiki/Installing).
Expand Down
26 changes: 25 additions & 1 deletion backend/sosmessage-admin/app/controllers/Messages.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package controllers

import _root_.net.liftweb.json.JsonParser._
import play.api._
import data._
import play.api.mvc._
Expand Down Expand Up @@ -45,10 +46,33 @@ object Messages extends Controller {
val q = MongoDBObject("categoryId" -> new ObjectId(selectedCategoryId), "state" -> "approved")
val messages = messagesCollection.find(q).sort(messageOrder).foldLeft(List[DBObject]())((l, a) =>
a :: l
).reverse
).map(addRating(_))reverse

Ok(views.html.messages.index(categories, selectedCategoryId, messages, messageForm))
}

def addRating(message: DBObject) = {
val r = """
function(doc, out) {
for (var prop in doc.ratings) {
out.count++;
out.total += doc.ratings[prop];
}
}
"""
val f = """
function(out) {
out.avg = out.total / out.count;
}
"""
val rating = messagesCollection.group(MongoDBObject("ratings" -> 1),
MongoDBObject("_id" -> message.get("_id")), MongoDBObject("count" -> 0, "total" -> 0), r, f)
val j = parse(rating.mkString)
message.put("rating", j \ "avg" values)
message.put("ratingCount", (j \ "count" values).asInstanceOf[Double].toInt)
message
}

def save(selectedCategoryId: String) = Action { implicit request =>
messageForm.bindFromRequest().fold(
f => {
Expand Down
1 change: 1 addition & 0 deletions backend/sosmessage-admin/app/views/main.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<script src="@routes.Assets.at("javascripts/bootstrap-scrollspy.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("javascripts/bootstrap-tabs.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("javascripts/bootstrap-alerts.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("javascripts/jquery.raty.min.js")" type="text/javascript"></script>
<script src="@routes.Assets.at("javascripts/sosmessage.js")" type="text/javascript"></script>
</head>

Expand Down
7 changes: 6 additions & 1 deletion backend/sosmessage-admin/app/views/messages/index.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ <h1>Messages</h1>
<tr>
<th>Text</th>
<th>Created At</th>
<th>Rating</th>
<th class="delete-message-col"></th>
</tr>
</thead>
Expand All @@ -56,12 +57,16 @@ <h1>Messages</h1>
@message.get("text")
</pre>
</td>
<td>
<td>
@{
val df = java.text.DateFormat.getDateTimeInstance
df.format(message.get("createdAt"))
}
</td>
<td>
<div data-rating-start="@message.get("rating")"
data-rating-count="@message.get("ratingCount")"></div> (@message.get("ratingCount") ratings)
</td>
<td class="delete-message-col">
<form action="@routes.Messages.delete(selectedCategoryId, message.get("_id").toString)" method="POST"
style="margin-bottom:0">
Expand Down
4 changes: 3 additions & 1 deletion backend/sosmessage-admin/project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ object ApplicationBuild extends Build {
val appVersion = "1.0"

val appDependencies = Seq(
"com.mongodb.casbah" %% "casbah" % "2.1.5-1"
//"com.mongodb.casbah" %% "casbah" % "2.1.5-1",
"com.mongodb.casbah" %% "casbah" % "3.0.0-SNAPSHOT",
"net.liftweb" %% "lift-json" % "2.4-M4"
)

val main = PlayProject(appName, appVersion, appDependencies).settings(defaultScalaSettings:_*).settings(
Expand Down
Binary file added backend/sosmessage-admin/public/images/star-half.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added backend/sosmessage-admin/public/images/star-off.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added backend/sosmessage-admin/public/images/star-on.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions backend/sosmessage-admin/public/javascripts/jquery.raty.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions backend/sosmessage-admin/public/javascripts/sosmessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,23 @@ $(document).ready(function() {
window.location.href = $(this).attr("sosmessage-redirect-url");
});
});

// initialize the ratings
$('[data-rating-start]').each(function() {
var ratingCount = $(this).attr("data-rating-count")
if (ratingCount > 0) {
$(this).raty({
path: "/admin/assets/images/",
half: true,
readOnly: true,
start: $(this).attr("data-rating-start")
});
} else {
$(this).raty({
path: "/admin/assets/images/",
readOnly: true
});
}
});

});
3 changes: 2 additions & 1 deletion backend/sosmessage-api/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ libraryDependencies ++= Seq(
"net.databinder" %% "unfiltered-filter" % "0.5.0",
"net.databinder" %% "unfiltered-jetty" % "0.5.0",
"net.databinder" %% "unfiltered-json" % "0.5.0",
"com.mongodb.casbah" %% "casbah" % "2.1.5-1",
//"com.mongodb.casbah" %% "casbah" % "2.1.5-1",
"com.mongodb.casbah" %% "casbah" % "3.0.0-SNAPSHOT",
"ch.qos.logback" % "logback-classic" % "0.9.28",
"net.databinder" %% "unfiltered-spec" % "0.5.0" % "test"
)
65 changes: 51 additions & 14 deletions backend/sosmessage-api/src/main/scala/SosMessage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import com.mongodb.casbah.MongoConnection
import net.liftweb.json.JsonAST._
import net.liftweb.json.JsonDSL._
import net.liftweb.json.Printer._
import net.liftweb.json.JsonParser._
import com.mongodb.casbah.commons.MongoDBObject
import org.bson.types.ObjectId
import com.mongodb.casbah.Imports._
import com.mongodb.casbah.Implicits._
import java.util.Date

class SosMessage extends unfiltered.filter.Plan {
Expand All @@ -38,20 +38,43 @@ class SosMessage extends unfiltered.filter.Plan {
case GET(Path(Seg("api" :: "v1" :: "category" :: id :: "messages" :: Nil))) =>
val messageOrder = MongoDBObject("createdAt" -> -1)
val q = MongoDBObject("categoryId" -> new ObjectId(id), "state" -> "approved")
val messages = messagesCollection.find(q).sort(messageOrder).foldLeft(List[JValue]())((l, a) =>
val keys = MongoDBObject("category" -> 1, "categoryId" -> 1, "text" -> 1, "createdAt" -> 1)
val messages = messagesCollection.find(q, keys).sort(messageOrder).foldLeft(List[JValue]())((l, a) =>
messageToJSON(a) :: l
).reverse
val json = ("count", messages.size) ~ ("items", messages)
JsonContent ~> ResponseString(pretty(render(json)))

case GET(Path(Seg("api" :: "v1" :: "category" :: id :: "message" :: Nil))) =>
val q = MongoDBObject("categoryId" -> new ObjectId(id), "state" -> "approved")
val count = messagesCollection.find(q).count
val skip = random.nextInt(count)

val message = messagesCollection.find(q).limit(-1).skip(skip).next()
val json = messageToJSON(message)
JsonContent ~> ResponseString(pretty(render(json)))
val count = messagesCollection.find(q, MongoDBObject("_id")).count
val skip = random.nextInt(if (count <= 0) 1 else count)

val keys = MongoDBObject("category" -> 1, "categoryId" -> 1, "text" -> 1, "createdAt" -> 1)
val messages = messagesCollection.find(q, keys).limit(-1).skip(skip)
if (!messages.isEmpty) {
val message = messages.next()

val r = """
function(doc, out) {
for (var prop in doc.ratings) {
out.count++;
out.total += doc.ratings[prop];
}
}
"""
val f = """
function(out) {
out.avg = out.total / out.count;
}
"""
val rating = messagesCollection.group(MongoDBObject("ratings" -> 1),
MongoDBObject("_id" -> message.get("_id")), MongoDBObject("count" -> 0, "total" -> 0), r, f)
val json = messageToJSON(message, Some(parse(rating.mkString)))
JsonContent ~> ResponseString(pretty(render(json)))
} else {
NoContent
}

case req @ POST(Path(Seg("api" :: "v1" :: "category" :: categoryId :: "message" :: Nil))) =>
categoriesCollection.findOne(MongoDBObject("_id" -> new ObjectId(categoryId))).map { category =>
Expand All @@ -68,6 +91,14 @@ class SosMessage extends unfiltered.filter.Plan {
}
NoContent

case req @ POST(Path(Seg("api" :: "v1" :: "message" :: messageId :: "rate" :: Nil))) =>
val Params(form) = req
val uid = form("uid")(0)
val rating = if(form("rating")(0).toInt > 5) 5 else form("rating")(0).toInt
val key = "ratings." + uid.replaceAll ("\\.", "-")
messagesCollection.update(MongoDBObject("_id" -> new ObjectId(messageId)), $set (key -> rating), false, false)
NoContent

// case GET(Path(Seg("api" :: "v1" :: "category" :: id :: "randomMessage" :: Nil))) =>
// val random = scala.math.random
// println(random)
Expand All @@ -84,13 +115,19 @@ class SosMessage extends unfiltered.filter.Plan {
// }
}

private def messageToJSON(o: DBObject) = {
("id", o.get("_id").toString) ~
private def messageToJSON(message: DBObject, rating: Option[JValue] = None) = {
val json = ("id", message.get("_id").toString) ~
("type", "message") ~
("category", o.get("category").toString) ~
("categoryId", o.get("categoryId").toString) ~
("text", o.get("text").toString) ~
("createdAt", o.get("createdAt").toString)
("category", message.get("category").toString) ~
("categoryId", message.get("categoryId").toString) ~
("text", message.get("text").toString) ~
("createdAt", message.get("createdAt").toString)

rating match {
case None => json ~ ("rating" -> 0) ~ ("ratingCount" -> 0)
case Some(r) =>
json ~ ("rating" -> rating \ "avg") ~ ("ratingCount" -> rating \ "count")
}
}

private def categoryToJSON(o: DBObject) = {
Expand Down

0 comments on commit bb779f3

Please sign in to comment.