upgrade schema, resolvers, panel added
This commit is contained in:
@@ -1,26 +1,22 @@
|
||||
import time
|
||||
import trafilatura
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from cache.cache import (
|
||||
cache_author,
|
||||
cache_by_id,
|
||||
cache_topic,
|
||||
invalidate_shout_related_cache,
|
||||
invalidate_shouts_cache,
|
||||
)
|
||||
from orm.author import Author
|
||||
from auth.orm import Author
|
||||
from orm.draft import Draft, DraftAuthor, DraftTopic
|
||||
from orm.shout import Shout, ShoutAuthor, ShoutTopic
|
||||
from orm.topic import Topic
|
||||
from services.auth import login_required
|
||||
from services.db import local_session
|
||||
from services.notify import notify_shout
|
||||
from services.schema import mutation, query
|
||||
from services.search import search_service
|
||||
from utils.html_wrapper import wrap_html_fragment
|
||||
from utils.extract_text import extract_text
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
|
||||
def create_shout_from_draft(session, draft, author_id):
|
||||
"""
|
||||
Создаёт новый объект публикации (Shout) на основе черновика.
|
||||
@@ -62,11 +58,11 @@ def create_shout_from_draft(session, draft, author_id):
|
||||
draft=draft.id,
|
||||
deleted_at=None,
|
||||
)
|
||||
|
||||
|
||||
# Инициализируем пустые массивы для связей
|
||||
shout.topics = []
|
||||
shout.authors = []
|
||||
|
||||
|
||||
return shout
|
||||
|
||||
|
||||
@@ -75,10 +71,10 @@ def create_shout_from_draft(session, draft, author_id):
|
||||
async def load_drafts(_, info):
|
||||
"""
|
||||
Загружает все черновики, доступные текущему пользователю.
|
||||
|
||||
|
||||
Предварительно загружает связанные объекты (topics, authors, publication),
|
||||
чтобы избежать ошибок с отсоединенными объектами при сериализации.
|
||||
|
||||
|
||||
Returns:
|
||||
dict: Список черновиков или сообщение об ошибке
|
||||
"""
|
||||
@@ -97,12 +93,12 @@ async def load_drafts(_, info):
|
||||
.options(
|
||||
joinedload(Draft.topics),
|
||||
joinedload(Draft.authors),
|
||||
joinedload(Draft.publication) # Загружаем связанную публикацию
|
||||
joinedload(Draft.publication), # Загружаем связанную публикацию
|
||||
)
|
||||
.filter(Draft.authors.any(Author.id == author_id))
|
||||
)
|
||||
drafts = drafts_query.all()
|
||||
|
||||
|
||||
# Преобразуем объекты в словари, пока они в контексте сессии
|
||||
drafts_data = []
|
||||
for draft in drafts:
|
||||
@@ -110,19 +106,19 @@ async def load_drafts(_, info):
|
||||
# Всегда возвращаем массив для topics, даже если он пустой
|
||||
draft_dict["topics"] = [topic.dict() for topic in (draft.topics or [])]
|
||||
draft_dict["authors"] = [author.dict() for author in (draft.authors or [])]
|
||||
|
||||
|
||||
# Добавляем информацию о публикации, если она есть
|
||||
if draft.publication:
|
||||
draft_dict["publication"] = {
|
||||
"id": draft.publication.id,
|
||||
"slug": draft.publication.slug,
|
||||
"published_at": draft.publication.published_at
|
||||
"published_at": draft.publication.published_at,
|
||||
}
|
||||
else:
|
||||
draft_dict["publication"] = None
|
||||
|
||||
draft_dict["publication"] = None
|
||||
|
||||
drafts_data.append(draft_dict)
|
||||
|
||||
|
||||
return {"drafts": drafts_data}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load drafts: {e}", exc_info=True)
|
||||
@@ -180,27 +176,27 @@ async def create_draft(_, info, draft_input):
|
||||
# Remove id from input if present since it's auto-generated
|
||||
if "id" in draft_input:
|
||||
del draft_input["id"]
|
||||
|
||||
|
||||
# Добавляем текущее время создания и ID автора
|
||||
draft_input["created_at"] = int(time.time())
|
||||
draft_input["created_by"] = author_id
|
||||
draft = Draft(**draft_input)
|
||||
session.add(draft)
|
||||
session.flush()
|
||||
|
||||
|
||||
# Добавляем создателя как автора
|
||||
da = DraftAuthor(shout=draft.id, author=author_id)
|
||||
session.add(da)
|
||||
|
||||
|
||||
session.commit()
|
||||
return {"draft": draft}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create draft: {e}", exc_info=True)
|
||||
return {"error": f"Failed to create draft: {str(e)}"}
|
||||
|
||||
|
||||
def generate_teaser(body, limit=300):
|
||||
body_html = wrap_html_fragment(body)
|
||||
body_text = trafilatura.extract(body_html, include_comments=False, include_tables=False)
|
||||
body_text = extract_text(body)
|
||||
body_teaser = ". ".join(body_text[:limit].split(". ")[:-1])
|
||||
return body_teaser
|
||||
|
||||
@@ -246,9 +242,20 @@ async def update_draft(_, info, draft_id: int, draft_input):
|
||||
|
||||
# Фильтруем входные данные, оставляя только разрешенные поля
|
||||
allowed_fields = {
|
||||
"layout", "author_ids", "topic_ids", "main_topic_id",
|
||||
"media", "lead", "subtitle", "lang", "seo", "body",
|
||||
"title", "slug", "cover", "cover_caption"
|
||||
"layout",
|
||||
"author_ids",
|
||||
"topic_ids",
|
||||
"main_topic_id",
|
||||
"media",
|
||||
"lead",
|
||||
"subtitle",
|
||||
"lang",
|
||||
"seo",
|
||||
"body",
|
||||
"title",
|
||||
"slug",
|
||||
"cover",
|
||||
"cover_caption",
|
||||
}
|
||||
filtered_input = {k: v for k, v in draft_input.items() if k in allowed_fields}
|
||||
|
||||
@@ -277,9 +284,9 @@ async def update_draft(_, info, draft_id: int, draft_input):
|
||||
# Добавляем новые связи
|
||||
for tid in topic_ids:
|
||||
dt = DraftTopic(
|
||||
shout=draft_id,
|
||||
shout=draft_id,
|
||||
topic=tid,
|
||||
main=(tid == main_topic_id) if main_topic_id else False
|
||||
main=(tid == main_topic_id) if main_topic_id else False,
|
||||
)
|
||||
session.add(dt)
|
||||
|
||||
@@ -287,13 +294,10 @@ async def update_draft(_, info, draft_id: int, draft_input):
|
||||
if "seo" not in filtered_input and not draft.seo:
|
||||
body_src = filtered_input.get("body", draft.body)
|
||||
lead_src = filtered_input.get("lead", draft.lead)
|
||||
body_html = wrap_html_fragment(body_src)
|
||||
lead_html = wrap_html_fragment(lead_src)
|
||||
|
||||
|
||||
try:
|
||||
body_text = trafilatura.extract(body_html, include_comments=False, include_tables=False) if body_src else None
|
||||
lead_text = trafilatura.extract(lead_html, include_comments=False, include_tables=False) if lead_src else None
|
||||
|
||||
body_text = extract_text(body_src) if body_src else None
|
||||
lead_text = extract_text(lead_src) if lead_src else None
|
||||
body_teaser = generate_teaser(body_text, 300) if body_text else ""
|
||||
filtered_input["seo"] = lead_text if lead_text else body_teaser
|
||||
except Exception as e:
|
||||
@@ -308,14 +312,14 @@ async def update_draft(_, info, draft_id: int, draft_input):
|
||||
draft.updated_by = author_id
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
# Преобразуем объект в словарь для ответа
|
||||
draft_dict = draft.dict()
|
||||
draft_dict["topics"] = [topic.dict() for topic in draft.topics]
|
||||
draft_dict["authors"] = [author.dict() for author in draft.authors]
|
||||
# Добавляем объект автора в updated_by
|
||||
draft_dict["updated_by"] = author_dict
|
||||
|
||||
|
||||
return {"draft": draft_dict}
|
||||
|
||||
except Exception as e:
|
||||
@@ -343,13 +347,13 @@ async def delete_draft(_, info, draft_id: int):
|
||||
def validate_html_content(html_content: str) -> tuple[bool, str]:
|
||||
"""
|
||||
Проверяет валидность HTML контента через trafilatura.
|
||||
|
||||
|
||||
Args:
|
||||
html_content: HTML строка для проверки
|
||||
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (валидность, сообщение об ошибке)
|
||||
|
||||
|
||||
Example:
|
||||
>>> is_valid, error = validate_html_content("<p>Valid HTML</p>")
|
||||
>>> is_valid
|
||||
@@ -364,13 +368,10 @@ def validate_html_content(html_content: str) -> tuple[bool, str]:
|
||||
"""
|
||||
if not html_content or not html_content.strip():
|
||||
return False, "Content is empty"
|
||||
|
||||
|
||||
try:
|
||||
html_content = wrap_html_fragment(html_content)
|
||||
extracted = trafilatura.extract(html_content)
|
||||
if not extracted:
|
||||
return False, "Invalid HTML structure or empty content"
|
||||
return True, ""
|
||||
extracted = extract_text(html_content)
|
||||
return bool(extracted), extracted or ""
|
||||
except Exception as e:
|
||||
logger.error(f"HTML validation error: {e}", exc_info=True)
|
||||
return False, f"Invalid HTML content: {str(e)}"
|
||||
@@ -381,10 +382,10 @@ def validate_html_content(html_content: str) -> tuple[bool, str]:
|
||||
async def publish_draft(_, info, draft_id: int):
|
||||
"""
|
||||
Публикует черновик, создавая новый Shout или обновляя существующий.
|
||||
|
||||
|
||||
Args:
|
||||
draft_id (int): ID черновика для публикации
|
||||
|
||||
|
||||
Returns:
|
||||
dict: Результат публикации с shout или сообщением об ошибке
|
||||
"""
|
||||
@@ -400,11 +401,7 @@ async def publish_draft(_, info, draft_id: int):
|
||||
# Загружаем черновик со всеми связями
|
||||
draft = (
|
||||
session.query(Draft)
|
||||
.options(
|
||||
joinedload(Draft.topics),
|
||||
joinedload(Draft.authors),
|
||||
joinedload(Draft.publication)
|
||||
)
|
||||
.options(joinedload(Draft.topics), joinedload(Draft.authors), joinedload(Draft.publication))
|
||||
.filter(Draft.id == draft_id)
|
||||
.first()
|
||||
)
|
||||
@@ -421,7 +418,17 @@ async def publish_draft(_, info, draft_id: int):
|
||||
if draft.publication:
|
||||
shout = draft.publication
|
||||
# Обновляем существующую публикацию
|
||||
for field in ["body", "title", "subtitle", "lead", "cover", "cover_caption", "media", "lang", "seo"]:
|
||||
for field in [
|
||||
"body",
|
||||
"title",
|
||||
"subtitle",
|
||||
"lead",
|
||||
"cover",
|
||||
"cover_caption",
|
||||
"media",
|
||||
"lang",
|
||||
"seo",
|
||||
]:
|
||||
if hasattr(draft, field):
|
||||
setattr(shout, field, getattr(draft, field))
|
||||
shout.updated_at = int(time.time())
|
||||
@@ -440,16 +447,14 @@ async def publish_draft(_, info, draft_id: int):
|
||||
session.query(ShoutTopic).filter(ShoutTopic.shout == shout.id).delete()
|
||||
|
||||
# Добавляем авторов
|
||||
for author in (draft.authors or []):
|
||||
for author in draft.authors or []:
|
||||
sa = ShoutAuthor(shout=shout.id, author=author.id)
|
||||
session.add(sa)
|
||||
|
||||
# Добавляем темы
|
||||
for topic in (draft.topics or []):
|
||||
for topic in draft.topics or []:
|
||||
st = ShoutTopic(
|
||||
topic=topic.id,
|
||||
shout=shout.id,
|
||||
main=topic.main if hasattr(topic, "main") else False
|
||||
topic=topic.id, shout=shout.id, main=topic.main if hasattr(topic, "main") else False
|
||||
)
|
||||
session.add(st)
|
||||
|
||||
|
Reference in New Issue
Block a user