From 9d6ed7e74c3a42ebf1d9569177381df6a933ade2 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Tue, 10 Jul 2018 15:55:45 +0200 Subject: [PATCH] Better handling of expiration of GCUM and accidents report types. --- README.md | 7 ---- scripts/purge_old_gcum.py | 21 ---------- server/models.py | 1 + server/routes.py | 86 +++++++++++++++++++++++++++++++++++---- src/api/index.js | 4 +- src/i18n/en.js | 4 +- src/i18n/fr.js | 4 +- src/store/actions.js | 2 +- 8 files changed, 86 insertions(+), 43 deletions(-) delete mode 100644 scripts/purge_old_gcum.py diff --git a/README.md b/README.md index 4ca0ef4..1dff964 100644 --- a/README.md +++ b/README.md @@ -80,13 +80,6 @@ adapt its behavior: You can use the `wsgi.py` script at the root of the git repository to serve 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 diff --git a/scripts/purge_old_gcum.py b/scripts/purge_old_gcum.py deleted file mode 100644 index e611d79..0000000 --- a/scripts/purge_old_gcum.py +++ /dev/null @@ -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() diff --git a/server/models.py b/server/models.py index a4e12dc..e5dbc3b 100644 --- a/server/models.py +++ b/server/models.py @@ -43,6 +43,7 @@ class Report(BaseModel): datetime = peewee.DateTimeField( default=lambda: arrow.utcnow().replace(microsecond=0).datetime ) + expiration_datetime = peewee.DateTimeField(null=True) is_open = peewee.BooleanField(default=True) upvotes = peewee.IntegerField(default=0) downvotes = peewee.IntegerField(default=0) diff --git a/server/routes.py b/server/routes.py index d562598..bae2db4 100644 --- a/server/routes.py +++ b/server/routes.py @@ -3,6 +3,7 @@ """ Routes definitions """ +import arrow import json import bottle @@ -11,14 +12,9 @@ from server.models import Report from server import jsonapi -@bottle.route('/api/v1/reports', ["GET", "OPTIONS"]) -def get_reports(): +def get_reports(only_active=False): """ - API v1 GET reports route. - - Example:: - - GET /api/v1/reports + Get reports for the reports getting routes. .. note:: @@ -57,6 +53,11 @@ def get_reports(): query = Report.select() if 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) if page_number and 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"]) def post_report(): """ @@ -93,11 +157,17 @@ def post_report(): return jsonapi.JsonApiError(400, "Invalid JSON payload: " + str(exc)) try: - r = Report.create( + r = Report( type=payload['type'], lat=payload['lat'], 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: return jsonapi.JsonApiError(400, "Invalid report payload: " + str(exc)) diff --git a/src/api/index.js b/src/api/index.js index 16b76ef..f8afbe3 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -21,8 +21,8 @@ export function saveReport(type, lat, lng) { }); } -export function getReports() { - return fetch(`${BASE_URL}api/v1/reports`) +export function getActiveReports() { + return fetch(`${BASE_URL}api/v1/reports/active`) .then(response => response.json()) .then(response => response.data) .catch((exc) => { diff --git a/src/i18n/en.js b/src/i18n/en.js index fc44e65..ca1ba6d 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -34,9 +34,9 @@ export default { }, reportLabels: { accident: 'Accident', - accidentDescription: 'Any accident on the road', + accidentDescription: 'Any accident on the road (active for one hour).', 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', interruptDescription: 'An interruption of the bike lane (works, unexpected end of the bike lane, etc.).', misc: 'Other', diff --git a/src/i18n/fr.js b/src/i18n/fr.js index 1e1171b..f41b36d 100644 --- a/src/i18n/fr.js +++ b/src/i18n/fr.js @@ -34,9 +34,9 @@ export default { }, reportLabels: { accident: 'Accident', - accidentDescription: 'Un accident sur la route.', + accidentDescription: 'Un accident sur la route (actif pour une heure).', 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', interruptDescription: "Une interruption d'itinéraire cyclable (travaux, arrêt inattendu d'une piste cyclable, etc)", misc: 'Autre', diff --git a/src/store/actions.js b/src/store/actions.js index bc83fd9..0c674fd 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -14,7 +14,7 @@ import { export function fetchReports({ commit }) { commit(IS_LOADING); - return api.getReports() + return api.getActiveReports() .then(reports => commit(STORE_REPORTS, { reports })) .finally(() => commit(IS_DONE_LOADING)); }