Coverage for webapp/publisher/snaps/views.py: 84%
178 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-14 22:07 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-14 22:07 +0000
1# Packages
2import flask
3from canonicalwebteam.store_api.dashboard import Dashboard
4from canonicalwebteam.store_api.publishergw import PublisherGW
5from canonicalwebteam.exceptions import (
6 StoreApiError,
7 StoreApiResponseErrorList,
8)
9from flask.json import jsonify
11# Local
12from webapp import authentication
13from webapp.helpers import api_publisher_session, launchpad
14from webapp.api.exceptions import ApiError
15from webapp.decorators import exchange_required, login_required
16from webapp.endpoints.publisher import listing as listing_endpoint
17from webapp.endpoints import cves
18from webapp.publisher.snaps import (
19 build_views,
20 listing_views,
21 logic,
22 metrics_views,
23 publicise_views,
24 release_views,
25 settings_views,
26 collaboration_views,
27)
28from webapp.endpoints.publisher.builds import (
29 get_snap_build_page,
30 get_validate_repo,
31 post_build,
32 post_disconnect_repo,
33)
34from webapp.endpoints.publisher.settings import (
35 get_settings_data,
36 post_settings_data,
37)
38from webapp.endpoints.publisher.publicise import get_publicise_data
39from webapp.endpoints.publisher.packages import get_package_metadata
40from webapp.endpoints.publisher.register import (
41 post_register_name,
42 post_register_name_dispute,
43)
44from webapp.endpoints import releases, builds
45from webapp.publisher.snaps.builds import map_snap_build_status
47dashboard = Dashboard(api_publisher_session)
48publisher_gateway = PublisherGW("snap", api_publisher_session)
51publisher_snaps = flask.Blueprint(
52 "publisher_snaps",
53 __name__,
54 template_folder="/templates",
55 static_folder="/static",
56)
58# Listing views
59publisher_snaps.add_url_rule(
60 "/account/snaps/<snap_name>/market",
61 view_func=listing_views.get_market_snap,
62)
63publisher_snaps.add_url_rule(
64 "/account/snaps/<snap_name>/listing",
65 view_func=listing_views.get_market_snap,
66 methods=["GET"],
67)
68publisher_snaps.add_url_rule(
69 "/account/snaps/<snap_name>/listing",
70 view_func=listing_views.redirect_post_market_snap,
71 methods=["POST"],
72)
73publisher_snaps.add_url_rule(
74 "/<snap_name>/listing",
75 view_func=listing_views.get_listing_snap,
76 methods=["GET"],
77)
78publisher_snaps.add_url_rule(
79 "/api/<snap_name>/listing",
80 view_func=listing_endpoint.get_listing_data,
81 methods=["GET"],
82)
83publisher_snaps.add_url_rule(
84 "/api/<snap_name>/listing",
85 view_func=listing_endpoint.post_listing_data,
86 methods=["POST"],
87)
88publisher_snaps.add_url_rule(
89 "/<snap_name>/preview",
90 view_func=listing_views.post_preview,
91 methods=["POST"],
92)
93publisher_snaps.add_url_rule(
94 "/<snap_name>/collaboration",
95 view_func=collaboration_views.get_collaboration_snap,
96 methods=["GET"],
97)
99# Build views
100publisher_snaps.add_url_rule(
101 "/<snap_name>/builds",
102 view_func=build_views.get_snap_builds_page,
103 methods=["GET"],
104),
106publisher_snaps.add_url_rule(
107 "/<snap_name>/builds/<build_id>",
108 view_func=get_snap_build_page,
109 methods=["GET"],
110),
112publisher_snaps.add_url_rule(
113 "/api/<snap_name>/repo",
114 view_func=builds.get_snap_repo,
115 methods=["GET"],
116)
117publisher_snaps.add_url_rule(
118 "/api/<snap_name>/builds",
119 view_func=build_views.get_snap_builds,
120 methods=["GET"],
121)
122publisher_snaps.add_url_rule(
123 "/api/<snap_name>/builds",
124 view_func=build_views.post_snap_builds,
125 methods=["POST"],
126)
127publisher_snaps.add_url_rule(
128 "/api/<snap_name>/builds/<build_id>",
129 view_func=build_views.get_snap_build,
130 methods=["GET"],
131)
132publisher_snaps.add_url_rule(
133 "/api/<snap_name>/builds/validate-repo",
134 view_func=get_validate_repo,
135 methods=["GET"],
136)
137publisher_snaps.add_url_rule(
138 "/api/<snap_name>/builds/trigger-build",
139 view_func=post_build,
140 methods=["POST"],
141)
142publisher_snaps.add_url_rule(
143 "/api/<snap_name>/builds/check-build-request/<build_id>",
144 view_func=build_views.check_build_request,
145 methods=["GET"],
146)
147publisher_snaps.add_url_rule(
148 "/api/<snap_name>/webhook/notify",
149 view_func=build_views.post_github_webhook,
150 methods=["POST"],
151)
152# This route is to support previous webhooks from build.snapcraft.io
153publisher_snaps.add_url_rule(
154 "/api/<github_owner>/<github_repo>/webhook/notify",
155 view_func=build_views.post_github_webhook,
156 methods=["POST"],
157)
158publisher_snaps.add_url_rule(
159 "/api/<snap_name>/builds/update-webhook",
160 view_func=build_views.get_update_gh_webhooks,
161 methods=["GET"],
162)
163publisher_snaps.add_url_rule(
164 "/api/<snap_name>/builds/disconnect/",
165 view_func=post_disconnect_repo,
166 methods=["POST"],
167)
169# Release views
170publisher_snaps.add_url_rule(
171 "/account/snaps/<snap_name>/release",
172 view_func=release_views.redirect_get_release_history,
173)
174publisher_snaps.add_url_rule(
175 "/<snap_name>/release",
176 view_func=release_views.redirect_get_release_history,
177)
178publisher_snaps.add_url_rule(
179 "/<snap_name>/releases",
180 view_func=release_views.get_releases,
181 methods=["GET"],
182)
183publisher_snaps.add_url_rule(
184 "/api/<snap_name>/releases",
185 view_func=releases.get_release_history_data,
186 methods=["GET"],
187)
188publisher_snaps.add_url_rule(
189 "/account/snaps/<snap_name>/release",
190 view_func=release_views.redirect_post_release,
191 methods=["POST"],
192)
193publisher_snaps.add_url_rule(
194 "/<snap_name>/release",
195 view_func=release_views.redirect_post_release,
196 methods=["POST"],
197)
198publisher_snaps.add_url_rule(
199 "/<snap_name>/releases/json",
200 view_func=release_views.get_release_history_json,
201)
202publisher_snaps.add_url_rule(
203 "/<snap_name>/releases",
204 view_func=release_views.post_release,
205 methods=["POST"],
206)
207publisher_snaps.add_url_rule(
208 "/<snap_name>/release/close-channel",
209 view_func=release_views.redirect_post_close_channel,
210 methods=["POST"],
211)
212publisher_snaps.add_url_rule(
213 "/<snap_name>/releases/close-channel",
214 view_func=release_views.post_close_channel,
215 methods=["POST"],
216)
217publisher_snaps.add_url_rule(
218 "/<snap_name>/releases/default-track",
219 view_func=release_views.post_default_track,
220 methods=["POST"],
221)
222publisher_snaps.add_url_rule(
223 "/<snap_name>/releases/revision/<revision>",
224 view_func=release_views.get_snap_revision_json,
225)
227# Metrics views
228publisher_snaps.add_url_rule(
229 "/snaps/metrics/json",
230 view_func=metrics_views.get_account_snaps_metrics,
231 methods=["POST"],
232)
233publisher_snaps.add_url_rule(
234 "/account/snaps/<snap_name>/measure",
235 view_func=metrics_views.get_measure_snap,
236)
237publisher_snaps.add_url_rule(
238 "/account/snaps/<snap_name>/metrics",
239 view_func=metrics_views.get_measure_snap,
240)
241publisher_snaps.add_url_rule(
242 "/<snap_name>/metrics",
243 view_func=metrics_views.publisher_snap_metrics,
244)
246publisher_snaps.add_url_rule(
247 "/<snap_name>/metrics/active-devices",
248 view_func=metrics_views.get_active_devices,
249)
251publisher_snaps.add_url_rule(
252 "/<snap_name>/metrics/active-latest-devices",
253 view_func=metrics_views.get_latest_active_devices,
254)
256publisher_snaps.add_url_rule(
257 "/<snap_name>/metrics/active-device-annotation",
258 view_func=metrics_views.get_metric_annotaion,
259)
261publisher_snaps.add_url_rule(
262 "/<snap_name>/metrics/country-metric",
263 view_func=metrics_views.get_country_metric,
264)
266# Publice views
267publisher_snaps.add_url_rule(
268 "/<snap_name>/publicise",
269 view_func=publicise_views.get_publicise,
270)
271publisher_snaps.add_url_rule(
272 "/<snap_name>/publicise/badges",
273 view_func=publicise_views.get_publicise,
274)
275publisher_snaps.add_url_rule(
276 "/<snap_name>/publicise/cards",
277 view_func=publicise_views.get_publicise,
278)
279publisher_snaps.add_url_rule(
280 "/api/<snap_name>/publicise",
281 view_func=get_publicise_data,
282)
284# Settings views
285publisher_snaps.add_url_rule(
286 "/<snap_name>/settings",
287 view_func=settings_views.get_settings,
288)
289publisher_snaps.add_url_rule(
290 "/api/<snap_name>/settings",
291 view_func=post_settings_data,
292 methods=["POST"],
293)
294publisher_snaps.add_url_rule(
295 "/api/<snap_name>/settings",
296 view_func=get_settings_data,
297)
299# CVE API
300publisher_snaps.add_url_rule(
301 "/api/<snap_name>/<revision>/cves",
302 view_func=cves.get_cves,
303)
305publisher_snaps.add_url_rule(
306 "/api/<snap_name>/cves",
307 view_func=cves.get_revisions_with_cves,
308)
311@publisher_snaps.route("/account/snaps")
312@login_required
313def redirect_get_account_snaps():
314 return flask.redirect(flask.url_for(".get_account_snaps"))
317@publisher_snaps.route("/snaps")
318@login_required
319def get_account_snaps():
320 account_info = dashboard.get_account(flask.session)
322 user_snaps, registered_snaps = logic.get_snaps_account_info(account_info)
324 flask_user = flask.session["publisher"]
326 context = {
327 "snaps": user_snaps,
328 "current_user": flask_user["nickname"],
329 "registered_snaps": registered_snaps,
330 }
332 return flask.render_template("store/publisher.html", **context)
335@publisher_snaps.route("/snaps.json")
336@login_required
337def get_user_snaps():
338 account_info = dashboard.get_account(flask.session)
340 user_snaps, registered_snaps = logic.get_snaps_account_info(account_info)
342 flask_user = flask.session["publisher"]
344 return flask.jsonify(
345 {
346 "snaps": user_snaps,
347 "current_user": flask_user["nickname"],
348 "registered_snaps": registered_snaps,
349 }
350 )
353@publisher_snaps.route("/snap-builds.json")
354@login_required
355def get_snap_build_status():
356 try:
357 account_info = dashboard.get_account(flask.session)
358 except (StoreApiError, ApiError):
359 return flask.jsonify({"error": "An unexpected error occurred"}), 400
361 response = []
362 user_snaps, _ = logic.get_snaps_account_info(account_info)
364 for snap_name in user_snaps:
365 snap_build_statuses = launchpad.get_snap_build_status(snap_name)
366 status = map_snap_build_status(snap_build_statuses)
368 response.append({"name": snap_name, "status": status})
370 return flask.jsonify(response)
373@publisher_snaps.route("/account/register-snap")
374def redirect_get_register_name():
375 return flask.redirect(flask.url_for(".get_register_name"))
378@publisher_snaps.route("/register-snap")
379@login_required
380def get_register_name():
381 return flask.render_template("store/publisher.html")
384@publisher_snaps.route("/account/register-snap", methods=["POST"])
385def redirect_post_register_name():
386 return flask.redirect(flask.url_for(".post_register_name"), 307)
389publisher_snaps.add_url_rule(
390 "/api/packages/<snap_name>",
391 view_func=get_package_metadata,
392 methods=["GET"],
393)
395publisher_snaps.add_url_rule(
396 "/api/register-snap",
397 view_func=post_register_name,
398 methods=["POST"],
399)
401publisher_snaps.add_url_rule(
402 "/api/register-name-dispute",
403 view_func=post_register_name_dispute,
404 methods=["POST"],
405)
408@publisher_snaps.route("/packages/<package_name>", methods=["DELETE"])
409@login_required
410@exchange_required
411def delete_package(package_name):
412 response = publisher_gateway.unregister_package_name(
413 flask.session, package_name
414 )
416 if response.status_code == 200:
417 return ("", 200)
418 return (
419 jsonify({"error": response.json()["error-list"][0]["message"]}),
420 response.status_code,
421 )
424@publisher_snaps.route("/snap_info/user_snap/<snap_name>", methods=["GET"])
425@login_required
426def get_is_user_snap(snap_name):
427 is_users_snap = False
428 try:
429 snap_info = dashboard.get_snap_info(flask.session, snap_name)
430 except (StoreApiError, ApiError):
431 return flask.jsonify({"error": "An unexpected error occurred"}), 400
433 if authentication.is_authenticated(flask.session):
434 publisher_info = flask.session.get("publisher", {})
435 if (
436 publisher_info.get("nickname")
437 == snap_info["publisher"]["username"]
438 ):
439 is_users_snap = True
441 return {"is_users_snap": is_users_snap}
444@publisher_snaps.route("/register-snap/json", methods=["POST"])
445@login_required
446def post_register_name_json():
447 snap_name = flask.request.form.get("snap-name")
449 if not snap_name:
450 return (
451 flask.jsonify({"errors": [{"message": "Snap name is required"}]}),
452 400,
453 )
455 try:
456 response = dashboard.post_register_name(
457 session=flask.session, snap_name=snap_name
458 )
459 except StoreApiResponseErrorList as api_response_error_list:
460 for error in api_response_error_list.errors:
461 # if snap name is already owned treat it as success
462 if error["code"] == "already_owned":
463 return flask.jsonify(
464 {"code": error["code"], "snap_name": snap_name}
465 )
466 return (
467 flask.jsonify({"errors": api_response_error_list.errors}),
468 api_response_error_list.status_code,
469 )
471 response["code"] = "created"
473 return flask.jsonify(response)
476@publisher_snaps.route("/register-name-dispute")
477@login_required
478def get_register_name_dispute():
479 snap_name = flask.request.args.get("snap-name")
481 if not snap_name:
482 return flask.redirect(
483 flask.url_for(".get_register_name", snap_name=snap_name)
484 )
485 return flask.render_template(
486 "store/publisher.html",
487 )
490@publisher_snaps.route("/request-reserved-name")
491@login_required
492def get_request_reserved_name():
493 stores = dashboard.get_stores(flask.session)
495 snap_name = flask.request.args.get("snap_name")
496 store_id = flask.request.args.get("store")
497 store_name = logic.get_store_name(store_id, stores)
499 if not snap_name:
500 return flask.redirect(
501 flask.url_for(
502 ".get_register_name", snap_name=snap_name, store=store_id
503 )
504 )
505 return flask.render_template(
506 "store/publisher.html",
507 snap_name=snap_name,
508 store=store_name,
509 )
512@publisher_snaps.route("/snaps/api/snap-count")
513@login_required
514def snap_count():
515 account_info = dashboard.get_account(flask.session)
517 user_snaps, registered_snaps = logic.get_snaps_account_info(account_info)
519 context = {"count": len(user_snaps), "snaps": list(user_snaps.keys())}
521 return flask.jsonify(context)