contrib/benitlux: use nitrestful to expose social network services to clients
authorAlexis Laferrière <alexis.laf@xymus.net>
Fri, 1 Apr 2016 15:54:16 +0000 (11:54 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Tue, 5 Apr 2016 12:51:17 +0000 (08:51 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

contrib/benitlux/.gitignore
contrib/benitlux/Makefile
contrib/benitlux/src/benitlux_controller.nit
contrib/benitlux/src/benitlux_web.nit

index 2ccbe7b..562fb6d 100644 (file)
@@ -1,4 +1,4 @@
-src/benitlux_serial.nit
+src/benitlux_restful.nit
 *.db
 *.email
 benitlux_corrections.txt
index be1543b..c14d648 100644 (file)
@@ -1,19 +1,19 @@
-all: server
+SERVER ?= localhost:8080
 
-pre-build: src/benitlux_serial.nit
+all: server
 
+server: bin/benitlux_daily bin/benitlux_web
 bin/benitlux_daily: $(shell ../../bin/nitls -M src/benitlux_daily.nit)
        mkdir -p bin/
        ../../bin/nitc -o $@ src/benitlux_daily.nit
 
-bin/benitlux_web: $(shell ../../bin/nitls -M src/benitlux_web.nit)
+bin/benitlux_web: $(shell ../../bin/nitls -M src/benitlux_web.nit) src/benitlux_restful.nit
        mkdir -p bin/
-       ../../bin/nitc -o $@ src/benitlux_web.nit
-
-server: bin/benitlux_daily bin/benitlux_web
+       ../../bin/nitc -o $@ src/benitlux_web.nit -D iface=$(SERVER)
 
-src/benitlux_serial.nit:
-       ../../bin/nitserial -o $@ src/benitlux_web.nit
+pre-build: src/benitlux_restful.nit
+src/benitlux_restful.nit: $(shell ../../bin/nitls -M src/benitlux_controller.nit)
+       ../../bin/nitrestful -o $@ src/benitlux_controller.nit
 
 # ---
 # Report
index 806b42e..b6562bd 100644 (file)
 module benitlux_controller
 
 import nitcorn
+import nitcorn::restful
 private import json::serialization
 
 import benitlux_model
 import benitlux_db
 import benitlux_view
+import benitlux_social
 
+# Server action for REST or Web, for a given location
 abstract class BenitluxAction
        super Action
 
@@ -83,12 +86,248 @@ class BenitluxSubscriptionAction
        end
 end
 
-# RESTful interface to compare beer offer between given dates
-#
-# Expects request in the format of `since/2014-07-24`, will replay with a
-# `BeerEvents` serialized to Json with the necessary meta-data to be deserialized.
+# RESTful interface for the client app
 class BenitluxRESTAction
        super BenitluxAction
+       super RestfulAction
+
+       # Sign up a new user
+       #
+       # signup?name=a&pass=b&email=c -> LoginResult | BenitluxError
+       fun signup(name, pass, email: String): HttpResponse
+       is restful do
+               if not name.name_is_ok then
+                       var error = new BenitluxError("Invalid username")
+                       return new HttpResponse.ok(error)
+               end
+
+               if not pass.pass_is_ok then
+                       var error = new BenitluxError("Invalid password")
+                       return new HttpResponse.ok(error)
+               end
+
+               # Query DB
+               var db = new DB.open(db_path)
+               var error_message = db.signup(name, pass, email)
+
+               var object: nullable Serializable
+               if error_message == null then
+                       object = db.login(name, pass)
+               else
+                       object = new BenitluxError(error_message)
+               end
+               db.close
+
+               if object == null then
+                       # There was an error in the call to login
+                       return new HttpResponse.server_error
+               end
+
+               # It went ok, may or may not be signed up
+               return new HttpResponse.ok(object)
+       end
+
+       # Attempt to login
+       #
+       # login?name=a&pass=b -> LoginResult | BenitluxError
+       fun login(name, pass: String): HttpResponse
+       is restful do
+               var db = new DB.open(db_path)
+               var log: nullable Serializable = db.login(name, pass)
+               db.close
+
+               if log == null then log = new BenitluxError("Login Failed", "Invalid username and password combination.")
+
+               return new HttpResponse.ok(log)
+       end
+
+       # Search a user
+       #
+       # search?token=b&query=a&offset=0 -> Array[UserAndFollowing] | BenitluxError
+       fun search(token: nullable String, query: String): HttpResponse
+       is restful do
+               var db = new DB.open(db_path)
+               var user_id = db.token_to_id(token)
+               var users = db.search_users(query, user_id)
+               db.close
+
+               if users == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(users)
+       end
+
+       # List available beers
+       #
+       # list?token=a[&offset=0&count=1] -> Array[BeerAndRatings] | BenitluxError
+       fun list(token: nullable String): HttpResponse
+       is restful do
+
+               # Query DB
+               var db = new DB.open(db_path)
+               var user_id = db.token_to_id(token)
+               var list = db.list_beers_and_rating(user_id)
+               db.close
+
+               if list == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(list)
+       end
+
+       # Post a review of `beer`
+       #
+       # review?token=a&beer=b&rating=0 -> true | BenitluxError
+       fun review(token: String, rating, beer: Int): HttpResponse
+       is restful do
+               # Query DB
+               var db = new DB.open(db_path)
+               var user_id = db.token_to_id(token)
+
+               if user_id == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+
+               db.post_review(user_id, beer, rating, "")
+               db.close
+
+               return new HttpResponse.ok(true)
+       end
+
+       # Set whether user of `token` follows `user_to`, by default set as follow
+       #
+       # follow?token=a&user_to=0 -> true | BenitluxError
+       fun follow(token: String, user_to: Int, follow: nullable Bool): HttpResponse
+       is restful do
+
+               # Query DB
+               var db = new DB.open(db_path)
+               var user = db.token_to_id(token)
+
+               if user == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+
+               if follow or else true then
+                       db.add_followed(user, user_to)
+               else db.remove_followed(user, user_to)
+
+               db.close
+
+               return new HttpResponse.ok(true)
+       end
+
+       # List followers of the user of `token`
+       #
+       # followers?token=a -> Array[UserAndFollowing] | BenitluxError | BenitluxError
+       fun followers(token: String): HttpResponse
+       is restful do
+               # Query DB
+               var db = new DB.open(db_path)
+               var user = db.token_to_id(token)
+               if user == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+               var users = db.followers(user)
+               db.close
+
+               if users == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(users)
+       end
+
+       # List users followed by the user of `token`
+       #
+       # followed?token=a -> Array[UserAndFollowing] | BenitluxError
+       fun followed(token: String): HttpResponse
+       is restful do
+               # Query DB
+               var db = new DB.open(db_path)
+               var user = db.token_to_id(token)
+               if user == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+               var users = db.followed(user)
+               db.close
+
+               if users == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(users)
+       end
+
+       # List friends of the user of `token`
+       #
+       # friends?token=a -> Array[UserAndFollowing] | BenitluxError
+       fun friends(token: String, n: nullable Int): HttpResponse
+       is restful do
+               # Query DB
+               var db = new DB.open(db_path)
+               var user = db.token_to_id(token)
+               var users = db.friends(user, n)
+               db.close
+
+               if users == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(users)
+       end
+
+       # Check user in or out
+       #
+       # checkin?token=a -> true | BenitluxError
+       fun checkin(token: String, is_in: nullable Bool): HttpResponse
+       is restful do
+               var db = new DB.open(db_path)
+               var id = db.token_to_id(token)
+               if id == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+
+               # Register in DB
+               db.checkin(id, is_in or else true)
+
+               # Update followed_followers
+               var common_followers = db.followed_followers(id)
+               db.close
+
+               return new HttpResponse.ok(true)
+       end
+
+       # List users currently checked in among friends of the user of `token`
+       #
+       # checkedin?token=a -> Array[UserAndFollowing]
+       fun checkedin(token: String): HttpResponse
+       is restful do
+               var db = new DB.open(db_path)
+               var user_id = db.token_to_id(token)
+               if user_id == null then
+                       db.close
+                       return new HttpResponse.invalid_token
+               end
+               var report = db.checkedin_followed_followers(user_id)
+               db.close
+
+               if report == null then return new HttpResponse.server_error
+               return new HttpResponse.ok(report)
+       end
+
+       # List beer changes since `date` with information in relation to the user of `token`
+       #
+       # since?token=a&date=date -> BeerEvents
+       fun since(token, date: nullable String): HttpResponse
+       is restful do
+               # Query DB
+               var db = new DB.open(db_path)
+               var user_id = db.token_to_id(token)
+               var list = db.list_beers_and_rating(user_id, date)
+               db.close
+
+               if list == null then return new HttpResponse.server_error
+
+               return new HttpResponse.ok(list)
+       end
 
        redef fun answer(request, turi)
        do
@@ -124,6 +363,9 @@ class BenitluxRESTAction
        end
 end
 
+# ---
+# Misc services
+
 redef class Text
        # Rewrite the date represented by `self` in the format expected by SQLite
        private fun std_date: String
@@ -141,3 +383,37 @@ redef class Text
                return "{y}-{m}-{d}"
        end
 end
+
+redef class HttpResponse
+
+       # Respond with `data` in Json and a code 200
+       init ok(data: Serializable)
+       do
+               init 200
+               body = data.to_json_string
+       end
+
+       # Respond with a `BenitluxError` in JSON and a code 403
+       init invalid_token
+       do
+               init 403
+               var error = new BenitluxTokenError("Forbidden", "Invalid or outdated token.")
+               body = error.to_json_string
+       end
+
+       # Respond with a `BenitluxError` in JSON and a code 400
+       init bad_request
+       do
+               init 400
+               var error = new BenitluxError("Bad Request", "Application error, or it needs to be updated.")
+               body = error.to_json_string
+       end
+
+       # Respond with a `BenitluxError` in JSON and a code 500
+       init server_error
+       do
+               init 500
+               var error = new BenitluxError("Internal Server Error", "Server error, try again later.")
+               body = error.to_json_string
+       end
+end
index 9739241..302b5de 100644 (file)
@@ -20,6 +20,7 @@ module benitlux_web
 import benitlux_model
 import benitlux_view
 import benitlux_controller
+import benitlux_restful
 
 # Listening interface
 fun iface: String do return "localhost:8080"