Monorepo: consolidate 7 repos into one
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account into a single repository. Eliminates submodule sync, sibling model copying at build time, and per-app CI orchestration. Changes: - Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs - Remove stale sibling model copies from each app - Update all 6 Dockerfiles for monorepo build context (root = .) - Add build directives to docker-compose.yml - Add single .gitea/workflows/ci.yml with change detection - Add .dockerignore for monorepo build context - Create __init__.py for federation and account (cross-app imports)
This commit is contained in:
255
shared/contracts/dtos.py
Normal file
255
shared/contracts/dtos.py
Normal file
@@ -0,0 +1,255 @@
|
||||
"""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
|
||||
app_domain: str = "federation"
|
||||
|
||||
|
||||
@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
|
||||
Reference in New Issue
Block a user