From 98eb7cc14a13c06c381fde1f31257b40c292db40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Wed, 18 Aug 2021 17:47:57 +0300 Subject: [PATCH] Add /api/v1/badges/ endpoint cc: #552 --- CHANGELOG.md | 3 ++ hc/api/tests/test_get_badges.py | 43 ++++++++++++++++++++++ hc/api/urls.py | 1 + hc/api/views.py | 24 ++++++++++++- templates/docs/api.html | 58 ++++++++++++++++++++++++++++++ templates/docs/api.md | 64 +++++++++++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 hc/api/tests/test_get_badges.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a0dcbcd..3cfd7f92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## v1.23.0 - Unreleased +### Improvements +- Add /api/v1/badges/ endpoint (#552) + ### Bug Fixes - Add handling for non-latin-1 characters in webhook headers - Fix dark mode bug in selectpicker widgets diff --git a/hc/api/tests/test_get_badges.py b/hc/api/tests/test_get_badges.py new file mode 100644 index 00000000..c9771be7 --- /dev/null +++ b/hc/api/tests/test_get_badges.py @@ -0,0 +1,43 @@ +from datetime import timedelta as td + +from hc.api.models import Check +from hc.test import BaseTestCase + + +class GetBadgesTestCase(BaseTestCase): + def setUp(self): + super().setUp() + + self.a1 = Check(project=self.project, name="Alice 1") + self.a1.timeout = td(seconds=3600) + self.a1.grace = td(seconds=900) + self.a1.n_pings = 0 + self.a1.status = "new" + self.a1.tags = "foo bar" + self.a1.save() + + self.url = "/api/v1/badges/" + + def get(self, api_key="X" * 32, qs=""): + return self.client.get(self.url + qs, HTTP_X_API_KEY=api_key) + + def test_it_works(self): + + r = self.get() + self.assertEqual(r.status_code, 200) + self.assertEqual(r["Access-Control-Allow-Origin"], "*") + + doc = r.json() + self.assertTrue("foo" in doc["badges"]) + self.assertTrue("svg" in doc["badges"]["foo"]) + + def test_readonly_key_is_allowed(self): + self.project.api_key_readonly = "R" * 32 + self.project.save() + + r = self.get(api_key=self.project.api_key_readonly) + self.assertEqual(r.status_code, 200) + + def test_it_rejects_post(self): + r = self.client.post(self.url, HTTP_X_API_KEY="X" * 32) + self.assertEqual(r.status_code, 405) diff --git a/hc/api/urls.py b/hc/api/urls.py index 7546b68a..8aeeb222 100644 --- a/hc/api/urls.py +++ b/hc/api/urls.py @@ -46,6 +46,7 @@ urlpatterns = [ path("api/v1/checks//flips/", views.flips_by_uuid, name="hc-api-flips"), path("api/v1/checks//flips/", views.flips_by_unique_key), path("api/v1/channels/", views.channels), + path("api/v1/badges/", views.badges), path( "badge///.", views.badge, diff --git a/hc/api/views.py b/hc/api/views.py index 03ebb6ef..9f40779a 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -21,7 +21,7 @@ from hc.api import schemas from hc.api.decorators import authorize, authorize_read, cors, validate_json from hc.api.forms import FlipsFiltersForm from hc.api.models import MAX_DELTA, Flip, Channel, Check, Notification, Ping -from hc.lib.badges import check_signature, get_badge_svg +from hc.lib.badges import check_signature, get_badge_svg, get_badge_url class BadChannelException(Exception): @@ -377,6 +377,28 @@ def flips_by_unique_key(request, unique_key): return HttpResponseNotFound() +@cors("GET") +@authorize_read +def badges(request): + tags = set(["*"]) + for check in Check.objects.filter(project=request.project): + tags.update(check.tags_list()) + + key = request.project.badge_key + badges = {} + for tag in tags: + badges[tag] = { + "svg": get_badge_url(key, tag), + "svg3": get_badge_url(key, tag, with_late=True), + "json": get_badge_url(key, tag, fmt="json"), + "json3": get_badge_url(key, tag, fmt="json", with_late=True), + "shields": get_badge_url(key, tag, fmt="shields"), + "shields3": get_badge_url(key, tag, fmt="shields", with_late=True), + } + + return JsonResponse({"badges": badges}) + + @never_cache @cors("GET") def badge(request, badge_key, signature, tag, fmt): diff --git a/templates/docs/api.html b/templates/docs/api.html index f26c6caf..42df1407 100644 --- a/templates/docs/api.html +++ b/templates/docs/api.html @@ -46,6 +46,10 @@ in your account.

