diff --git a/README.md b/README.md index ce77ee9..6aaa6d5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,11 @@ could know the location of the displayed map. The data collected by https://cyclo.phyks.me/ is available under an [ODbL](https://opendatacommons.org/licenses/odbl/) license. You can get the -most up to date JSON dump of available reports at https://cyclo.phyks.me/api/v1/reports. +most up to date JSON dump of available reports at +https://cyclo.phyks.me/api/v1/reports. + +Statistics about the instance can be fetched at +https://cyclo.phyks.me/api/v1/stats. ## Hosting your own @@ -78,6 +82,9 @@ python -m server It is better to use a dedicated `virtualenv` if you can :) +API routes are all listed within `server/routes.py` file, with documentation +strings. + #### Useful environment variables You can pass a few environment variables to the `python -m server` command to diff --git a/server/routes.py b/server/routes.py index 124a92c..2eb4554 100644 --- a/server/routes.py +++ b/server/routes.py @@ -264,3 +264,34 @@ def downvote_report(id): return { "data": r.to_json() } + + +@bottle.route('/api/v1/stats', ["GET", "OPTIONS"]) +def get_stats(): + """ + API v1 GET stats about this instance. + + Example:: + + GET /api/v1/states + """ + # Handle CORS + if bottle.request.method == 'OPTIONS': + return {} + + nb_reports = Report.select().count() + nb_active_reports = Report.select().where( + (Report.expiration_datetime == None) | + (Report.expiration_datetime > arrow.utcnow().replace(microsecond=0).datetime) + ).count() + last_added_report_datetime = Report.select().order_by( + Report.datetime.desc() + ).get().datetime + + return { + "data": { + "nb_reports": nb_reports, + "nb_active_reports": nb_active_reports, + "last_added_report_datetime": last_added_report_datetime + } + } diff --git a/src/api/index.js b/src/api/index.js index 4cb0a73..ba9a886 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -36,6 +36,16 @@ export function getActiveReports() { }); } +export function getStats() { + return fetch(`${BASE_URL}api/v1/stats`) + .then(response => response.json()) + .then(response => response.data) + .catch((exc) => { + console.error(`Unable to fetch stats: ${exc}.`); + throw exc; + }); +} + export function downvote(id) { return fetch(`${BASE_URL}api/v1/reports/${id}/downvote`, { method: 'POST', diff --git a/src/components/ReportCard.vue b/src/components/ReportCard.vue index 189a38e..ee564b5 100644 --- a/src/components/ReportCard.vue +++ b/src/components/ReportCard.vue @@ -12,7 +12,7 @@ {{ report.label }} - {{ $t('reportCard.Reported') }} {{ report.fromNow }} + {{ $t('reportCard.Reported', { fromNow: report.fromNow }) }} diff --git a/src/i18n/en.json b/src/i18n/en.json index 442eb8b..7642195 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,7 +1,12 @@ { "about": { "availableReportsTitle": "The available reports so far are:", - "license": "It is released under an MIT license (source code). Icons are based on creations from Wikimedia, Vecteezy, Pixabay or Flaticon. Sounds are based on CC0 works from freesound.org. The map background is using tiles from Carto.com or Thunderforest, thanks to OpenStreetMap contributors and Leaflet. Collected reports are available under ODbL license. Manual location picking uses the awesome API from adresse.data.gouv.fr.", + "lastReportAdded": "Last report added {fromNow}.", + "license": "License", + "licenseDescription": "It is released under an MIT license (source code). Icons are based on creations from Wikimedia, Vecteezy, Pixabay or Flaticon. Sounds are based on CC0 works from freesound.org. The map background is using tiles from Carto.com or Thunderforest, thanks to OpenStreetMap contributors and Leaflet. Collected reports are available under ODbL license. Manual location picking uses the awesome API from adresse.data.gouv.fr.", + "nbActiveReports": "No active report. | One active report. | {nbActiveReports} active reports.", + "nbReports": "No report. | One report. | {nbReports} reports.", + "stats": "Stats", "summary": "This app lets you track and share issues with bike lanes.", "usage": "How to use", "usageDescription": "Use the button in the lower right corner to add a new report at your current location. To add a report elsewhere, do a click where you want the report to be shown. Press on a marker on the map to display more informations and report the problem as being still there or solved." @@ -61,7 +66,7 @@ "vibrate": "Vibrate" }, "reportCard": { - "Reported": "Reported" + "Reported": "Reported {fromNow}." }, "reportDialog": { "unableToSendDescription": "There was a network issue preventing from sending the latest report.", diff --git a/src/views/About.vue b/src/views/About.vue index ba57f75..f1b5901 100644 --- a/src/views/About.vue +++ b/src/views/About.vue @@ -10,18 +10,52 @@

{{ $t('about.availableReportsTitle') }}

-

+

{{ $t('about.stats') }}

+ + + +

{{ $t('about.license') }}

+

diff --git a/src/vuetify.js b/src/vuetify.js index 616c7d3..2ccd8c9 100644 --- a/src/vuetify.js +++ b/src/vuetify.js @@ -12,6 +12,7 @@ import VGrid from 'vuetify/es5/components/VGrid'; import VIcon from 'vuetify/es5/components/VIcon'; import VList from 'vuetify/es5/components/VList'; import VMenu from 'vuetify/es5/components/VMenu'; +import VProgressCircular from 'vuetify/es5/components/VProgressCircular'; import VProgressLinear from 'vuetify/es5/components/VProgressLinear'; import VSelect from 'vuetify/es5/components/VSelect'; import VSwitch from 'vuetify/es5/components/VSwitch'; @@ -32,6 +33,7 @@ Vue.use(Vuetify, { VIcon, VList, VMenu, + VProgressCircular, VProgressLinear, VSelect, VSwitch,