feat(attachments): PostAttachment model + 0009 (table + batch counter)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:10:53 -04:00
parent e3f6e6fadd
commit 8f69478227
5 changed files with 155 additions and 0 deletions
+2
View File
@@ -11,6 +11,7 @@ from .import_settings import ImportSettings
from .import_task import ImportTask
from .ml_settings import MLSettings
from .post import Post
from .post_attachment import PostAttachment
from .series_page import SeriesPage
from .source import Source
from .tag import Tag, TagKind, image_tag
@@ -25,6 +26,7 @@ __all__ = [
"Source",
"Credential",
"Post",
"PostAttachment",
"SeriesPage",
"ImageRecord",
"ImageProvenance",
+1
View File
@@ -25,6 +25,7 @@ class ImportBatch(Base):
imported: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
skipped: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
failed: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
attachments: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
status: Mapped[str] = mapped_column(String(16), nullable=False, default="running", index=True)
# running | complete | cancelled
+47
View File
@@ -0,0 +1,47 @@
"""PostAttachment — a non-art file preserved from a post.
Art images become ImageRecords; everything else a post contained
(archives, .exe, .pdf, ...) is captured here so nothing is lost.
post_id is nullable (set only when an adjacent sidecar yields a Post);
artist_id mirrors the canonical attribution model (FC-2d-vii-c). Both
FKs are SET NULL so deleting a Post/Artist never deletes the preserved
binary row.
"""
from datetime import datetime
from sqlalchemy import (
BigInteger,
DateTime,
ForeignKey,
Integer,
String,
Text,
func,
)
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base
class PostAttachment(Base):
__tablename__ = "post_attachment"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
post_id: Mapped[int | None] = mapped_column(
ForeignKey("post.id", ondelete="SET NULL"), nullable=True, index=True
)
artist_id: Mapped[int | None] = mapped_column(
ForeignKey("artist.id", ondelete="SET NULL"), nullable=True, index=True
)
sha256: Mapped[str] = mapped_column(
String(64), nullable=False, unique=True, index=True
)
path: Mapped[str] = mapped_column(Text, nullable=False)
original_filename: Mapped[str] = mapped_column(Text, nullable=False)
ext: Mapped[str] = mapped_column(String(32), nullable=False)
mime: Mapped[str | None] = mapped_column(String(128), nullable=True)
size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False)
captured_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)