Get a list of existing integrations GET SITE_ROOT/api/v1/channels/ + +Get project's badges +GET SITE_ROOT/api/v1/badges/ +

Authentication

@@ -63,6 +67,7 @@ and read-only API keys on the Project Settings page.

  • Get a list of existing checks
  • Get a single check
  • Get a list of check's status changes
  • +
  • Get project's badges
  • Omits sensitive information from the API responses. See the documentation of individual API endpoints for details.

    @@ -807,4 +812,57 @@ number of returned pings depends on the account's billing plan: 100 for free acc } ] } + + +

    Get Project's Badges

    +

    GET SITE_ROOT/api/v1/badges/

    +

    Returns a map of all tags in the project, with badge URLs for each tag.

    +

    Response Codes

    +
    +
    200 OK
    +
    The request succeeded.
    +
    401 Unauthorized
    +
    The API key is either missing or invalid.
    +
    +

    Example Request

    +
    curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/badges/
    +
    + +

    Example Response

    +
    {
    +  "badges": {
    +    "backup": {
    +      "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.svg",
    +      "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.svg",
    +      "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.json",
    +      "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.json",
    +      "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.shields",
    +      "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.shields"
    +    },
    +    "db": {
    +      "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.svg",
    +      "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.svg",
    +      "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.json",
    +      "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.json",
    +      "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.shields",
    +      "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.shields"
    +    },
    +    "production": {
    +      "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.svg",
    +      "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.svg",
    +      "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.json",
    +      "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.json",
    +      "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.shields",
    +      "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.shields"
    +    },
    +    "*": {
    +      "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.svg",
    +      "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.svg",
    +      "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.json",
    +      "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.json",
    +      "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.shields",
    +      "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.shields"
    +    }
    +  }
    +}
     
    \ No newline at end of file diff --git a/templates/docs/api.md b/templates/docs/api.md index 99d6b5b7..addab3bb 100644 --- a/templates/docs/api.md +++ b/templates/docs/api.md @@ -16,6 +16,7 @@ Endpoint Name | Endpoint Address [Get a list of check's logged pings](#list-pings) | `GET SITE_ROOT/api/v1/checks//pings/` [Get a list of check's status changes](#list-flips) | `GET SITE_ROOT/api/v1/checks//flips/`
    `GET SITE_ROOT/api/v1/checks//flips/` [Get a list of existing integrations](#list-channels) | `GET SITE_ROOT/api/v1/channels/` +[Get project's badges](#list-badges) | `GET SITE_ROOT/api/v1/badges/` ## Authentication @@ -33,6 +34,7 @@ read-only key * [Get a list of existing checks](#list-checks) * [Get a single check](#get-check) * [Get a list of check's status changes](#list-flips) + * [Get project's badges](#list-badges) Omits sensitive information from the API responses. See the documentation of individual API endpoints for details. @@ -946,3 +948,65 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/channels/ ] } ``` + +## Get Project's Badges {: #list-badges .rule } + +`GET SITE_ROOT/api/v1/badges/` + +Returns a map of all tags in the project, with badge URLs for each tag. + +### Response Codes + +200 OK +: The request succeeded. + +401 Unauthorized +: The API key is either missing or invalid. + +### Example Request + +```bash +curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/badges/ +``` + +### Example Response + +```json +{ + "badges": { + "backup": { + "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.svg", + "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.svg", + "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.json", + "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.json", + "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.shields", + "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.shields" + }, + "db": { + "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.svg", + "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.svg", + "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.json", + "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.json", + "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.shields", + "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.shields" + }, + "production": { + "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.svg", + "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.svg", + "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.json", + "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.json", + "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.shields", + "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.shields" + }, + "*": { + "svg": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.svg", + "svg3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.svg", + "json": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.json", + "json3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.json", + "shields": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.shields", + "shields3": "SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.shields" + } + } +} +``` +