feat: SQLAlchemy models for Article, Image, Issue, Setting
This commit is contained in:
@@ -1 +1,74 @@
|
|||||||
pass
|
import json
|
||||||
|
from datetime import datetime, date, timezone
|
||||||
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
|
class Article(db.Model):
|
||||||
|
__tablename__ = "articles"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
guid = db.Column(db.Text, unique=True, nullable=False)
|
||||||
|
title = db.Column(db.Text, nullable=False)
|
||||||
|
author = db.Column(db.Text, nullable=False)
|
||||||
|
pub_date = db.Column(db.DateTime, nullable=False)
|
||||||
|
categories = db.Column(db.Text, nullable=False, default="[]")
|
||||||
|
link = db.Column(db.Text, nullable=False)
|
||||||
|
content_html = db.Column(db.Text, nullable=False, default="")
|
||||||
|
fetched_at = db.Column(
|
||||||
|
db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
|
||||||
|
images = db.relationship("Image", backref="article", lazy=True,
|
||||||
|
cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
|
||||||
|
class Image(db.Model):
|
||||||
|
__tablename__ = "images"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
article_id = db.Column(db.Integer, db.ForeignKey("articles.id"), nullable=False)
|
||||||
|
original_url = db.Column(db.Text, nullable=False)
|
||||||
|
local_path = db.Column(db.Text, nullable=False)
|
||||||
|
width = db.Column(db.Integer, nullable=False)
|
||||||
|
height = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Issue(db.Model):
|
||||||
|
__tablename__ = "issues"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
week_start = db.Column(db.Date, nullable=False)
|
||||||
|
week_end = db.Column(db.Date, nullable=False)
|
||||||
|
cover_method = db.Column(db.Text, nullable=False)
|
||||||
|
cover_path = db.Column(db.Text, nullable=False)
|
||||||
|
epub_path = db.Column(db.Text, nullable=False)
|
||||||
|
article_ids = db.Column(db.Text, nullable=False, default="[]")
|
||||||
|
excluded_article_ids = db.Column(db.Text, nullable=False, default="[]")
|
||||||
|
created_at = db.Column(
|
||||||
|
db.DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
status = db.Column(db.Text, nullable=False, default="draft")
|
||||||
|
|
||||||
|
|
||||||
|
class Setting(db.Model):
|
||||||
|
__tablename__ = "settings"
|
||||||
|
|
||||||
|
key = db.Column(db.Text, primary_key=True)
|
||||||
|
value = db.Column(db.Text, nullable=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(key, default=None):
|
||||||
|
row = Setting.query.get(key)
|
||||||
|
if row is None:
|
||||||
|
return default
|
||||||
|
return json.loads(row.value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set(key, value):
|
||||||
|
row = Setting.query.get(key)
|
||||||
|
if row is None:
|
||||||
|
row = Setting(key=key, value=json.dumps(value))
|
||||||
|
db.session.add(row)
|
||||||
|
else:
|
||||||
|
row.value = json.dumps(value)
|
||||||
|
db.session.commit()
|
||||||
|
|||||||
86
tests/test_models.py
Normal file
86
tests/test_models.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime, date
|
||||||
|
from src.models import Article, Image, Issue, Setting
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_article(db):
|
||||||
|
article = Article(
|
||||||
|
guid="https://example.com/?p=100",
|
||||||
|
title="Test Article",
|
||||||
|
author="Test Author",
|
||||||
|
pub_date=datetime(2026, 4, 6, 12, 0, 0),
|
||||||
|
categories=json.dumps(["Government"]),
|
||||||
|
link="https://example.com/test",
|
||||||
|
content_html="<p>Test content</p>",
|
||||||
|
)
|
||||||
|
db.session.add(article)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
saved = Article.query.filter_by(guid="https://example.com/?p=100").first()
|
||||||
|
assert saved is not None
|
||||||
|
assert saved.title == "Test Article"
|
||||||
|
assert saved.author == "Test Author"
|
||||||
|
assert json.loads(saved.categories) == ["Government"]
|
||||||
|
assert saved.fetched_at is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_article_guid_unique(db):
|
||||||
|
a1 = Article(guid="dup", title="A", author="X", pub_date=datetime.now(),
|
||||||
|
categories="[]", link="http://a", content_html="")
|
||||||
|
a2 = Article(guid="dup", title="B", author="Y", pub_date=datetime.now(),
|
||||||
|
categories="[]", link="http://b", content_html="")
|
||||||
|
db.session.add(a1)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.add(a2)
|
||||||
|
try:
|
||||||
|
db.session.commit()
|
||||||
|
assert False, "Should have raised IntegrityError"
|
||||||
|
except Exception:
|
||||||
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_image(db):
|
||||||
|
article = Article(guid="img-test", title="A", author="X",
|
||||||
|
pub_date=datetime.now(), categories="[]",
|
||||||
|
link="http://a", content_html="")
|
||||||
|
db.session.add(article)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
img = Image(
|
||||||
|
article_id=article.id,
|
||||||
|
original_url="https://example.com/photo.jpg",
|
||||||
|
local_path="data/images/abc123.jpg",
|
||||||
|
width=800,
|
||||||
|
height=450,
|
||||||
|
)
|
||||||
|
db.session.add(img)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
assert img.id is not None
|
||||||
|
assert img.article.guid == "img-test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_issue(db):
|
||||||
|
issue = Issue(
|
||||||
|
week_start=date(2026, 4, 6),
|
||||||
|
week_end=date(2026, 4, 12),
|
||||||
|
cover_method="text",
|
||||||
|
cover_path="data/issues/cover.jpg",
|
||||||
|
epub_path="data/issues/test.epub",
|
||||||
|
article_ids=json.dumps([1, 2, 3]),
|
||||||
|
excluded_article_ids=json.dumps([]),
|
||||||
|
status="published",
|
||||||
|
)
|
||||||
|
db.session.add(issue)
|
||||||
|
db.session.commit()
|
||||||
|
assert issue.id is not None
|
||||||
|
assert issue.created_at is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_crud(db):
|
||||||
|
Setting.set("fetch_interval", 2)
|
||||||
|
assert Setting.get("fetch_interval") == 2
|
||||||
|
assert Setting.get("nonexistent", default="fallback") == "fallback"
|
||||||
|
|
||||||
|
Setting.set("fetch_interval", 4)
|
||||||
|
assert Setting.get("fetch_interval") == 4
|
||||||
Reference in New Issue
Block a user