Browse Source

Add /api/v1/badges/ endpoint

cc: #552
pull/555/head
Pēteris Caune 3 years ago
parent
commit
98eb7cc14a
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
6 changed files with 192 additions and 1 deletions
  1. +3
    -0
      CHANGELOG.md
  2. +43
    -0
      hc/api/tests/test_get_badges.py
  3. +1
    -0
      hc/api/urls.py
  4. +23
    -1
      hc/api/views.py
  5. +58
    -0
      templates/docs/api.html
  6. +64
    -0
      templates/docs/api.md

+ 3
- 0
CHANGELOG.md View File

@ -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


+ 43
- 0
hc/api/tests/test_get_badges.py View File

@ -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)

+ 1
- 0
hc/api/urls.py View File

@ -46,6 +46,7 @@ urlpatterns = [
path("api/v1/checks/<uuid:code>/flips/", views.flips_by_uuid, name="hc-api-flips"),
path("api/v1/checks/<sha1:unique_key>/flips/", views.flips_by_unique_key),
path("api/v1/channels/", views.channels),
path("api/v1/badges/", views.badges),
path(
"badge/<slug:badge_key>/<slug:signature>/<quoted:tag>.<slug:fmt>",
views.badge,


+ 23
- 1
hc/api/views.py View File

@ -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):


+ 58
- 0
templates/docs/api.html View File

@ -46,6 +46,10 @@ in your account.</p>
<td><a href="#list-channels">Get a list of existing integrations</a></td>
<td><code>GET SITE_ROOT/api/v1/channels/</code></td>
</tr>
<tr>
<td><a href="#list-badges">Get project's badges</a></td>
<td><code>GET SITE_ROOT/api/v1/badges/</code></td>
</tr>
</tbody>
</table>
<h2>Authentication</h2>
@ -63,6 +67,7 @@ and read-only API keys on the <strong>Project Settings</strong> page.</p>
<li><a href="#list-checks">Get a list of existing checks</a></li>
<li><a href="#get-check">Get a single check</a></li>
<li><a href="#list-flips">Get a list of check's status changes</a></li>
<li><a href="#list-badges">Get project's badges</a></li>
</ul>
<p>Omits sensitive information from the API responses. See the documentation of
individual API endpoints for details.</p>
@ -807,4 +812,57 @@ number of returned pings depends on the account's billing plan: 100 for free acc
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h2 class="rule" id="list-badges">Get Project's Badges</h2>
<p><code>GET SITE_ROOT/api/v1/badges/</code></p>
<p>Returns a map of all tags in the project, with badge URLs for each tag.</p>
<h3>Response Codes</h3>
<dl>
<dt>200 OK</dt>
<dd>The request succeeded.</dd>
<dt>401 Unauthorized</dt>
<dd>The API key is either missing or invalid.</dd>
</dl>
<h3>Example Request</h3>
<div class="highlight"><pre><span></span><code>curl --header <span class="s2">&quot;X-Api-Key: your-api-key&quot;</span> SITE_ROOT/api/v1/badges/
</code></pre></div>
<h3>Example Response</h3>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
<span class="nt">&quot;badges&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;backup&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;svg&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;svg3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;json&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;json3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M-2/backup.shields&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/LOegDs5M/backup.shields&quot;</span>
<span class="p">},</span>
<span class="nt">&quot;db&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;svg&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;svg3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;json&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;json3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm-2/db.shields&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/99MuQaKm/db.shields&quot;</span>
<span class="p">},</span>
<span class="nt">&quot;production&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;svg&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;svg3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;json&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;json3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8-2/production.shields&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/1TEhqie8/production.shields&quot;</span>
<span class="p">},</span>
<span class="nt">&quot;*&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;svg&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;svg3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.svg&quot;</span><span class="p">,</span>
<span class="nt">&quot;json&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;json3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.json&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe-2.shields&quot;</span><span class="p">,</span>
<span class="nt">&quot;shields3&quot;</span><span class="p">:</span> <span class="s2">&quot;SITE_ROOT/badge/67541b37-8b9c-4d17-b952-690eae/9X7kcZoe.shields&quot;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>

+ 64
- 0
templates/docs/api.md View File

@ -16,6 +16,7 @@ Endpoint Name | Endpoint Address
[Get a list of check's logged pings](#list-pings) | `GET SITE_ROOT/api/v1/checks/<uuid>/pings/`
[Get a list of check's status changes](#list-flips) | `GET SITE_ROOT/api/v1/checks/<uuid>/flips/`<br>`GET SITE_ROOT/api/v1/checks/<unique_key>/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"
}
}
}
```

Loading…
Cancel
Save