aboutsummaryrefslogtreecommitdiff
path: root/webmentions_ssg/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'webmentions_ssg/models.py')
-rw-r--r--webmentions_ssg/models.py99
1 files changed, 98 insertions, 1 deletions
diff --git a/webmentions_ssg/models.py b/webmentions_ssg/models.py
index e7ff0be..5d22395 100644
--- a/webmentions_ssg/models.py
+++ b/webmentions_ssg/models.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import uuid
from datetime import UTC, datetime
from enum import StrEnum
+from typing import Never
from flask_login import UserMixin
from sqlalchemy import (
@@ -27,12 +28,20 @@ from . import Base
class SentWebmentionStatus(StrEnum):
+ """
+ Represent the outcome of a Webmention send attempt.
+ """
+
SENT = "sent"
UNSUPPORTED = "unsupported"
FAILED = "failed"
class User(UserMixin, Base):
+ """
+ Represent an authenticated application user.
+ """
+
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -40,21 +49,46 @@ class User(UserMixin, Base):
password_hash: Mapped[str] = mapped_column(Text(), nullable=False)
def __repr__(self) -> str:
+ """
+ Return a developer-readable representation of the user.
+
+ :return: Representation containing the username.
+ """
return f"<User {self.username}>"
@property
- def password(self) -> None:
+ def password(self) -> Never:
+ """
+ Prevent access to the user's plain-text password.
+
+ :raises AttributeError: Always, because the password is write-only.
+ """
raise AttributeError("Password is write-only")
@password.setter
def password(self, password: str) -> None:
+ """
+ Hash and store a new password.
+
+ :param password: Plain-text password to hash.
+ """
self.password_hash = generate_password_hash(password)
def check_password(self, password: str) -> bool:
+ """
+ Check a plain-text password against the stored password hash.
+
+ :param password: Plain-text password to check.
+ :return: Whether the password matches the stored hash.
+ """
return check_password_hash(self.password_hash, password)
class ReceivedWebmention(Base):
+ """
+ Represent a Webmention received for verification.
+ """
+
__tablename__ = "received_webmentions"
uuid: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True)
@@ -78,16 +112,38 @@ class ReceivedWebmention(Base):
UniqueConstraint("source", "target", name="uq_webmention_source_target"),
)
+ def __repr__(self) -> str:
+ """
+ Return a developer-readable representation of the received Webmention.
+
+ :return: Representation containing the identifier, source, and target.
+ """
+ return f"<ReceivedWebmention {self.source!r} -> {self.target!r}>"
+
@property
def verified(self) -> bool:
+ """
+ Return whether the Webmention has been successfully verified.
+
+ :return: Whether the Webmention status is ``verified``.
+ """
return self.status == "verified"
@property
def created_at(self) -> datetime:
+ """
+ Return the creation time encoded in the Webmention UUID.
+
+ :return: Creation time as a timezone-aware datetime.
+ """
return uuid7_to_datetime(self.uuid)
class Source(Base):
+ """
+ Represent a generated source document containing outgoing Webmentions.
+ """
+
__tablename__ = "sources"
uuid: Mapped[uuid.UUID] = mapped_column(
@@ -114,12 +170,29 @@ class Source(Base):
back_populates="source", cascade="all, delete-orphan", lazy="selectin"
)
+ def __repr__(self) -> str:
+ """
+ Return a developer-readable representation of the source.
+
+ :return: Representation containing the identifier and public URL.
+ """
+ return f"<Source {self.url!r}>"
+
@property
def created_at(self) -> datetime:
+ """
+ Return the creation time encoded in the source UUID.
+
+ :return: Creation time as a timezone-aware datetime.
+ """
return uuid7_to_datetime(self.uuid)
class SentWebmention(Base):
+ """
+ Represent an outgoing Webmention associated with a source.
+ """
+
__tablename__ = "sent_webmentions"
uuid: Mapped[uuid.UUID] = mapped_column(
@@ -181,8 +254,21 @@ class SentWebmention(Base):
),
)
+ def __repr__(self) -> str:
+ """
+ Return a developer-readable representation of the sent Webmention.
+
+ :return: Representation containing the identifier and target URL.
+ """
+ return f"<SentWebmention {self.target!r}>"
+
@property
def pending(self) -> bool:
+ """
+ Return whether the Webmention has an unprocessed source revision.
+
+ :return: Whether the desired revision still requires processing.
+ """
return (
self.processed_revision is None
or self.processed_revision < self.desired_revision
@@ -190,8 +276,19 @@ class SentWebmention(Base):
@property
def created_at(self) -> datetime:
+ """
+ Return the creation time encoded in the Webmention UUID.
+
+ :return: Creation time as a timezone-aware datetime.
+ """
return uuid7_to_datetime(self.uuid)
def uuid7_to_datetime(identifier: uuid.UUID) -> datetime:
+ """
+ Convert a UUID version 7 timestamp to a datetime.
+
+ :param identifier: UUID whose embedded timestamp should be converted.
+ :return: Timestamp as a timezone-aware UTC datetime.
+ """
return datetime.fromtimestamp(identifier.time / 1000, tz=UTC)