package com.hellolift.api
import net.liftweb.http._
import com.hellolift.model._
import scala.xml._
class BlogAPI(val request: RequestState)
extends SimpleController {
def index: ResponseIt = {
// To be filled in.
}
def get(id: String): ResponseIt = {
// To be filled in.
}
def create: ResponseIt = {
// To be filled in.
}
def delete(id: String): ResponseIt = {
// To be filled in.
}
}
GET /api/$itemid
def index: ResponseIt {
request match {
case RequestState(ParsePath("api" :: itemid :: Nil,
_, _), GetRequest, _, _) => get(itemid)
}
}
def get(id: String): ResponseIt = {
val it = Item.find(By(Item.id, Helpers.toLong(id)),
By(Item.author, user)
it match {
case Full(item) => AtomResponse(item.toAtom())
case Empty => NotFoundResponse()
}
}
scala.xml
makes it really easy to output valid XML.
def toAtom = {
val id = "http://example.com/api/" + this.id
val formatter = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
val updated = formatter.format(this.lastedited.is)
<entry xmlns="http://www.w3.org/2005/Atom">
<id>{id}</id>
<updated>{updated}</updated>
<author>
<name>{name}</name>
</author>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
{body}
</div>
</content>
</entry>
}
POST /api/create
PostRequest
// the previous pattern matching clause is assumed
// (for space reasons)
def index: ResponseIt = {
request match {
case RequestState(ParsePath("api" :: "create"
:: Nil, _, _), PostRequest, _, _) => create
}
}
POST /api/create
def create: ResponseIt = {
try {
val xml = XML.load(httpRequest.getInputStream())
val body = (xml \\ "content").text
val item = Item.create.author(user).body(body)
item.save
AtomCreatedResponse(item.toAtom)
} catch {
case e: Exception => BadResponse()
}
}
\\
searches the children for the element.user
coming from?DELETE /api/$itemid
def index: ResponseIt = {
request match {
case RequestState(ParsePath("api" :: itemid ::
Nil, _, _), DeleteRequest, _, _)
=> delete(itemid)
}
}
def delete(itemid: String): ResponseIt = {
Item.find(By(Item.id, Helpers.toLong(itemid)),
By(Item.author, user)) match {
case Full(item) => {
item.delete_!
OkResponse()
}
case _ => NotFoundResponse()
}
}
BlogAPI
?Boot.scala
class Boot {
def boot {
val apiDispatcher: LiftRules.DispatchPf = {
case RequestMatcher(r, ParsePath("api" :: _ ::
Nil, _, _), _, _) => api(r, "index")
}
LiftRules.statelessDispatchTable = apiDispatcher
orElse LiftRules.statelessDispatchTable
}
// this doesn't format well for presentations.
private def api
(request: RequestState, methodName: String)
(req: RequestState): Can[ResponseIt] =
createInvoker(methodName,
new BlogAPI(request)).flatMap(_() match {
case Full(ret: ResponseIt) => Full(ret)
case _ => Empty
})
}
api
method is really just boilerplate and we should make it go away in lift so you don't have to think about it.statlessDispatchTable
keeps your api requests from incurring session costs.user
is Answered
// getUser is on the next slide
def authenticated(f: User => ResponseIt) = {
getUser match {
case Full(user) => f(user)
case Empty => UnauthorizedResponse("Our Realm")
}
}
// new and improved
def get(id: String): ResponseIt {
authenticated { user => {
val it = Item.find(By(Item.id, Helpers.toLong(id)),
By(Item.author, user)
it match {
case Full(item) => AtomResponse(item.toAtom())
case Empty => NotFoundResponse()
}
}
}
ResponseIt
.Authorization
header and find
the proper User based on it's contents
private def getUser: Can[User] = {
Can.legacyNullTest(request.request
.getHeader("Authorization")).flatMap(auth => {
val up: List[String] = if (auth.length > 5) {
new String(Base64.decodeBase64(auth.substring(5,
auth.length).getBytes())).split(":").toList
} else {
Nil
}
if (up.length < 2) {
Empty
} else {
val email = up(0)
val password = up(1)
User.find(By(User.email, email)) match {
case Full(user) if user.validated &&
user.password.match_?(password) => Full(user)
case _ => Empty
}
}
})}
Base64
is from Apache Commons Codec.