Browse Source

Implementing changes, documentation and tests

pull/372/head
James Kirsop 5 years ago
parent
commit
f1acd7a3f4
7 changed files with 68 additions and 8 deletions
  1. +2
    -0
      CHANGELOG.md
  2. +23
    -0
      hc/api/migrations/0071_check_unique_key.py
  3. +7
    -4
      hc/api/models.py
  4. +17
    -0
      hc/api/tests/test_get_check.py
  5. +11
    -0
      hc/api/urls.py
  6. +4
    -1
      hc/api/views.py
  7. +4
    -3
      templates/docs/api.md

+ 2
- 0
CHANGELOG.md View File

@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file.
- Add a "Transfer Ownership" feature in Project Settings - Add a "Transfer Ownership" feature in Project Settings
- In checks list, the pause button asks for confirmation (#356) - In checks list, the pause button asks for confirmation (#356)
- Added /api/v1/metrics/ endpoint, useful for monitoring the service itself - Added /api/v1/metrics/ endpoint, useful for monitoring the service itself
- Migrated the `unique_key` field on a Check from a Python property to a database property
- Allowed the /api/v1/checks/ endpoint to receive a UUID or `unique_key`
### Bug Fixes ### Bug Fixes
- "Get a single check" API call now supports read-only API keys (#346) - "Get a single check" API call now supports read-only API keys (#346)


+ 23
- 0
hc/api/migrations/0071_check_unique_key.py View File

@ -0,0 +1,23 @@
# Generated by Django 3.0.4 on 2020-05-28 03:20
from django.db import migrations, models
from hc.api.models import Check
def generate_unique_keys(apps, schema_editor):
for o in Check.objects.all():
o.save()
class Migration(migrations.Migration):
dependencies = [
('api', '0070_auto_20200411_1310'),
]
operations = [
migrations.AddField(
model_name='check',
name='unique_key',
field=models.CharField(blank=True, max_length=40),
),
migrations.RunPython(generate_unique_keys),
]

+ 7
- 4
hc/api/models.py View File

@ -84,6 +84,7 @@ class Check(models.Model):
has_confirmation_link = models.BooleanField(default=False) has_confirmation_link = models.BooleanField(default=False)
alert_after = models.DateTimeField(null=True, blank=True, editable=False) alert_after = models.DateTimeField(null=True, blank=True, editable=False)
status = models.CharField(max_length=6, choices=STATUSES, default="new") status = models.CharField(max_length=6, choices=STATUSES, default="new")
unique_key = models.CharField(max_length=40, blank=True)
class Meta: class Meta:
indexes = [ indexes = [
@ -96,6 +97,12 @@ class Check(models.Model):
) )
] ]
def save(self, *args, **kwargs):
if not self.unique_key:
code_half = self.code.hex[:16]
self.unique_key = hashlib.sha1(code_half.encode()).hexdigest()
super().save(*args,**kwargs)
def __str__(self): def __str__(self):
return "%s (%d)" % (self.name or self.code, self.id) return "%s (%d)" % (self.name or self.code, self.id)
@ -200,10 +207,6 @@ class Check(models.Model):
codes = self.channel_set.order_by("code").values_list("code", flat=True) codes = self.channel_set.order_by("code").values_list("code", flat=True)
return ",".join(map(str, codes)) return ",".join(map(str, codes))
@property
def unique_key(self):
code_half = self.code.hex[:16]
return hashlib.sha1(code_half.encode()).hexdigest()
def to_dict(self, readonly=False): def to_dict(self, readonly=False):


+ 17
- 0
hc/api/tests/test_get_check.py View File

@ -53,6 +53,23 @@ class GetCheckTestCase(BaseTestCase):
r = self.get(made_up_code) r = self.get(made_up_code)
self.assertEqual(r.status_code, 404) self.assertEqual(r.status_code, 404)
def test_it_handles_unique_key(self):
r = self.get(self.a1.unique_key)
self.assertEqual(r.status_code, 200)
self.assertEqual(r["Access-Control-Allow-Origin"], "*")
doc = r.json()
self.assertEqual(len(doc), 13)
self.assertEqual(doc["timeout"], 3600)
self.assertEqual(doc["grace"], 900)
self.assertEqual(doc["ping_url"], self.a1.url())
self.assertEqual(doc["last_ping"], None)
self.assertEqual(doc["n_pings"], 0)
self.assertEqual(doc["status"], "new")
self.assertEqual(doc["channels"], str(self.c1.code))
self.assertEqual(doc["desc"], "This is description")
def test_readonly_key_works(self): def test_readonly_key_works(self):
self.project.api_key_readonly = "R" * 32 self.project.api_key_readonly = "R" * 32
self.project.save() self.project.save()


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

