From 927f7a2309a98138586acf0c10840bdbef0c7c2e Mon Sep 17 00:00:00 2001
From: Sean Anderson
Date: Mon, 19 Feb 2024 17:30:29 -0500
Subject: [PATCH] site: api: Add a next_page field as the canonical next page
Supply a next_page field which fills in the parameters automatically. This
will let us move away from limit/offset paging if I ever get around to
doing it. Undocument offset as the canonical way is now next_page. The
implementation itself is almost directly lifted from the existing
navigation macro.
Signed-off-by: Sean Anderson
---
test/site_test.py | 35 +++++++++++++++++++---------------
trends/site/api.py | 13 ++++++++++++-
trends/site/templates/api.html | 20 +++++++++----------
3 files changed, 41 insertions(+), 27 deletions(-)
diff --git a/test/site_test.py b/test/site_test.py
index 444bada..e0047d4 100644
--- a/test/site_test.py
+++ b/test/site_test.py
@@ -232,9 +232,10 @@ def test_api_logs(client):
def get(**params):
resp = client.get("api/v1/logs", query_string=params)
assert resp.status_code == 200
- return resp.json['logs']
+ resp = resp.json
+ return resp['logs'], resp['next_page']
- logs = get()
+ logs, next_page = get()
valid_keys = {
'demoid',
'duplicate_of',
@@ -280,22 +281,26 @@ def get(**params):
updated_pivot = max(updated_pivot, log['updated'])
assert logs == sorted(logs, key=lambda log: log['logid'], reverse=True)
+ assert next_page is None
def paged(limit=10):
off = 0
while True:
- logs = get(limit=limit, offset=off)
+ logs, next_page = get(limit=limit, offset=off)
assert len(logs) <= limit
yield from logs
if len(logs) < limit:
+ assert next_page is None
return
+
off += limit
+ assert next_page == f"/api/v1/logs?limit={limit}&offset={off}"
assert logs == list(paged())
- assert get(offset=len(logs)) == []
+ assert get(offset=len(logs)) == ([], None)
- for log in get(view='players'):
+ for log in get(view='players')[0]:
assert set(log.keys()) == valid_keys | { 'red', 'blue' }
for team in ('red', 'blue'):
team = log[team]
@@ -315,32 +320,32 @@ def paged(limit=10):
for by in ('logid', 'date', 'duration'):
key = lambda log: log['time' if by == 'date' else by]
for reverse in (True, False):
- logs = get(sort=by, sort_dir='desc' if reverse else 'asc')
+ logs, _ = get(sort=by, sort_dir='desc' if reverse else 'asc')
assert logs == sorted(logs, key=key, reverse=reverse)
- for log in get(league="etf2l"):
+ for log in get(league="etf2l")[0]:
assert log['league'] == "etf2l"
- for log in get(format="sixes"):
+ for log in get(format="sixes")[0]:
assert log['format'] == "sixes"
- for log in get(title="serveme"):
+ for log in get(title="serveme")[0]:
assert "serveme" in log['title']
- for log in get(map="cp"):
+ for log in get(map="cp")[0]:
assert "cp" in log['map']
- for log in get(date_from='2019-11-06', timezone='America/New_York'):
+ for log in get(date_from='2019-11-06', timezone='America/New_York')[0]:
assert log['time'] >= 1573016400
- for log in get(date_to='2019-11-06', timezone='America/New_York'):
+ for log in get(date_to='2019-11-06', timezone='America/New_York')[0]:
assert log['time'] <= 1573016400
- for log in get(time_from=1573016400):
+ for log in get(time_from=1573016400)[0]:
assert log['time'] >= 1573016400
- for log in get(time_to=1573016400):
+ for log in get(time_to=1573016400)[0]:
assert log['time'] <= 1573016400
- for log in get(updated_since=updated_pivot):
+ for log in get(updated_since=updated_pivot)[0]:
assert log['updated'] > updated_pivot
diff --git a/trends/site/api.py b/trends/site/api.py
index bf1d023..ad67420 100644
--- a/trends/site/api.py
+++ b/trends/site/api.py
@@ -24,12 +24,23 @@ def do_cache(resp):
resp.cache_control.max_age = 300
return resp
+def next_page(rows):
+ args = flask.request.args.copy()
+ args.update(flask.request.view_args)
+
+ limit, offset = flask.g.page
+ args['limit'] = limit
+ if len(rows) == limit:
+ args['offset'] = offset + limit
+ return flask.url_for(flask.request.endpoint, **args)
+
@api.route('/logs')
def logs():
if resp := logs_last_modified():
return resp
view = flask.request.args.get('view', 'basic', str)
- return flask.jsonify(logs=[dict(log) for log in get_logs(view)])
+ logs = [dict(log) for log in get_logs(view)]
+ return flask.jsonify(logs=logs, next_page=next_page(logs))
@api.route('/maps')
def maps():
diff --git a/trends/site/templates/api.html b/trends/site/templates/api.html
index 9a143ba..4912a62 100644
--- a/trends/site/templates/api.html
+++ b/trends/site/templates/api.html
@@ -171,15 +171,6 @@ Parameters
control-point logs.
- {{ paramdef("offset") }}
-
- Skip results equal to the value of this parameter. For example, supplying
- {{ pre("limit=10") }} and {{ pre("offset=20") }} to the {{ apiref('logs') }} endpoint would
- return 10 matches starting from the
- 21st match. This may be used to request successive pages from an endpoint.
- The default offset is 0.
-
-
{{ paramdef("sort") }}
This parameter specifies the key to use when sorting the output. The valid keys vary between
@@ -284,6 +275,13 @@
Members
assigned by their respective {{ fieldref('league') }}s.
+ {{ fielddef('next_page') }}
+
+ A relative URL to the next page of results. If the number of returned results is equal
+ to the {{ paramref('limit') }}, then this field may be non-{{ pre("null") }} even if the
+ next page is empty.
+
+
{{ fielddef('steamid64') }}
A unique number identifying a player. This is assigned by
@@ -323,7 +321,6 @@
Parameters
{{ paramref("league") }}
{{ paramref("limit") }}
{{ paramref("map") }}
- {{ paramref("offset") }}
{{ paramref("sort") }}, with valid values being:
@@ -431,7 +428,8 @@ Response
-
+ {{ pre('next_page') }} : option(string)
+ The {{ fieldref('next_page') }} of logs, if any
{% endblock %}