1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Actions for the Web interface of Benitlux
18 module benitlux_controller
21 import nitcorn
::restful
22 private import json
::serialization
27 import benitlux_social
29 # Server action for REST or Web, for a given location
30 abstract class BenitluxAction
33 # Database used for both the mailing list and the social network
36 # Path to the storage of the last email sent
37 var sample_email_path
= "benitlux_sherbrooke.email"
40 # Web interface to subscribe to the mailing list
41 class BenitluxSubscriptionAction
44 redef fun answer
(request
, turi
)
46 var template
= new BenitluxDocument
48 var sub
= request
.post_args
.keys
.has
("sub")
49 var unsub
= request
.all_args
.keys
.has
("unsub")
52 if request
.all_args
.keys
.has
("email") then email
= request
.all_args
["email"].trim
55 if email
.is_empty
or not email
.chars
.has
('@') or not email
.chars
.has
('.') then
56 template
.message_level
= "danger"
57 template
.message_content
= "Invalid email."
58 else if sub
and request
.post_args
.keys
.has
("email") then
59 template
.message_level
= "success"
60 template
.message_content
= "Subscription successful!"
64 template
.message_level
= "warning"
65 template
.message_content
= "You've been unsubscribed."
71 if sample_email_path
.file_exists
then
72 var f
= new FileReader.open
(sample_email_path
)
73 var lines
= new Array[String]
74 for line
in f
.read_all
.split_with
("\n") do if not line
.is_empty
then lines
.add line
76 template
.sample_email_lines
= lines
79 var response
= new HttpResponse(200)
80 response
.body
= template
.write_to_string
85 # RESTful interface for the client app
86 class BenitluxRESTAction
92 # signup?name=a&pass=b&email=c -> LoginResult | BenitluxError
93 fun signup
(name
, pass
, email
: String): HttpResponse
96 if not name
.name_is_ok
then
97 var error
= new BenitluxError("Invalid username")
98 return new HttpResponse.ok
(error
)
101 if not pass
.pass_is_ok
then
102 var error
= new BenitluxError("Invalid password")
103 return new HttpResponse.ok
(error
)
107 var error_message
= db
.signup
(name
, pass
, email
)
109 var object
: nullable Serializable
110 if error_message
== null then
111 object
= db
.login
(name
, pass
)
113 object
= new BenitluxError(error_message
)
116 if object
== null then
117 # There was an error in the call to login
118 return new HttpResponse.server_error
121 # It went ok, may or may not be signed up
122 return new HttpResponse.ok
(object
)
127 # login?name=a&pass=b -> LoginResult | BenitluxError
128 fun login
(name
, pass
: String): HttpResponse
130 var log
: nullable Serializable = db
.login
(name
, pass
)
131 if log
== null then log
= new BenitluxError("Login Failed", "Invalid username and password combination.")
133 return new HttpResponse.ok
(log
)
138 # search?token=b&query=a&offset=0 -> Array[UserAndFollowing] | BenitluxError
139 fun search
(token
: nullable String, query
: String): HttpResponse
141 var user_id
= db
.token_to_id
(token
)
142 var users
= db
.search_users
(query
, user_id
)
143 if users
== null then return new HttpResponse.server_error
145 return new HttpResponse.ok
(users
)
148 # List available beers
150 # list?token=a[&offset=0&count=1] -> Array[BeerAndRatings] | BenitluxError
151 fun list
(token
: nullable String): HttpResponse
153 var user_id
= db
.token_to_id
(token
)
154 var list
= db
.list_beers_and_rating
(user_id
)
155 if list
== null then return new HttpResponse.server_error
157 return new HttpResponse.ok
(list
)
160 # Post a review of `beer`
162 # review?token=a&beer=b&rating=0 -> true | BenitluxError
163 fun review
(token
: String, rating
, beer
: Int): HttpResponse
165 var user_id
= db
.token_to_id
(token
)
166 if user_id
== null then return new HttpResponse.invalid_token
168 db
.post_review
(user_id
, beer
, rating
, "")
170 return new HttpResponse.ok
(true)
173 # Set whether user of `token` follows `user_to`, by default set as follow
175 # follow?token=a&user_to=0 -> true | BenitluxError
176 fun follow
(token
: String, user_to
: Int, follow
: nullable Bool): HttpResponse
178 var user
= db
.token_to_id
(token
)
179 if user
== null then return new HttpResponse.invalid_token
181 if follow
or else true then
182 db
.add_followed
(user
, user_to
)
183 else db
.remove_followed
(user
, user_to
)
185 return new HttpResponse.ok
(true)
188 # List followers of the user of `token`
190 # followers?token=a -> Array[UserAndFollowing] | BenitluxError | BenitluxError
191 fun followers
(token
: String): HttpResponse
193 var user
= db
.token_to_id
(token
)
194 if user
== null then return new HttpResponse.invalid_token
196 var users
= db
.followers
(user
)
197 if users
== null then return new HttpResponse.server_error
199 return new HttpResponse.ok
(users
)
202 # List users followed by the user of `token`
204 # followed?token=a -> Array[UserAndFollowing] | BenitluxError
205 fun followed
(token
: String): HttpResponse
207 var user
= db
.token_to_id
(token
)
208 if user
== null then return new HttpResponse.invalid_token
210 var users
= db
.followed
(user
)
211 if users
== null then return new HttpResponse.server_error
213 return new HttpResponse.ok
(users
)
216 # List friends of the user of `token`
218 # friends?token=a -> Array[UserAndFollowing] | BenitluxError
219 fun friends
(token
: String, n
: nullable Int): HttpResponse
221 var user
= db
.token_to_id
(token
)
222 var users
= db
.friends
(user
, n
)
223 if users
== null then return new HttpResponse.server_error
225 return new HttpResponse.ok
(users
)
228 # Check user in or out
230 # checkin?token=a -> true | BenitluxError
231 fun checkin
(token
: String, is_in
: nullable Bool): HttpResponse
233 var id
= db
.token_to_id
(token
)
234 if id
== null then return new HttpResponse.invalid_token
237 db
.checkin
(id
, is_in
or else true)
239 # Update followed_followers
240 var common_followers
= db
.followed_followers
(id
)
242 # Sent push notifications to connected reciprocal friends
243 if common_followers
!= null then
244 for friend
in common_followers
do
245 var conn
= push_connections
.get_or_null
(friend
.id
)
247 push_connections
.keys
.remove friend
.id
248 if not conn
.closed
then
249 var report
= db
.checkedin_followed_followers
(friend
.id
)
250 var response
= if report
== null then
251 new HttpResponse.server_error
252 else new HttpResponse.ok
(report
)
253 conn
.respond response
260 return new HttpResponse.ok
(true)
263 # List users currently checked in among friends of the user of `token`
265 # checkedin?token=a -> Array[UserAndFollowing]
266 fun checkedin
(token
: String): HttpResponse
268 var user_id
= db
.token_to_id
(token
)
269 if user_id
== null then return new HttpResponse.invalid_token
271 var report
= db
.checkedin_followed_followers
(user_id
)
272 if report
== null then return new HttpResponse.server_error
273 return new HttpResponse.ok
(report
)
276 # List beer changes since `date` with information in relation to the user of `token`
278 # since?token=a&date=date -> BeerEvents
279 fun since
(token
, date
: nullable String): HttpResponse
282 var user_id
= db
.token_to_id
(token
)
283 var list
= db
.list_beers_and_rating
(user_id
, date
)
284 if list
== null then return new HttpResponse.server_error
286 return new HttpResponse.ok
(list
)
289 # Fallback answer on errors
290 redef fun answer
(request
, turi
) do return new HttpResponse.bad_request
296 # Benitlux push notification interface
297 class BenitluxPushAction
300 # Intercept the full answer to set aside the connection and complete it later
301 redef fun prepare_respond_and_close
(request
, turi
, connection
)
303 var token
= request
.string_arg
("token")
305 var user
= db
.token_to_id
(token
)
307 # Report errors right away
308 var response
= new HttpResponse.invalid_token
309 connection
.respond response
314 # Set aside the connection
315 push_connections
[user
] = connection
320 # Connections left open for a push notification, organized per user id
321 private var push_connections
= new Map[Int, HttpServer]
328 # Rewrite the date represented by `self` in the format expected by SQLite
329 private fun std_date
: String
331 var parts
= self.split
("-")
332 if parts
.length
!= 3 then return "1970-01-01"
334 var y
= parts
[0].to_s
335 var m
= parts
[1].to_s
336 var d
= parts
[2].to_s
338 m
= "0"*(2 - m
.length
) + m
339 d
= "0"*(2 - d
.length
) + d
345 redef class HttpResponse
347 # Respond with `data` in Json and a code 200
348 init ok
(data
: Serializable)
351 body
= data
.serialize_to_json
354 # Respond with a `BenitluxError` in JSON and a code 403
358 var error
= new BenitluxTokenError("Forbidden", "Invalid or outdated token.")
359 body
= error
.serialize_to_json
362 # Respond with a `BenitluxError` in JSON and a code 400
366 var error
= new BenitluxError("Bad Request", "Application error, or it needs to be updated.")
367 body
= error
.serialize_to_json
370 # Respond with a `BenitluxError` in JSON and a code 500
374 var error
= new BenitluxError("Internal Server Error", "Server error, try again later.")
375 body
= error
.serialize_to_json