Better handling of expiration of GCUM and accidents report types.
This commit is contained in:
parent
bc52d4f929
commit
9d6ed7e74c
@ -80,13 +80,6 @@ adapt its behavior:
|
|||||||
You can use the `wsgi.py` script at the root of the git repository to serve
|
You can use the `wsgi.py` script at the root of the git repository to serve
|
||||||
the server side part.
|
the server side part.
|
||||||
|
|
||||||
#### Useful scripts
|
|
||||||
|
|
||||||
The `scripts` folder contain some useful scripts:
|
|
||||||
|
|
||||||
* `purge_old_gcum_and_accidents.py` deletes `gcum` and `accident` type report
|
|
||||||
older than one hour. You should set a crontask to call it regularly.
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# coding: utf-8
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
SCRIPT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
sys.path.append(os.path.abspath(os.path.join(SCRIPT_DIRECTORY, '..')))
|
|
||||||
|
|
||||||
import arrow
|
|
||||||
|
|
||||||
from server.models import db, Report
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
db.connect()
|
|
||||||
one_hour_ago = arrow.utcnow().shift(hours=-1).datetime
|
|
||||||
nb = Report.delete().where(
|
|
||||||
((Report.type == 'accident') | (Report.type == 'gcum')) &
|
|
||||||
(Report.datetime < one_hour_ago)
|
|
||||||
).execute()
|
|
||||||
print("%d accident/GCUM reports purged." % nb)
|
|
||||||
if not db.is_closed():
|
|
||||||
db.close()
|
|
@ -43,6 +43,7 @@ class Report(BaseModel):
|
|||||||
datetime = peewee.DateTimeField(
|
datetime = peewee.DateTimeField(
|
||||||
default=lambda: arrow.utcnow().replace(microsecond=0).datetime
|
default=lambda: arrow.utcnow().replace(microsecond=0).datetime
|
||||||
)
|
)
|
||||||
|
expiration_datetime = peewee.DateTimeField(null=True)
|
||||||
is_open = peewee.BooleanField(default=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)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
Routes definitions
|
Routes definitions
|
||||||
"""
|
"""
|
||||||
|
import arrow
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import bottle
|
import bottle
|
||||||
@ -11,14 +12,9 @@ from server.models import Report
|
|||||||
from server import jsonapi
|
from server import jsonapi
|
||||||
|
|
||||||
|
|
||||||
@bottle.route('/api/v1/reports', ["GET", "OPTIONS"])
|
def get_reports(only_active=False):
|
||||||
def get_reports():
|
|
||||||
"""
|
"""
|
||||||
API v1 GET reports route.
|
Get reports for the reports getting routes.
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
GET /api/v1/reports
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -57,6 +53,11 @@ def get_reports():
|
|||||||
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 > arrow.utcnow().replace(microsecond=0).datetime)
|
||||||
|
)
|
||||||
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)
|
||||||
@ -69,6 +70,69 @@ def get_reports():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@bottle.route('/api/v1/reports', ["GET", "OPTIONS"])
|
||||||
|
def get_all_reports():
|
||||||
|
"""
|
||||||
|
API v1 GET reports route. Get all reports.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
GET /api/v1/reports
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
.. 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():
|
||||||
"""
|
"""
|
||||||
@ -93,11 +157,17 @@ def post_report():
|
|||||||
return jsonapi.JsonApiError(400, "Invalid JSON payload: " + str(exc))
|
return jsonapi.JsonApiError(400, "Invalid JSON payload: " + str(exc))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = Report.create(
|
r = Report(
|
||||||
type=payload['type'],
|
type=payload['type'],
|
||||||
lat=payload['lat'],
|
lat=payload['lat'],
|
||||||
lng=payload['lng']
|
lng=payload['lng']
|
||||||
)
|
)
|
||||||
|
# Handle expiration
|
||||||
|
if r.type in ['accident', 'gcum']:
|
||||||
|
r.expiration_datetime = (
|
||||||
|
arrow.utcnow().replace(microsecond=0).shift(hours=+1).datetime
|
||||||
|
)
|
||||||
|
r.save()
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
return jsonapi.JsonApiError(400, "Invalid report payload: " + str(exc))
|
return jsonapi.JsonApiError(400, "Invalid report payload: " + str(exc))
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ export function saveReport(type, lat, lng) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReports() {
|
export function getActiveReports() {
|
||||||
return fetch(`${BASE_URL}api/v1/reports`)
|
return fetch(`${BASE_URL}api/v1/reports/active`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch((exc) => {
|
.catch((exc) => {
|
||||||
|
@ -34,9 +34,9 @@ export default {
|
|||||||
},
|
},
|
||||||
reportLabels: {
|
reportLabels: {
|
||||||
accident: 'Accident',
|
accident: 'Accident',
|
||||||
accidentDescription: 'Any accident on the road',
|
accidentDescription: 'Any accident on the road (active for one hour).',
|
||||||
gcum: 'GCUM',
|
gcum: 'GCUM',
|
||||||
gcumDescription: 'A car poorly parked on a bike lane. Such reports are automatically deleted after one hour, as they are by nature temporary.',
|
gcumDescription: 'A car poorly parked on a bike lane. Such reports are automatically deleted after one hour, as they are by nature temporary (active for one hour).',
|
||||||
interrupt: 'Interruption',
|
interrupt: 'Interruption',
|
||||||
interruptDescription: 'An interruption of the bike lane (works, unexpected end of the bike lane, etc.).',
|
interruptDescription: 'An interruption of the bike lane (works, unexpected end of the bike lane, etc.).',
|
||||||
misc: 'Other',
|
misc: 'Other',
|
||||||
|
@ -34,9 +34,9 @@ export default {
|
|||||||
},
|
},
|
||||||
reportLabels: {
|
reportLabels: {
|
||||||
accident: 'Accident',
|
accident: 'Accident',
|
||||||
accidentDescription: 'Un accident sur la route.',
|
accidentDescription: 'Un accident sur la route (actif pour une heure).',
|
||||||
gcum: 'GCUM',
|
gcum: 'GCUM',
|
||||||
gcumDescription: "Une voiture (mal) garée sur la piste cyclable. Ces signalements sont automatiquement supprimés au bout d'une heure car ils sont par essence temporaires.",
|
gcumDescription: "Une voiture (mal) garée sur la piste cyclable. Ces signalements sont automatiquement supprimés au bout d'une heure car ils sont par essence temporaires (actif pour une heure).",
|
||||||
interrupt: 'Interruption',
|
interrupt: 'Interruption',
|
||||||
interruptDescription: "Une interruption d'itinéraire cyclable (travaux, arrêt inattendu d'une piste cyclable, etc)",
|
interruptDescription: "Une interruption d'itinéraire cyclable (travaux, arrêt inattendu d'une piste cyclable, etc)",
|
||||||
misc: 'Autre',
|
misc: 'Autre',
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
|
|
||||||
export function fetchReports({ commit }) {
|
export function fetchReports({ commit }) {
|
||||||
commit(IS_LOADING);
|
commit(IS_LOADING);
|
||||||
return api.getReports()
|
return api.getActiveReports()
|
||||||
.then(reports => commit(STORE_REPORTS, { reports }))
|
.then(reports => commit(STORE_REPORTS, { reports }))
|
||||||
.finally(() => commit(IS_DONE_LOADING));
|
.finally(() => commit(IS_DONE_LOADING));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user