nitweb: catalog api serves tags related data
[nit.git] / src / web / api_catalog.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 module api_catalog
16
17 import web_base
18 import catalog
19
20 redef class NitwebConfig
21
22 # Catalog to pass to handlers.
23 var catalog: Catalog is noinit
24
25 # Build the catalog
26 #
27 # This method should be called at nitweb startup.
28 fun build_catalog do
29 self.catalog = new Catalog(modelbuilder)
30 self.catalog.build_catalog(model.mpackages)
31 end
32 end
33
34 redef class APIRouter
35 redef init do
36 super
37 use("/catalog/packages/", new APICatalogPackages(config))
38
39 use("/catalog/highlighted", new APICatalogHighLighted(config))
40 use("/catalog/required", new APICatalogMostRequired(config))
41 use("/catalog/bytags", new APICatalogByTags(config))
42 use("/catalog/contributors", new APICatalogContributors(config))
43 use("/catalog/stats", new APICatalogStats(config))
44
45 use("/catalog/tags", new APICatalogTags(config))
46 use("/catalog/tag/:tid", new APICatalogTag(config))
47 end
48 end
49
50 abstract class APICatalogHandler
51 super APIHandler
52
53 # Sorter used to sort packages
54 #
55 # Sorting is based on mpackage score.
56 var mpackages_sorter = new CatalogScoreSorter(config.catalog) is lazy
57
58 # List the 10 best packages from `cpt`
59 fun list_best(cpt: Counter[MPackage]): JsonArray do
60 var res = new JsonArray
61 var best = cpt.sort
62 for i in [1..10] do
63 if i > best.length then break
64 res.add best[best.length-i]
65 end
66 return res
67 end
68
69 # List packages by group.
70 fun list_by(map: MultiHashMap[Object, MPackage]): JsonObject do
71 var res = new JsonObject
72 var keys = map.keys.to_a
73 alpha_comparator.sort(keys)
74 for k in keys do
75 var projs = map[k].to_a
76 alpha_comparator.sort(projs)
77 res[k.to_s.html_escape] = new JsonArray.from(projs)
78 end
79 return res
80 end
81 end
82
83 # Get all the packages from the catalog using pagination
84 #
85 # `GET /packages?p=1&n=10`: get the list of catalog by page
86 class APICatalogPackages
87 super APICatalogHandler
88
89 redef fun get(req, res) do
90 var page = req.int_arg("p")
91 var limit = req.int_arg("n")
92 var mpackages = config.catalog.mpackages.values.to_a
93 mpackages_sorter.sort(mpackages)
94 var response = new JsonArray.from(mpackages)
95 res.json paginate(response, response.length, page, limit)
96 end
97 end
98
99 class APICatalogHighLighted
100 super APICatalogHandler
101
102 redef fun get(req, res) do res.json list_best(config.catalog.score)
103 end
104
105 class APICatalogMostRequired
106 super APICatalogHandler
107
108 redef fun get(req, res) do
109 if config.catalog.deps.not_empty then
110 var reqs = new Counter[MPackage]
111 for p in config.model.mpackages do
112 reqs[p] = config.catalog.deps[p].smallers.length - 1
113 end
114 res.json list_best(reqs)
115 return
116 end
117 res.json new JsonArray
118 end
119 end
120
121 class APICatalogByTags
122 super APICatalogHandler
123
124 redef fun get(req, res) do res.json list_by(config.catalog.tag2proj)
125 end
126
127 class APICatalogContributors
128 super APICatalogHandler
129
130 redef fun get(req, res) do
131 var obj = new JsonObject
132 obj["maintainers"] = new JsonArray.from(config.catalog.maint2proj.keys)
133 obj["contributors"] = new JsonArray.from(config.catalog.contrib2proj.keys)
134 res.json obj
135 end
136 end
137
138 # Get the catalog statistics
139 #
140 # `GET /stats`: return the catalog statistics
141 class APICatalogStats
142 super APICatalogHandler
143
144 redef fun get(req, res) do
145 res.json config.catalog.catalog_stats
146 end
147 end
148
149 # Get all the tags from the catalog
150 #
151 # `GET /tags`: the list of tags associated with their number of packages
152 class APICatalogTags
153 super APICatalogHandler
154
155 # Sorter to sort tags alphabetically
156 var tags_sorter = new CatalogTagsSorter
157
158 redef fun get(req, res) do
159 var obj = new JsonObject
160
161 var tags = config.catalog.tag2proj.keys.to_a
162 tags_sorter.sort(tags)
163
164 for tag in tags do
165 if not config.catalog.tag2proj.has_key(tag) then continue
166 obj[tag] = config.catalog.tag2proj[tag].length
167 end
168 res.json obj
169 end
170 end
171
172 # Get the packages related to a tag
173 #
174 # `GET /tag/:tid?p=1&n=10`: return a paginated list of packages
175 class APICatalogTag
176 super APICatalogHandler
177
178 redef fun get(req, res) do
179 var page = req.int_arg("p")
180 var limit = req.int_arg("n")
181 var id = req.param("tid")
182 if id == null then
183 res.api_error(400, "Missing tag")
184 return
185 end
186 id = id.from_percent_encoding
187 if not config.catalog.tag2proj.has_key(id) then
188 res.api_error(404, "Tag not found")
189 return
190 end
191 var obj = new JsonObject
192 obj["tag"] = id
193 var mpackages = config.catalog.tag2proj[id]
194 mpackages_sorter.sort(mpackages)
195 var response = new JsonArray.from(mpackages)
196 obj["packages"] = paginate(response, response.length, page, limit)
197 res.json obj
198 end
199 end
200 redef class Catalog
201
202 # Build the catalog from `mpackages`
203 fun build_catalog(mpackages: Array[MPackage]) do
204 # Compute the poset
205 for p in mpackages do
206 var g = p.root
207 assert g != null
208 modelbuilder.scan_group(g)
209
210 deps.add_node(p)
211 for gg in p.mgroups do for m in gg.mmodules do
212 for im in m.in_importation.direct_greaters do
213 var ip = im.mpackage
214 if ip == null or ip == p then continue
215 deps.add_edge(p, ip)
216 end
217 end
218 end
219 # Build the catalog
220 for mpackage in mpackages do
221 package_page(mpackage)
222 git_info(mpackage)
223 mpackage_stats(mpackage)
224 end
225 end
226 end
227
228 redef class MPackageMetadata
229 serialize
230
231 redef fun core_serialize_to(v) do
232 super
233 v.serialize_attribute("license", license)
234 v.serialize_attribute("maintainers", maintainers)
235 v.serialize_attribute("contributors", contributors)
236 v.serialize_attribute("tags", tags)
237 v.serialize_attribute("tryit", tryit)
238 v.serialize_attribute("apk", apk)
239 v.serialize_attribute("homepage", homepage)
240 v.serialize_attribute("browse", browse)
241 v.serialize_attribute("git", git)
242 v.serialize_attribute("issues", issues)
243 v.serialize_attribute("first_date", first_date)
244 v.serialize_attribute("last_date", last_date)
245 end
246 end
247
248 # Catalog statistics
249 redef class CatalogStats
250 serialize
251
252 redef fun core_serialize_to(v) do
253 super
254 v.serialize_attribute("packages", packages)
255 v.serialize_attribute("maintainers", maintainers)
256 v.serialize_attribute("contributors", contributors)
257 v.serialize_attribute("tags", tags)
258 v.serialize_attribute("modules", modules)
259 v.serialize_attribute("classes", classes)
260 v.serialize_attribute("methods", methods)
261 v.serialize_attribute("loc", loc)
262 end
263 end
264
265 # MPackage statistics for the catalog
266 redef class MPackageStats
267 serialize
268
269 redef fun core_serialize_to(v) do
270 super
271 v.serialize_attribute("mmodules", mmodules)
272 v.serialize_attribute("mclasses", mclasses)
273 v.serialize_attribute("mmethods", mmethods)
274 v.serialize_attribute("loc", loc)
275 v.serialize_attribute("errors", errors)
276 v.serialize_attribute("warnings", warnings)
277 v.serialize_attribute("warnings_per_kloc", warnings_per_kloc)
278 v.serialize_attribute("documentation_score", documentation_score)
279 v.serialize_attribute("commits", commits)
280 v.serialize_attribute("score", score)
281 end
282 end
283
284 redef class Person
285 serialize
286
287 redef fun core_serialize_to(v) do
288 super
289 v.serialize_attribute("name", name)
290 v.serialize_attribute("email", email)
291 v.serialize_attribute("gravatar", gravatar)
292 end
293 end
294
295 redef class MPackage
296 # Serialize the full catalog version of `self` to JSON
297 #
298 # See: `FullCatalogSerializer`
299 fun to_full_catalog_json(catalog: Catalog, plain, pretty: nullable Bool): String do
300 var stream = new StringWriter
301 var serializer = new FullCatalogSerializer(stream, catalog)
302 serializer.plain_json = plain or else false
303 serializer.pretty_json = pretty or else false
304 serializer.serialize self
305 stream.close
306 return stream.to_s
307 end
308
309 redef fun core_serialize_to(v) do
310 super
311 v.serialize_attribute("metadata", metadata)
312 if v isa FullCatalogSerializer then
313 v.serialize_attribute("stats", v.catalog.mpackages_stats[self])
314
315 var parents = v.catalog.deps[self].direct_greaters.to_a
316 v.serialize_attribute("dependencies", v.deps_to_json(parents))
317 var children = v.catalog.deps[self].direct_smallers.to_a
318 v.serialize_attribute("clients", v.deps_to_json(children))
319 end
320 end
321 end
322
323 # CatalogSerializer decorate the Package JSON with full catalog metadata
324 #
325 # See MEntity::to_full_catalog_json.
326 class FullCatalogSerializer
327 super FullJsonSerializer
328
329 # Catalog used to decorate the MPackages
330 var catalog: Catalog
331
332 private fun deps_to_json(mpackages: Array[MPackage]): JsonArray do
333 var res = new JsonArray
334 for mpackage in mpackages do
335 res.add dep_to_json(mpackage)
336 end
337 return res
338 end
339
340 private fun dep_to_json(mpackage: MPackage): JsonObject do
341 var obj = new JsonObject
342 obj["name"] = mpackage.name
343 var mdoc = mpackage.mdoc_or_fallback
344 if mdoc != null then
345 obj["synopsis"] = mdoc.synopsis.write_to_string
346 end
347 return obj
348 end
349 end