Merge: Nitsmell : Adding new code smells and print console updated
[nit.git] / src / web / api_feedback.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Feedback related features
16 module api_feedback
17
18 import web_base
19 import popcorn::pop_auth
20
21 redef class NitwebConfig
22
23 # MongoDB collection used to store stars.
24 var stars = new StarRatingRepo(db.collection("stars")) is lazy
25 end
26
27 redef class APIRouter
28
29 redef init do
30 super
31
32 use("/feedback/grades/most", new APIStarsMost(config))
33 use("/feedback/grades/best", new APIStarsBest(config))
34 use("/feedback/grades/worst", new APIStarsWorst(config))
35 use("/feedback/grades/users", new APIStarsUsers(config))
36
37 use("/feedback/user/stars", new APIUserStars(config))
38
39 use("/feedback/stars/:id", new APIStars(config))
40 use("/feedback/stars/:id/dimension/:dimension", new APIStarsDimension(config))
41 end
42 end
43
44 # Base handler for feedback features.
45 abstract class APIFeedBack
46 super APIHandler
47
48 # Get the user logged in or null if no session
49 fun get_session_user(req: HttpRequest): nullable User do
50 var session = req.session
51 if session == null then return null
52 return session.user
53 end
54
55 # Get the login of the session user or null if no session
56 fun get_session_login(req: HttpRequest): nullable String do
57 var user = get_session_user(req)
58 if user == null then return null
59 return user.login
60 end
61 end
62
63 # Most rated entities
64 class APIStarsMost
65 super APIFeedBack
66
67 redef fun get(req, res) do
68 res.json new JsonArray.from(config.stars.most_rated)
69 end
70 end
71
72 # Best rated entities
73 class APIStarsBest
74 super APIFeedBack
75
76 redef fun get(req, res) do
77 res.json new JsonArray.from(config.stars.best_rated)
78 end
79 end
80
81 # Best rated entities
82 class APIStarsWorst
83 super APIFeedBack
84
85 redef fun get(req, res) do
86 res.json new JsonArray.from(config.stars.worst_rated)
87 end
88 end
89
90 # Best rated entities
91 class APIStarsUsers
92 super APIFeedBack
93
94 redef fun get(req, res) do
95 res.json new JsonArray.from(config.stars.users_ratings)
96 end
97 end
98
99 # Stars attributed to mentities by user
100 class APIUserStars
101 super APIFeedBack
102
103 redef fun get(req, res) do
104 var user = get_session_user(req)
105 if user == null then return
106 res.json new JsonArray.from(user.ratings(config))
107 end
108 end
109
110 # Stars attributed to mentities
111 class APIStars
112 super APIFeedBack
113
114 redef fun get(req, res) do
115 var login = get_session_login(req)
116 var mentity = mentity_from_uri(req, res)
117 if mentity == null then return
118 res.json mentity.ratings(config, login)
119 end
120 end
121
122 # Stars attributed to mentities by dimension
123 class APIStarsDimension
124 super APIFeedBack
125
126 redef fun get(req, res) do
127 var login = get_session_login(req)
128 var mentity = mentity_from_uri(req, res)
129 if mentity == null then return
130 var dimension = req.param("dimension")
131 if dimension == null then return
132 res.json mentity.ratings_by_dimension(config, dimension, login)
133 end
134
135 redef fun post(req, res) do
136 var user = get_session_user(req)
137 var login = null
138 if user != null then login = user.login
139
140 var mentity = mentity_from_uri(req, res)
141 if mentity == null then return
142 var dimension = req.param("dimension")
143 if dimension == null then return
144
145 # Retrieve user previous rating
146 var previous = null
147 if user != null then
148 previous = user.find_previous_rating(config, mentity, dimension)
149 end
150
151 var obj = req.body.parse_json
152 if not obj isa JsonObject then
153 res.api_error(400, "Expected a JSON object")
154 return
155 end
156 var rating = obj["rating"]
157 if not rating isa Int then
158 res.api_error(400, "Expected a key `rating`")
159 return
160 end
161
162 if previous != null then
163 previous.rating = rating
164 previous.timestamp = get_time
165 config.stars.save previous
166 else
167 config.stars.save new StarRating(login, mentity.full_name, dimension, rating)
168 end
169 res.json mentity.ratings_by_dimension(config, dimension, login)
170 end
171 end
172
173 # Star ratings allow users to rate mentities with a 5-stars system.
174 #
175 # Each rating can consider only one `dimension` of the mentity.
176 # Dimensions are arbitrary strings used to group ratings.
177 class StarRating
178 super RepoObject
179 serialize
180
181 # The user login that made that rating (or null if anon)
182 var user: nullable String
183
184 # Rated `MEntity::full_name`
185 var mentity: String
186
187 # The dimension rated (arbritrary key)
188 var dimension: nullable String
189
190 # The rating (traditionally a score between 0 and 5)
191 var rating: Int is writable
192
193 # Timestamp when this rating was created
194 var timestamp = 0 is writable
195 end
196
197 redef class User
198
199 # Find a previous rating of `self` for `mentity` and `dimension`
200 fun find_previous_rating(config: NitwebConfig, mentity: MEntity, dimension: nullable String): nullable StarRating do
201 var match = new MongoMatch
202 match.eq("mentity", mentity.full_name)
203 match.eq("dimension", dimension)
204 match.eq("user", login)
205 return config.stars.find(match)
206 end
207
208 # Find all ratings by `self`
209 fun ratings(config: NitwebConfig): Array[StarRating] do
210 return config.stars.find_all((new MongoMatch).eq("user", login))
211 end
212 end
213
214 redef class MEntity
215
216 # Get the ratings of a `dimension`
217 fun ratings_by_dimension(config: NitwebConfig, dimension: String, user: nullable String): JsonObject do
218 var match = (new MongoMatch).eq("mentity", full_name).eq("dimension", dimension)
219 var pipeline = new MongoPipeline
220 pipeline.match(match)
221 pipeline.group((new MongoGroup("mean_group")).avg("mean", "$rating"))
222
223 var res = config.stars.collection.aggregate(pipeline)
224 var obj = new JsonObject
225 obj["mean"] = if res.is_empty then 0.0 else res.first["mean"]
226 obj["ratings"] = new JsonArray.from(config.stars.find_all(match))
227
228 if user != null then
229 match["user"] = user
230 obj["user"] = config.stars.find(match)
231 end
232 return obj
233 end
234
235 # Get the ratings of a `mentity`
236 fun ratings(config: NitwebConfig, user: nullable String): JsonObject do
237 var match = new JsonObject
238 match["mentity"] = full_name
239 match["ratings"] = new JsonArray.from(config.stars.find_all(match))
240 match["feature"] = ratings_by_dimension(config, "feature", user)
241 match["doc"] = ratings_by_dimension(config, "doc", user)
242 match["examples"] = ratings_by_dimension(config, "examples", user)
243 match["code"] = ratings_by_dimension(config, "code", user)
244 return match
245 end
246 end
247
248 # StarRating Mongo Repository
249 class StarRatingRepo
250 super MongoRepository[StarRating]
251
252 # Find most rated mentities
253 fun most_rated: Array[JsonObject] do
254 var pipeline = new MongoPipeline
255 pipeline.group((new MongoGroup("$mentity")).sum("count", 1))
256 pipeline.sort((new MongoMatch).eq("count", -1))
257 pipeline.limit(10)
258 return collection.aggregate(pipeline)
259 end
260
261 # Find best rated mentities
262 fun best_rated: Array[JsonObject] do
263 var pipeline = new MongoPipeline
264 pipeline.group((new MongoGroup("$mentity")).avg("avg", "$rating"))
265 pipeline.sort((new MongoMatch).eq("avg", -1))
266 pipeline.limit(10)
267 return collection.aggregate(pipeline)
268 end
269
270 # Find worst rated mentities
271 fun worst_rated: Array[JsonObject] do
272 var pipeline = new MongoPipeline
273 pipeline.group((new MongoGroup("$mentity")).avg("avg", "$rating"))
274 pipeline.sort((new MongoMatch).eq("avg", 1))
275 pipeline.limit(10)
276 return collection.aggregate(pipeline)
277 end
278
279 # Find worst rated mentities
280 fun users_ratings: Array[JsonObject] do
281 var pipeline = new MongoPipeline
282 pipeline.group((new MongoGroup("$user")).sum("count", 1))
283 pipeline.sort((new MongoMatch).eq("count", -1))
284 pipeline.limit(10)
285 return collection.aggregate(pipeline)
286 end
287 end