Drop the active reports API route and use API filtering abilities
instead Fix for #49.
This commit is contained in:
parent
0f03700f71
commit
4345c3f4e3
@ -23,6 +23,8 @@ versions, meaning that the `0.3.py` script handles the migration of the
|
|||||||
database from the version immediately before `0.3` to the `0.3` version of the
|
database from the version immediately before `0.3` to the `0.3` version of the
|
||||||
app.
|
app.
|
||||||
|
|
||||||
|
**Always** make a backup of your database prior to running a migration on it.
|
||||||
|
|
||||||
If you upgrade through several versions at once, you should run all the
|
If you upgrade through several versions at once, you should run all the
|
||||||
migrations scripts for all the intermediate versions, in the ascending order.
|
migrations scripts for all the intermediate versions, in the ascending order.
|
||||||
There are currently no automated way to handle the updates of the database
|
There are currently no automated way to handle the updates of the database
|
||||||
|
@ -56,11 +56,11 @@ If you want to make an `OR` condition, satisfying either one of the filters,
|
|||||||
you should make two different API calls.
|
you should make two different API calls.
|
||||||
|
|
||||||
|
|
||||||
#### Commplex filtering
|
#### Complex filtering
|
||||||
|
|
||||||
You can also use more complex elaborations through the syntax
|
You can also use more complex elaborations through the syntax
|
||||||
`filter[FIELD][OPERATION]=VALUE`. The available operations are `eq` (equal,
|
`filter[FIELD][OPERATOR]=VALUE`. The available operators are `eq` (equal,
|
||||||
default operation), `ne` (not equal), `gt` (greater than), `ge` (greater or
|
default operator), `ne` (not equal), `gt` (greater than), `ge` (greater or
|
||||||
equal), `lt` (lower than) and `le` (lower or equal). For instance,
|
equal), `lt` (lower than) and `le` (lower or equal). For instance,
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -75,3 +75,14 @@ reports in a geographical bounding box:
|
|||||||
```
|
```
|
||||||
> GET /api/v1/reports?filter[lat][gt]=LAT_MIN&filter[lat][lt]=LAT_MAX&filter[lng][gt]=LNG_MIN&filter[lng][lt]=LNG_MAX
|
> GET /api/v1/reports?filter[lat][gt]=LAT_MIN&filter[lat][lt]=LAT_MAX&filter[lng][gt]=LNG_MIN&filter[lng][lt]=LNG_MAX
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All operators can have a trailing `?` to make them consider fields without
|
||||||
|
value as matching the condition as well. This means you can use something like
|
||||||
|
|
||||||
|
```
|
||||||
|
> /api/v1/reports?filter[expiration_datetime][gt?]=2018-10-18T09:06:38.538375
|
||||||
|
```
|
||||||
|
|
||||||
|
to get the all the reports with an expiration datetime in the future or no
|
||||||
|
expiration datetime, that is all currently active reports, assuming the
|
||||||
|
current datetime is `2018-10-18T09:06:38.538375`.
|
||||||
|
@ -29,6 +29,7 @@ def run_migration():
|
|||||||
migrate(
|
migrate(
|
||||||
migrator.add_column('report', 'first_report_datetime',
|
migrator.add_column('report', 'first_report_datetime',
|
||||||
peewee.DateTimeField(default=UTC_now)),
|
peewee.DateTimeField(default=UTC_now)),
|
||||||
|
migrator.drop_column('report', 'is_open'),
|
||||||
)
|
)
|
||||||
query = Report.select()
|
query = Report.select()
|
||||||
for report in query:
|
for report in query:
|
||||||
|
@ -10,7 +10,7 @@ import re
|
|||||||
|
|
||||||
import bottle
|
import bottle
|
||||||
|
|
||||||
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+?)\](\[([A-z0-9_]+)\])?")
|
FILTER_RE = re.compile(r"filter\[([A-z0-9_]+?)\](\[([A-z0-9_]+\??)\])?")
|
||||||
|
|
||||||
|
|
||||||
class DateAwareJSONEncoder(json.JSONEncoder):
|
class DateAwareJSONEncoder(json.JSONEncoder):
|
||||||
@ -76,21 +76,36 @@ def JsonApiParseQuery(query, model, default_sorting=None):
|
|||||||
for value in query.getall(param):
|
for value in query.getall(param):
|
||||||
# Handle operation
|
# Handle operation
|
||||||
operation = filter_match.group(3)
|
operation = filter_match.group(3)
|
||||||
if operation == 'eq' or operation is None:
|
if operation is None:
|
||||||
filters.append(field == value)
|
# Default operation is 'eq'
|
||||||
|
operation = 'eq'
|
||||||
|
|
||||||
|
# Handle '?' modifier
|
||||||
|
modifier_filter = None
|
||||||
|
if operation.endswith('?'):
|
||||||
|
modifier_filter = (field == None)
|
||||||
|
operation = operation.rstrip('?')
|
||||||
|
|
||||||
|
if operation == 'eq':
|
||||||
|
operation_filter = (field == value)
|
||||||
elif operation == 'ne':
|
elif operation == 'ne':
|
||||||
filters.append(field != value)
|
operation_filter = (field != value)
|
||||||
elif operation == 'gt':
|
elif operation == 'gt':
|
||||||
filters.append(field > value)
|
operation_filter = (field > value)
|
||||||
elif operation == 'ge':
|
elif operation == 'ge':
|
||||||
filters.append(field >= value)
|
operation_filter = (field >= value)
|
||||||
elif operation == 'lt':
|
elif operation == 'lt':
|
||||||
filters.append(field < value)
|
operation_filter = (field < value)
|
||||||
elif operation == 'le':
|
elif operation == 'le':
|
||||||
filters.append(field <= value)
|
operation_filter = (field <= value)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid filtering operator provided.")
|
raise ValueError("Invalid filtering operator provided.")
|
||||||
|
|
||||||
|
if modifier_filter:
|
||||||
|
filters.append(modifier_filter | operation_filter)
|
||||||
|
else:
|
||||||
|
filters.append(operation_filter)
|
||||||
|
|
||||||
# Handle pagination according to JSON API spec
|
# Handle pagination according to JSON API spec
|
||||||
page_number, page_size = 0, None
|
page_number, page_size = 0, None
|
||||||
try:
|
try:
|
||||||
|
@ -48,7 +48,6 @@ class Report(BaseModel):
|
|||||||
default=UTC_now
|
default=UTC_now
|
||||||
)
|
)
|
||||||
expiration_datetime = peewee.DateTimeField(null=True)
|
expiration_datetime = peewee.DateTimeField(null=True)
|
||||||
is_open = peewee.BooleanField(default=True)
|
|
||||||
upvotes = peewee.IntegerField(default=0)
|
upvotes = peewee.IntegerField(default=0)
|
||||||
downvotes = peewee.IntegerField(default=0)
|
downvotes = peewee.IntegerField(default=0)
|
||||||
|
|
||||||
|
142
server/routes.py
142
server/routes.py
@ -37,9 +37,34 @@ def check_auth():
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def get_reports(only_active=False):
|
@bottle.route('/api/v1/reports', ["GET", "OPTIONS"])
|
||||||
|
def get_all_reports():
|
||||||
"""
|
"""
|
||||||
Get reports for the reports getting routes.
|
API v1 GET reports route. Get all reports.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
> GET /api/v1/reports
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"attributes": {
|
||||||
|
"expiration_datetime": null,
|
||||||
|
"downvotes": 0,
|
||||||
|
"datetime": "2018-06-27T16:44:12+00:00",
|
||||||
|
"lat": 48.842005,
|
||||||
|
"upvotes": 1,
|
||||||
|
"lng": 2.386278,
|
||||||
|
"type": "interrupt",
|
||||||
|
…
|
||||||
|
},
|
||||||
|
"type": "reports",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
…
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -78,11 +103,6 @@ def get_reports(only_active=False):
|
|||||||
query = Report.select()
|
query = Report.select()
|
||||||
if filters:
|
if filters:
|
||||||
query = query.where(*filters)
|
query = query.where(*filters)
|
||||||
if only_active:
|
|
||||||
query = query.where(
|
|
||||||
(Report.expiration_datetime == None) |
|
|
||||||
(Report.expiration_datetime > UTC_now())
|
|
||||||
)
|
|
||||||
query = query.order_by(*sorting)
|
query = query.order_by(*sorting)
|
||||||
if page_number and page_size:
|
if page_number and page_size:
|
||||||
query = query.paginate(page_number, page_size)
|
query = query.paginate(page_number, page_size)
|
||||||
@ -95,111 +115,6 @@ def get_reports(only_active=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@bottle.route('/api/v1/reports', ["GET", "OPTIONS"])
|
|
||||||
def get_all_reports():
|
|
||||||
"""
|
|
||||||
API v1 GET reports route. Get all reports.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
> GET /api/v1/reports
|
|
||||||
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"expiration_datetime": null,
|
|
||||||
"downvotes": 0,
|
|
||||||
"datetime": "2018-06-27T16:44:12+00:00",
|
|
||||||
"is_open": true,
|
|
||||||
"lat": 48.842005,
|
|
||||||
"upvotes": 1,
|
|
||||||
"lng": 2.386278,
|
|
||||||
"type": "interrupt",
|
|
||||||
…
|
|
||||||
},
|
|
||||||
"type": "reports",
|
|
||||||
"id": 1
|
|
||||||
},
|
|
||||||
…
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Filtering can be done through the ``filter`` GET param, according
|
|
||||||
to JSON API spec (http://jsonapi.org/recommendations/#filtering).
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
By default no pagination is done. Pagination can be forced using
|
|
||||||
``page[size]`` to specify a number of items per page and
|
|
||||||
``page[number]`` to specify which page to return. Pages are numbered
|
|
||||||
starting from 0.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Sorting can be handled through the ``sort`` GET param, according to
|
|
||||||
JSON API spec (http://jsonapi.org/format/#fetching-sorting).
|
|
||||||
|
|
||||||
:return: The available reports objects in a JSON ``data`` dict.
|
|
||||||
"""
|
|
||||||
return get_reports(only_active=False)
|
|
||||||
|
|
||||||
|
|
||||||
@bottle.route('/api/v1/reports/active', ["GET", "OPTIONS"])
|
|
||||||
def get_active_reports():
|
|
||||||
"""
|
|
||||||
API v1 GET reports route. Only get active reports (those with
|
|
||||||
``expiration_datetime`` in the future).
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
> GET /api/v1/reports/active
|
|
||||||
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"expiration_datetime": null,
|
|
||||||
"downvotes": 0,
|
|
||||||
"datetime": "2018-06-27T16:44:12+00:00",
|
|
||||||
"is_open": true,
|
|
||||||
"lat": 48.842005,
|
|
||||||
"upvotes": 1,
|
|
||||||
"lng": 2.386278,
|
|
||||||
"type": "interrupt",
|
|
||||||
…
|
|
||||||
},
|
|
||||||
"type": "reports",
|
|
||||||
"id": 1
|
|
||||||
},
|
|
||||||
…
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Filtering can be done through the ``filter`` GET param, according
|
|
||||||
to JSON API spec (http://jsonapi.org/recommendations/#filtering).
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
By default no pagination is done. Pagination can be forced using
|
|
||||||
``page[size]`` to specify a number of items per page and
|
|
||||||
``page[number]`` to specify which page to return. Pages are numbered
|
|
||||||
starting from 0.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Sorting can be handled through the ``sort`` GET param, according to
|
|
||||||
JSON API spec (http://jsonapi.org/format/#fetching-sorting).
|
|
||||||
|
|
||||||
:return: The available reports objects in a JSON ``data`` dict.
|
|
||||||
"""
|
|
||||||
return get_reports(only_active=True)
|
|
||||||
|
|
||||||
|
|
||||||
@bottle.route('/api/v1/reports', ["POST", "OPTIONS"])
|
@bottle.route('/api/v1/reports', ["POST", "OPTIONS"])
|
||||||
def post_report():
|
def post_report():
|
||||||
"""
|
"""
|
||||||
@ -225,7 +140,6 @@ def post_report():
|
|||||||
"upvotes": 0,
|
"upvotes": 0,
|
||||||
"lng": 2.385234797066081,
|
"lng": 2.385234797066081,
|
||||||
"type": "pothole",
|
"type": "pothole",
|
||||||
"is_open": true,
|
|
||||||
…
|
…
|
||||||
},
|
},
|
||||||
"type": "reports",
|
"type": "reports",
|
||||||
@ -290,7 +204,6 @@ def upvote_report(id):
|
|||||||
"upvotes": 1,
|
"upvotes": 1,
|
||||||
"lng": 2.385234797066081,
|
"lng": 2.385234797066081,
|
||||||
"type": "pothole",
|
"type": "pothole",
|
||||||
"is_open": true,
|
|
||||||
…
|
…
|
||||||
},
|
},
|
||||||
"type": "reports",
|
"type": "reports",
|
||||||
@ -349,7 +262,6 @@ def downvote_report(id):
|
|||||||
"upvotes": 0,
|
"upvotes": 0,
|
||||||
"lng": 2.385234797066081,
|
"lng": 2.385234797066081,
|
||||||
"type": "pothole",
|
"type": "pothole",
|
||||||
"is_open": true,
|
|
||||||
…
|
…
|
||||||
},
|
},
|
||||||
"type": "reports",
|
"type": "reports",
|
||||||
|
@ -24,7 +24,7 @@ export function saveReport(type, lat, lng) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getActiveReports() {
|
export function getActiveReports() {
|
||||||
return fetch(`${BASE_URL}api/v1/reports/active`)
|
return fetch(`${BASE_URL}api/v1/reports?filter[expiration_datetime][gt?]=${(new Date()).toISOString()}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch((exc) => {
|
.catch((exc) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user