Adds get_followers_paginated and get_actor_timeline to FederationService protocol + SQL implementation + stubs. Includes accumulated federation changes: models, DTOs, delivery handler, webfinger, inline publishing, widget nav templates, and migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
255 lines
6.7 KiB
Python
255 lines
6.7 KiB
Python
"""Frozen dataclasses for cross-domain data transfer.
|
|
|
|
These are the *only* shapes that cross domain boundaries. Consumers never
|
|
see ORM model instances from another domain — only these DTOs.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Blog domain
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class PostDTO:
|
|
id: int
|
|
slug: str
|
|
title: str
|
|
status: str
|
|
visibility: str
|
|
is_page: bool = False
|
|
feature_image: str | None = None
|
|
html: str | None = None
|
|
excerpt: str | None = None
|
|
custom_excerpt: str | None = None
|
|
published_at: datetime | None = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Calendar / Events domain
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CalendarDTO:
|
|
id: int
|
|
container_type: str
|
|
container_id: int
|
|
name: str
|
|
slug: str
|
|
description: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TicketDTO:
|
|
id: int
|
|
code: str
|
|
state: str
|
|
entry_name: str
|
|
entry_start_at: datetime
|
|
entry_end_at: datetime | None = None
|
|
ticket_type_name: str | None = None
|
|
calendar_name: str | None = None
|
|
created_at: datetime | None = None
|
|
checked_in_at: datetime | None = None
|
|
entry_id: int | None = None
|
|
ticket_type_id: int | None = None
|
|
price: Decimal | None = None
|
|
order_id: int | None = None
|
|
calendar_container_id: int | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CalendarEntryDTO:
|
|
id: int
|
|
calendar_id: int
|
|
name: str
|
|
start_at: datetime
|
|
state: str
|
|
cost: Decimal
|
|
end_at: datetime | None = None
|
|
user_id: int | None = None
|
|
session_id: str | None = None
|
|
order_id: int | None = None
|
|
slot_id: int | None = None
|
|
ticket_price: Decimal | None = None
|
|
ticket_count: int | None = None
|
|
calendar_name: str | None = None
|
|
calendar_slug: str | None = None
|
|
calendar_container_id: int | None = None
|
|
calendar_container_type: str | None = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Market domain
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MarketPlaceDTO:
|
|
id: int
|
|
container_type: str
|
|
container_id: int
|
|
name: str
|
|
slug: str
|
|
description: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProductDTO:
|
|
id: int
|
|
slug: str
|
|
title: str | None = None
|
|
image: str | None = None
|
|
description_short: str | None = None
|
|
rrp: Decimal | None = None
|
|
regular_price: Decimal | None = None
|
|
special_price: Decimal | None = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cart domain
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CartItemDTO:
|
|
id: int
|
|
product_id: int
|
|
quantity: int
|
|
product_title: str | None = None
|
|
product_slug: str | None = None
|
|
product_image: str | None = None
|
|
unit_price: Decimal | None = None
|
|
market_place_id: int | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CartSummaryDTO:
|
|
count: int = 0
|
|
total: Decimal = Decimal("0")
|
|
calendar_count: int = 0
|
|
calendar_total: Decimal = Decimal("0")
|
|
items: list[CartItemDTO] = field(default_factory=list)
|
|
ticket_count: int = 0
|
|
ticket_total: Decimal = Decimal("0")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Federation / ActivityPub domain
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ActorProfileDTO:
|
|
id: int
|
|
user_id: int
|
|
preferred_username: str
|
|
public_key_pem: str
|
|
display_name: str | None = None
|
|
summary: str | None = None
|
|
inbox_url: str | None = None
|
|
outbox_url: str | None = None
|
|
created_at: datetime | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class APActivityDTO:
|
|
id: int
|
|
activity_id: str
|
|
activity_type: str
|
|
actor_profile_id: int
|
|
object_type: str | None = None
|
|
object_data: dict | None = None
|
|
published: datetime | None = None
|
|
is_local: bool = True
|
|
source_type: str | None = None
|
|
source_id: int | None = None
|
|
ipfs_cid: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class APFollowerDTO:
|
|
id: int
|
|
actor_profile_id: int
|
|
follower_acct: str
|
|
follower_inbox: str
|
|
follower_actor_url: str
|
|
created_at: datetime | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class APAnchorDTO:
|
|
id: int
|
|
merkle_root: str
|
|
activity_count: int = 0
|
|
tree_ipfs_cid: str | None = None
|
|
ots_proof_cid: str | None = None
|
|
confirmed_at: datetime | None = None
|
|
bitcoin_txid: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RemoteActorDTO:
|
|
id: int
|
|
actor_url: str
|
|
inbox_url: str
|
|
preferred_username: str
|
|
domain: str
|
|
display_name: str | None = None
|
|
summary: str | None = None
|
|
icon_url: str | None = None
|
|
shared_inbox_url: str | None = None
|
|
public_key_pem: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RemotePostDTO:
|
|
id: int
|
|
remote_actor_id: int
|
|
object_id: str
|
|
content: str
|
|
summary: str | None = None
|
|
url: str | None = None
|
|
attachments: list[dict] = field(default_factory=list)
|
|
tags: list[dict] = field(default_factory=list)
|
|
published: datetime | None = None
|
|
actor: RemoteActorDTO | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TimelineItemDTO:
|
|
id: str # composite key for cursor pagination
|
|
post_type: str # "local" | "remote" | "boost"
|
|
content: str # HTML
|
|
published: datetime
|
|
actor_name: str
|
|
actor_username: str
|
|
object_id: str | None = None
|
|
summary: str | None = None
|
|
url: str | None = None
|
|
attachments: list[dict] = field(default_factory=list)
|
|
tags: list[dict] = field(default_factory=list)
|
|
actor_domain: str | None = None # None = local
|
|
actor_icon: str | None = None
|
|
actor_url: str | None = None
|
|
boosted_by: str | None = None
|
|
like_count: int = 0
|
|
boost_count: int = 0
|
|
liked_by_me: bool = False
|
|
boosted_by_me: bool = False
|
|
author_inbox: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class NotificationDTO:
|
|
id: int
|
|
notification_type: str # follow/like/boost/mention/reply
|
|
from_actor_name: str
|
|
from_actor_username: str
|
|
created_at: datetime
|
|
read: bool
|
|
from_actor_domain: str | None = None
|
|
from_actor_icon: str | None = None
|
|
target_content_preview: str | None = None
|