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