From eb216a5f3643e346477e82ee91e6337205fde4ff Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 15 Apr 2025 20:16:01 +0300 Subject: [PATCH] draft-seo-handling --- orm/draft.py | 2 +- resolvers/draft.py | 14 +++++++------- resolvers/editor.py | 6 +++--- resolvers/topic.py | 7 +++---- services/viewed.py | 31 ++++++++++++++++--------------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/orm/draft.py b/orm/draft.py index 67c1d14d..1c669f02 100644 --- a/orm/draft.py +++ b/orm/draft.py @@ -32,7 +32,7 @@ class Draft(Base): created_at: int = Column(Integer, nullable=False, default=lambda: int(time.time())) created_by: int = Column(ForeignKey("author.id"), nullable=False) community: int = Column(ForeignKey("community.id"), nullable=False, default=1) - + # optional layout: str = Column(String, nullable=True, default="article") slug: str = Column(String, unique=True) diff --git a/resolvers/draft.py b/resolvers/draft.py index 8c2eb1a6..2f47e01a 100644 --- a/resolvers/draft.py +++ b/resolvers/draft.py @@ -1,8 +1,8 @@ import time from operator import or_ -from sqlalchemy.sql import and_ import trafilatura +from sqlalchemy.sql import and_ from cache.cache import ( cache_author, @@ -104,7 +104,7 @@ async def create_draft(_, info, draft_input): if "title" not in draft_input or not draft_input["title"]: draft_input["title"] = "" # Пустая строка вместо NULL - + # Проверяем slug - он должен быть или не пустым, или не передаваться вообще if "slug" in draft_input and (draft_input["slug"] is None or draft_input["slug"] == ""): # При создании черновика удаляем пустой slug из входных данных @@ -115,9 +115,9 @@ 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"] - + if "seo" not in draft_input and not draft_input["seo"]: - body_teaser = draft_input.get("body", "")[:300].split('\n')[:-1].join("\n") + body_teaser = draft_input.get("body", "")[:300].split("\n")[:-1].join("\n") draft_input["seo"] = draft_input.get("lead", body_teaser) # Добавляем текущее время создания @@ -164,13 +164,13 @@ async def update_draft(_, info, draft_id: int, draft_input): draft = session.query(Draft).filter(Draft.id == draft_id).first() if not draft: return {"error": "Draft not found"} - + if "seo" not in draft_input and not draft.seo: body_src = draft_input["body"] if "body" in draft_input else draft.body body_text = trafilatura.extract(body_src) lead_src = draft_input["lead"] if "lead" in draft_input else draft.lead lead_text = trafilatura.extract(lead_src) - body_teaser = body_text[:300].split('. ')[:-1].join(".\n") + body_teaser = body_text[:300].split(". ")[:-1].join(".\n") draft_input["seo"] = lead_text or body_teaser Draft.update(draft, draft_input) @@ -178,7 +178,7 @@ async def update_draft(_, info, draft_id: int, draft_input): current_time = int(time.time()) draft.updated_at = current_time draft.updated_by = author_id - + session.commit() return {"draft": draft} diff --git a/resolvers/editor.py b/resolvers/editor.py index 4f2d8beb..6d0b396f 100644 --- a/resolvers/editor.py +++ b/resolvers/editor.py @@ -1,6 +1,7 @@ import time import orjson +import trafilatura from sqlalchemy import and_, desc, select from sqlalchemy.orm import joinedload from sqlalchemy.sql.functions import coalesce @@ -22,7 +23,6 @@ from services.notify import notify_shout from services.schema import query from services.search import search_service from utils.logger import root_logger as logger -import trafilatura async def cache_by_id(entity, entity_id: int, cache_method): @@ -181,7 +181,7 @@ async def create_shout(_, info, inp): lead = inp.get("lead", "") body_text = trafilatura.extract(body) lead_text = trafilatura.extract(lead) - seo = inp.get("seo", lead_text or body_text[:300].split('. ')[:-1].join(". ")) + seo = inp.get("seo", lead_text or body_text[:300].split(". ")[:-1].join(". ")) new_shout = Shout( slug=slug, body=body, @@ -388,7 +388,7 @@ def patch_topics(session, shout, topics_input): # @login_required async def update_shout(_, info, shout_id: int, shout_input=None, publish=False): logger.info(f"Starting update_shout with id={shout_id}, publish={publish}") - logger.debug(f"Full shout_input: {shout_input}") # DraftInput + logger.debug(f"Full shout_input: {shout_input}") # DraftInput user_id = info.context.get("user_id") roles = info.context.get("roles", []) diff --git a/resolvers/topic.py b/resolvers/topic.py index da46734d..4ecf241b 100644 --- a/resolvers/topic.py +++ b/resolvers/topic.py @@ -6,7 +6,7 @@ from cache.cache import ( get_cached_topic_authors, get_cached_topic_by_slug, get_cached_topic_followers, - invalidate_cache_by_prefix + invalidate_cache_by_prefix, ) from orm.author import Author from orm.topic import Topic @@ -126,7 +126,7 @@ async def get_topics_with_stats(limit=100, offset=0, community_id=None, by=None) GROUP BY topic """ followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query))} - + # Запрос на получение статистики авторов для выбранных тем authors_stats_query = f""" SELECT st.topic, COUNT(DISTINCT sa.author) as authors_count @@ -149,7 +149,6 @@ async def get_topics_with_stats(limit=100, offset=0, community_id=None, by=None) """ comments_stats = {row[0]: row[1] for row in session.execute(text(comments_stats_query))} - # Формируем результат с добавлением статистики result = [] for topic in topics: @@ -158,7 +157,7 @@ async def get_topics_with_stats(limit=100, offset=0, community_id=None, by=None) "shouts": shouts_stats.get(topic.id, 0), "followers": followers_stats.get(topic.id, 0), "authors": authors_stats.get(topic.id, 0), - "comments": comments_stats.get(topic.id, 0) + "comments": comments_stats.get(topic.id, 0), } result.append(topic_dict) diff --git a/services/viewed.py b/services/viewed.py index a2b2702e..a9ddeed1 100644 --- a/services/viewed.py +++ b/services/viewed.py @@ -30,6 +30,7 @@ class ViewedStorage: Класс для хранения и доступа к данным о просмотрах. Использует Redis в качестве основного хранилища и Google Analytics для сбора новых данных. """ + lock = asyncio.Lock() views_by_shout = {} shouts_by_topic = {} @@ -68,42 +69,42 @@ class ViewedStorage: async def load_views_from_redis(): """Загрузка предварительно подсчитанных просмотров из Redis""" self = ViewedStorage - + # Подключаемся к Redis если соединение не установлено if not redis._client: await redis.connect() - + # Получаем список всех ключей migrated_views_* и находим самый последний keys = await redis.execute("KEYS", "migrated_views_*") if not keys: logger.warning(" * No migrated_views keys found in Redis") return - + # Фильтруем только ключи timestamp формата (исключаем migrated_views_slugs) timestamp_keys = [k for k in keys if k != "migrated_views_slugs"] if not timestamp_keys: logger.warning(" * No migrated_views timestamp keys found in Redis") return - + # Сортируем по времени создания (в названии ключа) и берем последний timestamp_keys.sort() latest_key = timestamp_keys[-1] self.redis_views_key = latest_key - + # Получаем метку времени создания для установки start_date timestamp = await redis.execute("HGET", latest_key, "_timestamp") if timestamp: self.last_update_timestamp = int(timestamp) timestamp_dt = datetime.fromtimestamp(int(timestamp)) self.start_date = timestamp_dt.strftime("%Y-%m-%d") - + # Если данные сегодняшние, считаем их актуальными now_date = datetime.now().strftime("%Y-%m-%d") if now_date == self.start_date: logger.info(" * Views data is up to date!") else: logger.warning(f" * Views data is from {self.start_date}, may need update") - + # Выводим информацию о количестве загруженных записей total_entries = await redis.execute("HGET", latest_key, "_total") if total_entries: @@ -160,33 +161,33 @@ class ViewedStorage: async def get_shout(shout_slug="", shout_id=0) -> int: """ Получение метрики просмотров shout по slug или id. - + Args: shout_slug: Slug публикации shout_id: ID публикации - + Returns: int: Количество просмотров """ self = ViewedStorage - + # Получаем данные из Redis для новой схемы хранения if not redis._client: await redis.connect() - + fresh_views = self.views_by_shout.get(shout_slug, 0) - + # Если есть id, пытаемся получить данные из Redis по ключу migrated_views_ if shout_id and self.redis_views_key: precounted_views = await redis.execute("HGET", self.redis_views_key, str(shout_id)) if precounted_views: return fresh_views + int(precounted_views) - + # Если нет id или данных, пытаемся получить по slug из отдельного хеша precounted_views = await redis.execute("HGET", "migrated_views_slugs", shout_slug) if precounted_views: return fresh_views + int(precounted_views) - + return fresh_views @staticmethod @@ -316,4 +317,4 @@ class ViewedStorage: except Exception as e: logger.error(f"Google Analytics API Error: {e}") - return 0 \ No newline at end of file + return 0