e20eab724f037c792d5f5630801267edafeccaf2
[nit.git] / contrib / benitlux / src / benitlux_controller.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Actions for the Web interface of Benitlux
18 module benitlux_controller
19
20 import nitcorn
21 import nitcorn::restful
22 private import json::serialization
23
24 import benitlux_model
25 import benitlux_db
26 import benitlux_view
27 import benitlux_social
28
29 # Server action for REST or Web, for a given location
30 abstract class BenitluxAction
31 super Action
32
33 # Path to the database
34 var db_path = "benitlux_sherbrooke.db"
35
36 # Path to the storage of the last email sent
37 var sample_email_path = "benitlux_sherbrooke.email"
38 end
39
40 # Web interface to subscribe to the mailing list
41 class BenitluxSubscriptionAction
42 super BenitluxAction
43
44 redef fun answer(request, turi)
45 do
46 var template = new BenitluxDocument
47
48 var sub = request.post_args.keys.has("sub")
49 var unsub = request.all_args.keys.has("unsub")
50
51 var email = null
52 if request.all_args.keys.has("email") then email = request.all_args["email"].trim
53
54 if email != null then
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!"
61
62 var db = new DB.open(db_path)
63 db.subscribe email
64 db.close
65 else if unsub then
66 template.message_level = "warning"
67 template.message_content = "You've been unsubscribed."
68
69 var db = new DB.open(db_path)
70 db.unsubscribe email
71 db.close
72 end
73 end
74
75 if sample_email_path.file_exists then
76 var f = new FileReader.open(sample_email_path)
77 var lines = new Array[String]
78 for line in f.read_all.split_with("\n") do if not line.is_empty then lines.add line
79 f.close
80 template.sample_email_lines = lines
81 end
82
83 var response = new HttpResponse(200)
84 response.body = template.write_to_string
85 return response
86 end
87 end
88
89 # RESTful interface for the client app
90 class BenitluxRESTAction
91 super BenitluxAction
92 super RestfulAction
93
94 # Sign up a new user
95 #
96 # signup?name=a&pass=b&email=c -> LoginResult | BenitluxError
97 fun signup(name, pass, email: String): HttpResponse
98 is restful do
99 if not name.name_is_ok then
100 var error = new BenitluxError("Invalid username")
101 return new HttpResponse.ok(error)
102 end
103
104 if not pass.pass_is_ok then
105 var error = new BenitluxError("Invalid password")
106 return new HttpResponse.ok(error)
107 end
108
109 # Query DB
110 var db = new DB.open(db_path)
111 var error_message = db.signup(name, pass, email)
112
113 var object: nullable Serializable
114 if error_message == null then
115 object = db.login(name, pass)
116 else
117 object = new BenitluxError(error_message)
118 end
119 db.close
120
121 if object == null then
122 # There was an error in the call to login
123 return new HttpResponse.server_error
124 end
125
126 # It went ok, may or may not be signed up
127 return new HttpResponse.ok(object)
128 end
129
130 # Attempt to login
131 #
132 # login?name=a&pass=b -> LoginResult | BenitluxError
133 fun login(name, pass: String): HttpResponse
134 is restful do
135 var db = new DB.open(db_path)
136 var log: nullable Serializable = db.login(name, pass)
137 db.close
138
139 if log == null then log = new BenitluxError("Login Failed", "Invalid username and password combination.")
140
141 return new HttpResponse.ok(log)
142 end
143
144 # Search a user
145 #
146 # search?token=b&query=a&offset=0 -> Array[UserAndFollowing] | BenitluxError
147 fun search(token: nullable String, query: String): HttpResponse
148 is restful do
149 var db = new DB.open(db_path)
150 var user_id = db.token_to_id(token)
151 var users = db.search_users(query, user_id)
152 db.close
153
154 if users == null then return new HttpResponse.server_error
155
156 return new HttpResponse.ok(users)
157 end
158
159 # List available beers
160 #
161 # list?token=a[&offset=0&count=1] -> Array[BeerAndRatings] | BenitluxError
162 fun list(token: nullable String): HttpResponse
163 is restful do
164
165 # Query DB
166 var db = new DB.open(db_path)
167 var user_id = db.token_to_id(token)
168 var list = db.list_beers_and_rating(user_id)
169 db.close
170
171 if list == null then return new HttpResponse.server_error
172
173 return new HttpResponse.ok(list)
174 end
175
176 # Post a review of `beer`
177 #
178 # review?token=a&beer=b&rating=0 -> true | BenitluxError
179 fun review(token: String, rating, beer: Int): HttpResponse
180 is restful do
181 # Query DB
182 var db = new DB.open(db_path)
183 var user_id = db.token_to_id(token)
184
185 if user_id == null then
186 db.close
187 return new HttpResponse.invalid_token
188 end
189
190 db.post_review(user_id, beer, rating, "")
191 db.close
192
193 return new HttpResponse.ok(true)
194 end
195
196 # Set whether user of `token` follows `user_to`, by default set as follow
197 #
198 # follow?token=a&user_to=0 -> true | BenitluxError
199 fun follow(token: String, user_to: Int, follow: nullable Bool): HttpResponse
200 is restful do
201
202 # Query DB
203 var db = new DB.open(db_path)
204 var user = db.token_to_id(token)
205
206 if user == null then
207 db.close
208 return new HttpResponse.invalid_token
209 end
210
211 if follow or else true then
212 db.add_followed(user, user_to)
213 else db.remove_followed(user, user_to)
214
215 db.close
216
217 return new HttpResponse.ok(true)
218 end
219
220 # List followers of the user of `token`
221 #
222 # followers?token=a -> Array[UserAndFollowing] | BenitluxError | BenitluxError
223 fun followers(token: String): HttpResponse
224 is restful do
225 # Query DB
226 var db = new DB.open(db_path)
227 var user = db.token_to_id(token)
228 if user == null then
229 db.close
230 return new HttpResponse.invalid_token
231 end
232 var users = db.followers(user)
233 db.close
234
235 if users == null then return new HttpResponse.server_error
236
237 return new HttpResponse.ok(users)
238 end
239
240 # List users followed by the user of `token`
241 #
242 # followed?token=a -> Array[UserAndFollowing] | BenitluxError
243 fun followed(token: String): HttpResponse
244 is restful do
245 # Query DB
246 var db = new DB.open(db_path)
247 var user = db.token_to_id(token)
248 if user == null then
249 db.close
250 return new HttpResponse.invalid_token
251 end
252 var users = db.followed(user)
253 db.close
254
255 if users == null then return new HttpResponse.server_error
256
257 return new HttpResponse.ok(users)
258 end
259
260 # List friends of the user of `token`
261 #
262 # friends?token=a -> Array[UserAndFollowing] | BenitluxError
263 fun friends(token: String, n: nullable Int): HttpResponse
264 is restful do
265 # Query DB
266 var db = new DB.open(db_path)
267 var user = db.token_to_id(token)
268 var users = db.friends(user, n)
269 db.close
270
271 if users == null then return new HttpResponse.server_error
272
273 return new HttpResponse.ok(users)
274 end
275
276 # Check user in or out
277 #
278 # checkin?token=a -> true | BenitluxError
279 fun checkin(token: String, is_in: nullable Bool): HttpResponse
280 is restful do
281 var db = new DB.open(db_path)
282 var id = db.token_to_id(token)
283 if id == null then
284 db.close
285 return new HttpResponse.invalid_token
286 end
287
288 # Register in DB
289 db.checkin(id, is_in or else true)
290
291 # Update followed_followers
292 var common_followers = db.followed_followers(id)
293 db.close
294
295 return new HttpResponse.ok(true)
296 end
297
298 # List users currently checked in among friends of the user of `token`
299 #
300 # checkedin?token=a -> Array[UserAndFollowing]
301 fun checkedin(token: String): HttpResponse
302 is restful do
303 var db = new DB.open(db_path)
304 var user_id = db.token_to_id(token)
305 if user_id == null then
306 db.close
307 return new HttpResponse.invalid_token
308 end
309 var report = db.checkedin_followed_followers(user_id)
310 db.close
311
312 if report == null then return new HttpResponse.server_error
313 return new HttpResponse.ok(report)
314 end
315
316 # List beer changes since `date` with information in relation to the user of `token`
317 #
318 # since?token=a&date=date -> BeerEvents
319 fun since(token, date: nullable String): HttpResponse
320 is restful do
321 # Query DB
322 var db = new DB.open(db_path)
323 var user_id = db.token_to_id(token)
324 var list = db.list_beers_and_rating(user_id, date)
325 db.close
326
327 if list == null then return new HttpResponse.server_error
328
329 return new HttpResponse.ok(list)
330 end
331
332 # Fallback answer on errors
333 redef fun answer(request, turi) do return new HttpResponse.bad_request
334 end
335
336 # ---
337 # Misc services
338
339 redef class Text
340 # Rewrite the date represented by `self` in the format expected by SQLite
341 private fun std_date: String
342 do
343 var parts = self.split("-")
344 if parts.length != 3 then return "1970-01-01"
345
346 var y = parts[0].to_s
347 var m = parts[1].to_s
348 var d = parts[2].to_s
349
350 m = "0"*(2 - m.length) + m
351 d = "0"*(2 - d.length) + d
352
353 return "{y}-{m}-{d}"
354 end
355 end
356
357 redef class HttpResponse
358
359 # Respond with `data` in Json and a code 200
360 init ok(data: Serializable)
361 do
362 init 200
363 body = data.to_json_string
364 end
365
366 # Respond with a `BenitluxError` in JSON and a code 403
367 init invalid_token
368 do
369 init 403
370 var error = new BenitluxTokenError("Forbidden", "Invalid or outdated token.")
371 body = error.to_json_string
372 end
373
374 # Respond with a `BenitluxError` in JSON and a code 400
375 init bad_request
376 do
377 init 400
378 var error = new BenitluxError("Bad Request", "Application error, or it needs to be updated.")
379 body = error.to_json_string
380 end
381
382 # Respond with a `BenitluxError` in JSON and a code 500
383 init server_error
384 do
385 init 500
386 var error = new BenitluxError("Internal Server Error", "Server error, try again later.")
387 body = error.to_json_string
388 end
389 end