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 ## v1.23.0 - Unreleased
### Improvements
- Add /api/v1/badges/ endpoint (#552)
### Bug Fixes ### Bug Fixes
- Add handling for non-latin-1 characters in webhook headers - Add handling for non-latin-1 characters in webhook headers
- Fix dark mode bug in selectpicker widgets - 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/<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/checks/<sha1:unique_key>/flips/", views.flips_by_unique_key),
path("api/v1/channels/", views.channels), path("api/v1/channels/", views.channels),
path("api/v1/badges/", views.badges),
path( path(
"badge/<slug:badge_key>/<slug:signature>/<quoted:tag>.<slug:fmt>", "badge/<slug:badge_key>/<slug:signature>/<quoted:tag>.<slug:fmt>",
views.badge, 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.decorators import authorize, authorize_read, cors, validate_json
from hc.api.forms import FlipsFiltersForm from hc.api.forms import FlipsFiltersForm
from hc.api.models import MAX_DELTA, Flip, Channel, Check, Notification, Ping 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): class BadChannelException(Exception):
@ -377,6 +377,28 @@ def flips_by_unique_key(request, unique_key):
return HttpResponseNotFound() 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 @never_cache
@cors("GET") @cors("GET")
def badge(request, badge_key, signature, tag, fmt): 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><a href="#list-channels">Get a list of existing integrations</a></td>
<td><code>GET SITE_ROOT/api/v1/channels/</code></td> <td><code>GET SITE_ROOT/api/v1/channels/</code></td>
</tr> </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> </tbody>
</table> </table>
<h2>Authentication</h2> <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="#list-checks">Get a list of existing checks</a></li>
<li><a href="#get-check">Get a single check</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-flips">Get a list of check's status changes</a></li>
<li><a href="#list-badges">Get project's badges</a></li>
</ul> </ul>
<p>Omits sensitive information from the API responses. See the documentation of <p>Omits sensitive information from the API responses. See the documentation of
individual API endpoints for details.</p> 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> <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> </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 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 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 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 ## Authentication
@ -33,6 +34,7 @@ read-only key
* [Get a list of existing checks](#list-checks) * [Get a list of existing checks](#list-checks)
* [Get a single check](#get-check) * [Get a single check](#get-check)
* [Get a list of check's status changes](#list-flips) * [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 Omits sensitive information from the API responses. See the documentation of
individual API endpoints for details. 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