Rework upvotes

Upvotes now reset the `datetime` field of the report, making it appear
as if it was newly reported. They also extend the lifetime for accidents
and GCUM.

This introduces database migrations as well.

Closes https://framagit.org/phyks/cyclassist/issues/41.
This commit is contained in:
Lucas Verney 2018-09-12 13:51:53 +02:00
parent 1139dc33c5
commit a7792f5dbb
7 changed files with 76 additions and 11 deletions

View File

@ -143,6 +143,12 @@ python -m server
to spawn the server-side part, listening on `localhost:8081`. to spawn the server-side part, listening on `localhost:8081`.
### Updating
Database migrations are in the `scripts/migrations` folder, labelled by
versions. You should run them in order from your current versions to the
latest one when you upgrade.
### Useful scripts for dev ### Useful scripts for dev
You can run `scripts/gps_to_gpx.py` on your GPX trace to create a You can run `scripts/gps_to_gpx.py` on your GPX trace to create a

41
scripts/migrations/0.3.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
"""
Database migration from < 0.3 to 0.3 version.
"""
import os
import sys
import peewee
SCRIPT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.abspath(os.path.join(SCRIPT_DIRECTORY, '..', '..')))
from playhouse.migrate import *
from server.models import db, Report
from server.tools import UTC_now
def run_migration():
if type(db) == peewee.SqliteDatabase:
migrator = SqliteMigrator(db)
elif type(db) == peewee.MySQLDatabase:
migrator = MySQLMigrator(db)
elif type(db) == peewee.PostgresqlDatabase:
migrator = PostgresqlMigrator(db)
else:
return
migrate(
migrator.add_column('report', 'first_report_datetime',
peewee.DateTimeField(default=UTC_now)),
)
query = Report.select()
for report in query:
report.first_report_datetime = report.datetime
report.save()
if __name__ == '__main__':
db.connect()
run_migration()

View File

@ -5,12 +5,13 @@ Models and database definition
""" """
import os import os
import arrow
import bottle import bottle
import peewee import peewee
from playhouse.db_url import connect from playhouse.db_url import connect
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from server.tools import UTC_now
db = connect(os.environ.get('DATABASE', 'sqlite:///reports.db')) db = connect(os.environ.get('DATABASE', 'sqlite:///reports.db'))
@ -40,8 +41,11 @@ class Report(BaseModel):
type = peewee.CharField(max_length=255) type = peewee.CharField(max_length=255)
lat = peewee.DoubleField() lat = peewee.DoubleField()
lng = peewee.DoubleField() lng = peewee.DoubleField()
first_report_datetime = peewee.DateTimeField(
default=UTC_now
)
datetime = peewee.DateTimeField( datetime = peewee.DateTimeField(
default=lambda: arrow.utcnow().replace(microsecond=0).naive default=UTC_now
) )
expiration_datetime = peewee.DateTimeField(null=True) expiration_datetime = peewee.DateTimeField(null=True)
is_open = peewee.BooleanField(default=True) is_open = peewee.BooleanField(default=True)

View File

@ -10,6 +10,7 @@ import os
import bottle import bottle
from server.models import Report from server.models import Report
from server.tools import UTC_now
from server import jsonapi from server import jsonapi
@ -80,7 +81,7 @@ def get_reports(only_active=False):
if only_active: if only_active:
query = query.where( query = query.where(
(Report.expiration_datetime == None) | (Report.expiration_datetime == None) |
(Report.expiration_datetime > arrow.utcnow().replace(microsecond=0).datetime) (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:
@ -195,7 +196,7 @@ def post_report():
# Handle expiration # Handle expiration
if r.type in ['accident', 'gcum']: if r.type in ['accident', 'gcum']:
r.expiration_datetime = ( r.expiration_datetime = (
arrow.utcnow().replace(microsecond=0).shift(hours=+1).datetime arrow.get(UTC_now()).shift(hours=+1).naive
) )
r.save() r.save()
except KeyError as exc: except KeyError as exc:
@ -228,7 +229,15 @@ def upvote_report(id):
r = Report.get(Report.id == id) r = Report.get(Report.id == id)
if not r: if not r:
return jsonapi.JsonApiError(404, "Invalid report id.") return jsonapi.JsonApiError(404, "Invalid report id.")
# Increase upvotes
r.upvotes += 1 r.upvotes += 1
# Update report datetime
r.datetime = UTC_now()
# Update expiration datetime
if r.type in ['accident', 'gcum']:
r.expiration_datetime = (
arrow.get(UTC_now()).shift(hours=+1).naive
)
r.save() r.save()
return { return {
@ -282,7 +291,7 @@ def get_stats():
nb_reports = Report.select().count() nb_reports = Report.select().count()
nb_active_reports = Report.select().where( nb_active_reports = Report.select().where(
(Report.expiration_datetime == None) | (Report.expiration_datetime == None) |
(Report.expiration_datetime > arrow.utcnow().replace(microsecond=0).datetime) (Report.expiration_datetime > UTC_now())
).count() ).count()
last_added_report_datetime = Report.select().order_by( last_added_report_datetime = Report.select().order_by(
Report.datetime.desc() Report.datetime.desc()

5
server/tools.py Normal file
View File

@ -0,0 +1,5 @@
import arrow
def UTC_now():
return arrow.utcnow().replace(microsecond=0).naive

View File

@ -85,8 +85,8 @@ export const MOCK_LOCATION_LNG_MAX = 2.392742;
export const UPDATE_REPORTS_DISTANCE_THRESHOLD = 500; // in meters export const UPDATE_REPORTS_DISTANCE_THRESHOLD = 500; // in meters
// Minimal ratio between upvotes and downvotes needed for a report to be shown // Minimal number of downvotes needed for a report to be masked
export const REPORT_VOTES_THRESHOLD = 0.5; export const REPORT_DOWNVOTES_THRESHOLD = 1;
export const EARTH_RADIUS = 6378137; // in meters export const EARTH_RADIUS = 6378137; // in meters

View File

@ -1,4 +1,4 @@
import { REPORT_VOTES_THRESHOLD } from '@/constants'; import { REPORT_DOWNVOTES_THRESHOLD } from '@/constants';
export function getLastLocation(state) { export function getLastLocation(state) {
const { gpx } = state.location; const { gpx } = state.location;
@ -10,9 +10,9 @@ export function getLastLocation(state) {
export function notDismissedReports(state) { export function notDismissedReports(state) {
return state.reports.filter((item) => { return state.reports.filter((item) => {
if (item.attributes.downvotes === 0) { if (item.attributes.downvotes >= REPORT_DOWNVOTES_THRESHOLD) {
return true; return false;
} }
return (item.attributes.upvotes / item.attributes.downvotes) > REPORT_VOTES_THRESHOLD; return true;
}); });
} }