Files
Stride/app/models.py
T

84 lines
3.7 KiB
Python

from datetime import UTC, datetime
from flask_login import UserMixin
from werkzeug.security import check_password_hash, generate_password_hash
from .extensions import db
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True, nullable=False)
email = db.Column(db.String(254), unique=True, nullable=False)
display_name = db.Column(db.String(80), nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
timezone = db.Column(db.String(64), nullable=False, default="Australia/Sydney")
# Assigned only by local setup / operator CLI. Never accepted from registration input.
local_account_key = db.Column(db.String(64), unique=True)
created_at = db.Column(db.DateTime(timezone=True), default=lambda: datetime.now(UTC))
activities = db.relationship(
"Activity", backref="owner", lazy="select", cascade="all, delete-orphan"
)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class GarminConnection(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True, nullable=False)
account_key = db.Column(db.String(24), unique=True, nullable=False)
tokens = db.Column(db.Text, nullable=False)
pending = db.Column(db.Boolean, nullable=False, default=True)
scheduled = db.Column(db.Boolean, nullable=False, default=True)
next_sync = db.Column(db.DateTime(timezone=True), nullable=False)
last_success = db.Column(db.DateTime(timezone=True))
message = db.Column(db.String(200), nullable=False, default="Sync queued.")
failures = db.Column(db.Integer, nullable=False, default=0)
class Activity(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
source_key = db.Column(db.String(180), nullable=False)
title = db.Column(db.String(160), nullable=False)
sport = db.Column(db.String(40), nullable=False, default="running")
started_at = db.Column(db.DateTime(timezone=True), nullable=False, index=True)
distance_m = db.Column(db.Float, nullable=False, default=0)
duration_s = db.Column(db.Float, nullable=False, default=0)
elevation_m = db.Column(db.Float, nullable=False, default=0)
avg_hr = db.Column(db.Float)
metrics = db.Column(db.JSON, nullable=False, default=dict, server_default="{}")
visibility = db.Column(db.String(12), nullable=False, default="private")
share_token = db.Column(db.String(64), unique=True)
samples = db.Column(db.JSON, nullable=False, default=list)
laps = db.Column(db.JSON, nullable=False, default=list)
created_at = db.Column(db.DateTime(timezone=True), default=lambda: datetime.now(UTC))
__table_args__ = (db.UniqueConstraint("owner_id", "source_key"),)
@property
def distance_km(self):
return self.distance_m / 1000
@property
def pace(self):
if self.distance_m <= 0:
return "—"
seconds = round(self.duration_s / self.distance_km)
return f"{seconds // 60}:{seconds % 60:02d}"
class Friendship(db.Model):
id = db.Column(db.Integer, primary_key=True)
low_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
high_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
requested_by = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
status = db.Column(db.String(12), nullable=False, default="pending")
__table_args__ = (
db.UniqueConstraint("low_id", "high_id"),
db.CheckConstraint("low_id < high_id"),
)