first commit
This commit is contained in:
167
app/models.py
Normal file
167
app/models.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
from .extensions import db, login_manager
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(UTC).replace(tzinfo=None)
|
||||
|
||||
|
||||
class TimestampMixin:
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=utcnow)
|
||||
updated_at = db.Column(db.DateTime, nullable=False, default=utcnow, onupdate=utcnow)
|
||||
|
||||
|
||||
class User(UserMixin, TimestampMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(120), nullable=False)
|
||||
email = db.Column(db.String(255), nullable=False, unique=True, index=True)
|
||||
password_hash = db.Column(db.String(255), nullable=False)
|
||||
avatar_path = db.Column(db.String(255), nullable=True)
|
||||
notification_task_due_enabled = db.Column(db.Boolean, nullable=False, default=True)
|
||||
notification_monthly_winner_enabled = db.Column(db.Boolean, nullable=False, default=True)
|
||||
|
||||
assigned_task_templates = db.relationship(
|
||||
"TaskTemplate",
|
||||
foreign_keys="TaskTemplate.default_assigned_user_id",
|
||||
backref="default_assigned_user",
|
||||
lazy=True,
|
||||
)
|
||||
assigned_tasks = db.relationship(
|
||||
"TaskInstance",
|
||||
foreign_keys="TaskInstance.assigned_user_id",
|
||||
backref="assigned_user",
|
||||
lazy=True,
|
||||
)
|
||||
completed_tasks = db.relationship(
|
||||
"TaskInstance",
|
||||
foreign_keys="TaskInstance.completed_by_user_id",
|
||||
backref="completed_by_user",
|
||||
lazy=True,
|
||||
)
|
||||
subscriptions = db.relationship("PushSubscription", backref="user", lazy=True, cascade="all, delete-orphan")
|
||||
|
||||
def set_password(self, password: str) -> None:
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password: str) -> bool:
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
@property
|
||||
def display_avatar(self) -> str:
|
||||
return self.avatar_path or "images/avatars/default.svg"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<User {self.email}>"
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id: str) -> User | None:
|
||||
return db.session.get(User, int(user_id))
|
||||
|
||||
|
||||
class TaskTemplate(TimestampMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(160), nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
default_points = db.Column(db.Integer, nullable=False, default=10)
|
||||
default_assigned_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
|
||||
recurrence_interval_value = db.Column(db.Integer, nullable=True)
|
||||
recurrence_interval_unit = db.Column(db.String(20), nullable=False, default="none")
|
||||
active = db.Column(db.Boolean, nullable=False, default=True)
|
||||
|
||||
instances = db.relationship("TaskInstance", backref="task_template", lazy=True, cascade="all, delete-orphan")
|
||||
|
||||
@property
|
||||
def recurrence_label(self) -> str:
|
||||
if self.recurrence_interval_unit == "none" or not self.recurrence_interval_value:
|
||||
return "Einmalig"
|
||||
return f"Alle {self.recurrence_interval_value} {self.recurrence_interval_unit}"
|
||||
|
||||
|
||||
class TaskInstance(TimestampMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
task_template_id = db.Column(db.Integer, db.ForeignKey("task_template.id"), nullable=False, index=True)
|
||||
title = db.Column(db.String(160), nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
assigned_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
|
||||
due_date = db.Column(db.Date, nullable=False, index=True)
|
||||
status = db.Column(db.String(20), nullable=False, default="open", index=True)
|
||||
completed_at = db.Column(db.DateTime, nullable=True, index=True)
|
||||
completed_by_user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True, index=True)
|
||||
points_awarded = db.Column(db.Integer, nullable=False, default=10)
|
||||
|
||||
@property
|
||||
def is_completed(self) -> bool:
|
||||
return self.completed_at is not None
|
||||
|
||||
def compute_status(self, reference_date: date | None = None) -> str:
|
||||
reference_date = reference_date or date.today()
|
||||
if self.completed_at:
|
||||
return "completed"
|
||||
if self.due_date < reference_date:
|
||||
return "overdue"
|
||||
if self.due_date <= reference_date + timedelta(days=2):
|
||||
return "soon"
|
||||
return "open"
|
||||
|
||||
@property
|
||||
def status_label(self) -> str:
|
||||
labels = {
|
||||
"open": "Offen",
|
||||
"soon": "Bald fällig",
|
||||
"overdue": "Überfällig",
|
||||
"completed": "Erledigt",
|
||||
}
|
||||
return labels.get(self.status, "Offen")
|
||||
|
||||
|
||||
class MonthlyScoreSnapshot(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
year = db.Column(db.Integer, nullable=False, index=True)
|
||||
month = db.Column(db.Integer, nullable=False, index=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
|
||||
total_points = db.Column(db.Integer, nullable=False, default=0)
|
||||
completed_tasks_count = db.Column(db.Integer, nullable=False, default=0)
|
||||
rank = db.Column(db.Integer, nullable=False, default=1)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=utcnow)
|
||||
|
||||
user = db.relationship("User", backref="monthly_snapshots")
|
||||
|
||||
__table_args__ = (db.UniqueConstraint("year", "month", "user_id", name="uq_snapshot_month_user"),)
|
||||
|
||||
|
||||
class PushSubscription(TimestampMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
|
||||
endpoint = db.Column(db.Text, nullable=False, unique=True)
|
||||
p256dh = db.Column(db.Text, nullable=False)
|
||||
auth = db.Column(db.Text, nullable=False)
|
||||
|
||||
|
||||
class NotificationLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False, index=True)
|
||||
type = db.Column(db.String(80), nullable=False, index=True)
|
||||
payload = db.Column(db.Text, nullable=False)
|
||||
sent_at = db.Column(db.DateTime, nullable=False, default=utcnow, index=True)
|
||||
|
||||
user = db.relationship("User", backref="notification_logs")
|
||||
|
||||
|
||||
class BadgeDefinition(TimestampMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(80), nullable=False, unique=True, index=True)
|
||||
name = db.Column(db.String(120), nullable=False)
|
||||
description = db.Column(db.String(255), nullable=False)
|
||||
icon_name = db.Column(db.String(80), nullable=False, default="sparkles")
|
||||
trigger_type = db.Column(db.String(80), nullable=False)
|
||||
threshold = db.Column(db.Integer, nullable=False, default=1)
|
||||
bonus_points = db.Column(db.Integer, nullable=False, default=0)
|
||||
active = db.Column(db.Boolean, nullable=False, default=True)
|
||||
|
||||
Reference in New Issue
Block a user