abstract class BenitluxAction
super Action
- # Path to the database
- var db_path = "benitlux_sherbrooke.db"
+ # Database used for both the mailing list and the social network
+ var db: BenitluxDB
# Path to the storage of the last email sent
var sample_email_path = "benitlux_sherbrooke.email"
template.message_level = "success"
template.message_content = "Subscription successful!"
- var db = new DB.open(db_path)
db.subscribe email
- db.close
else if unsub then
template.message_level = "warning"
template.message_content = "You've been unsubscribed."
- var db = new DB.open(db_path)
db.unsubscribe email
- db.close
end
end
# signup?name=a&pass=b&email=c -> LoginResult | BenitluxError
fun signup(name, pass, email: String): HttpResponse
is restful do
+ # Validate input
if not name.name_is_ok then
var error = new BenitluxError("Invalid username")
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
else
object = new BenitluxError(error_message)
end
- db.close
if object == null then
# There was an error in the call 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
+ # Is `token` valid?
+ #
+ # check_token?token=a -> true | BenitluxError
+ fun check_token(token: String): HttpResponse
+ is restful do
+ var user_id = db.token_to_id(token)
+ if user_id == null then return new HttpResponse.invalid_token
+ return new HttpResponse.ok(true)
+ 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)
# 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)
# 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
+ if user_id == null then return new HttpResponse.invalid_token
db.post_review(user_id, beer, rating, "")
- db.close
return new HttpResponse.ok(true)
end
# 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 user == null then return new HttpResponse.invalid_token
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
# 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 user == null then return new HttpResponse.invalid_token
+ var users = db.followers(user)
if users == null then return new HttpResponse.server_error
return new HttpResponse.ok(users)
# 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 user == null then return new HttpResponse.invalid_token
+ var users = db.followed(user)
if users == null then return new HttpResponse.server_error
return new HttpResponse.ok(users)
# 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)
# 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
+ if id == null then return new HttpResponse.invalid_token
# Register in DB
db.checkin(id, is_in or else true)
# Update followed_followers
var common_followers = db.followed_followers(id)
- db.close
+
+ # Sent push notifications to connected reciprocal friends
+ if common_followers != null then
+ for friend in common_followers do
+ var conn = push_connections.get_or_null(friend.id)
+ if conn != null then
+ push_connections.keys.remove friend.id
+ if not conn.closed then
+ var report = db.checkedin_followed_followers(friend.id)
+ var response = if report == null then
+ new HttpResponse.server_error
+ else new HttpResponse.ok(report)
+ conn.respond response
+ conn.close
+ end
+ end
+ end
+ end
return new HttpResponse.ok(true)
end
# 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 user_id == null then return new HttpResponse.invalid_token
+ var report = db.checkedin_followed_followers(user_id)
if report == null then return new HttpResponse.server_error
return new HttpResponse.ok(report)
end
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
# ---
+# Push notification
+
+# Benitlux push notification interface
+class BenitluxPushAction
+ super BenitluxAction
+
+ # Intercept the full answer to set aside the connection and complete it later
+ redef fun prepare_respond_and_close(request, turi, connection)
+ do
+ var token = request.string_arg("token")
+
+ var user = db.token_to_id(token)
+ if user == null then
+ # Report errors right away
+ var response = new HttpResponse.invalid_token
+ connection.respond response
+ connection.close
+ return
+ end
+
+ # Set aside the connection
+ push_connections[user] = connection
+ end
+end
+
+redef class Sys
+ # Connections left open for a push notification, organized per user id
+ private var push_connections = new Map[Int, HttpServer]
+end
+
+# ---
+# Administration
+
+# Path to the secret used to authenticate admin requests
+fun secret_path: String do return "benitlux.secret"
+
+# Services reserved to administrators
+class BenitluxAdminAction
+ super BenitluxAction
+ super RestfulAction
+
+ private fun server_secret: String do return secret_path.to_path.read_all
+
+ # Trigger sending daily menu to connected clients
+ #
+ # This should usually be called by an external cron program.
+ # send_daily_updates?secret=shared_secret -> true | BenitluxError
+ fun send_daily_updates(secret: nullable String): HttpResponse
+ is restful do
+ # Check secrets
+ var server_secret = server_secret
+ if server_secret.is_empty then
+ print_error "The admin interface needs a secret at '{secret_path}'"
+ return new HttpResponse.server_error
+ end
+
+ if server_secret != secret then
+ return new HttpResponse.invalid_token
+ end
+
+ # Load beer menu
+ var list = db.list_beers_and_rating
+ if list == null then return new HttpResponse.server_error
+
+ var msg = new DailyNotification(list)
+
+ # Broadcast updates
+ for conn in push_connections.values.to_a do
+ if not conn.closed then
+ conn.respond new HttpResponse.ok(msg)
+ conn.close
+ end
+ end
+ push_connections.clear
+
+ return new HttpResponse.ok(true)
+ end
+
+ redef fun answer(request, turi) do return new HttpResponse.bad_request
+end
+
+# ---
# Misc services
redef class Text