diff options
Diffstat (limited to 'webmentions_ssg/models.py')
| -rw-r--r-- | webmentions_ssg/models.py | 99 |
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) |