@ -13,8 +13,18 @@ class QuoteConverter:
def to_url(self, value): def to_url(self, value):
return quote(value, safe="") return quote(value, safe="")
class UniqueKeyConverter:
regex = "[A-z0-9]{40}"
def to_python(self, value):
return value
def to_url(self, value):
return value
register_converter(QuoteConverter, "quoted") register_converter(QuoteConverter, "quoted")
register_converter(UniqueKeyConverter, "unique_key")
urlpatterns = [ urlpatterns = [
path("ping/<uuid:code>/", views.ping, name="hc-ping-slash"), path("ping/<uuid:code>/", views.ping, name="hc-ping-slash"),
@ -23,6 +33,7 @@ urlpatterns = [
path("ping/<uuid:code>/start", views.ping, {"action": "start"}, name="hc-start"), path("ping/<uuid:code>/start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks), path("api/v1/checks/", views.checks),
path("api/v1/checks/<uuid:code>", views.single, name="hc-api-single"), path("api/v1/checks/<uuid:code>", views.single, name="hc-api-single"),
path("api/v1/checks/<unique_key:code>", views.single, name="hc-api-single"),
path("api/v1/checks/<uuid:code>/pause", views.pause, name="hc-api-pause"), path("api/v1/checks/<uuid:code>/pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications/<uuid:code>/bounce", views.bounce, name="hc-api-bounce"), path("api/v1/notifications/<uuid:code>/bounce", views.bounce, name="hc-api-bounce"),
path("api/v1/channels/", views.channels), path("api/v1/channels/", views.channels),


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

@ -181,7 +181,10 @@ def channels(request):
@validate_json() @validate_json()
@authorize_read @authorize_read
def get_check(request, code): def get_check(request, code):
check = get_object_or_404(Check, code=code)
if type(code) == str:
check = get_object_or_404(Check, unique_key=code)
else:
check = get_object_or_404(Check, code=code)
if check.project != request.project: if check.project != request.project:
return HttpResponseForbidden() return HttpResponseForbidden()


+ 4
- 3
templates/docs/api.md View File

@ -9,6 +9,7 @@ Endpoint Name | Endpoint Address
------------------------------------------------------|------- ------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/` [Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/`
[Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/<uuid>` [Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/<uuid>`
[Get a single check (using Read Only API)](#get-check) | `GET SITE_ROOT/api/v1/checks/<unique_key>`
[Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/` [Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/`
[Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/<uuid>` [Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/<uuid>`
[Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks/<uuid>/pause` [Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks/<uuid>/pause`
@ -125,7 +126,7 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
When using the read-only API key, the following fields are omitted: When using the read-only API key, the following fields are omitted:
`ping_url`, `update_url`, `pause_url`, `channels`. An extra `unique_key` field `ping_url`, `update_url`, `pause_url`, `channels`. An extra `unique_key` field
is added. This identifier is stable across API calls. Example:
is added which can be used [to `GET` a check](#get-check) in place of the `UUID`. The `unique_key` is stable across API calls. Example:
```json ```json
{ {
@ -160,9 +161,9 @@ is added. This identifier is stable across API calls. Example:
``` ```
## Get a Single Check {: #get-check .rule } ## Get a Single Check {: #get-check .rule }
`GET SITE_ROOT/api/v1/checks/<uuid>`
`GET SITE_ROOT/api/v1/checks/<uuid>` OR `GET SITE_ROOT/api/v1/checks/<unique_key>`
Returns a JSON representation of a single check.
Returns a JSON representation of a single check. Can take either the UUID or the `unique_key` (see [information above](#list-checks)) as the identifier of the check to return.
### Response Codes ### Response Codes


Loading…
Cancel
Save