0.9.32] - 2025-10-05
All checks were successful
Deploy on push / deploy (push) Successful in 5m54s

###  Features
- **Редактирование мигрированных шаутов**: Добавлена мутация `create_draft_from_shout` для создания черновика из существующего опубликованного шаута
  - Создаёт черновик со всеми данными из шаута (title, body, lead, topics, authors, media, etc.)
  - Проверяет авторство перед созданием черновика
  - Переиспользует существующий черновик если он уже создан для этого шаута
  - Копирует все связи: авторов и темы (включая main topic)

### 🔧 Fixed
- **NotificationEntity enum**: Исправлена ошибка `NotificationEntity.FOLLOWER` → `NotificationEntity.AUTHOR`
  - В enum не было значения `FOLLOWER`, используется `AUTHOR` для уведомлений о подписчиках

### Technical Details
- `core/schema/mutation.graphql`: добавлена мутация `create_draft_from_shout(shout_id: Int!): CommonResult!`
- `core/resolvers/draft.py`: добавлен resolver `create_draft_from_shout` с валидацией авторства
- `core/resolvers/notifier.py`: исправлено использование `NotificationEntity.AUTHOR` вместо несуществующего `FOLLOWER`
This commit is contained in:
2025-10-05 17:12:28 +03:00
parent 13343bb40e
commit 86dec15673
5 changed files with 123 additions and 2 deletions

View File

@@ -1,5 +1,23 @@
# Changelog
## [0.9.32] - 2025-10-05
### ✨ Features
- **Редактирование мигрированных шаутов**: Добавлена мутация `create_draft_from_shout` для создания черновика из существующего опубликованного шаута
- Создаёт черновик со всеми данными из шаута (title, body, lead, topics, authors, media, etc.)
- Проверяет авторство перед созданием черновика
- Переиспользует существующий черновик если он уже создан для этого шаута
- Копирует все связи: авторов и темы (включая main topic)
### 🔧 Fixed
- **NotificationEntity enum**: Исправлена ошибка `NotificationEntity.FOLLOWER``NotificationEntity.AUTHOR`
- В enum не было значения `FOLLOWER`, используется `AUTHOR` для уведомлений о подписчиках
### Technical Details
- `core/schema/mutation.graphql`: добавлена мутация `create_draft_from_shout(shout_id: Int!): CommonResult!`
- `core/resolvers/draft.py`: добавлен resolver `create_draft_from_shout` с валидацией авторства
- `core/resolvers/notifier.py`: исправлено использование `NotificationEntity.AUTHOR` вместо несуществующего `FOLLOWER`
## [0.9.31] - 2025-10-04
### ✅ Fixed: Notifications TODOs

View File

@@ -1,6 +1,6 @@
[project]
name = "discours-core"
version = "0.9.28"
version = "0.9.32"
description = "Core backend for Discours.io platform"
authors = [
{name = "Tony Rewin", email = "tonyrewin@yandex.ru"}

View File

@@ -274,6 +274,108 @@ async def create_draft(_: None, info: GraphQLResolveInfo, draft_input: dict[str,
return {"error": f"Failed to create draft: {e!s}"}
@mutation.field("create_draft_from_shout")
@login_required
async def create_draft_from_shout(_: None, info: GraphQLResolveInfo, shout_id: int) -> dict[str, Any]:
"""
Создаёт черновик из существующего опубликованного шаута для редактирования.
Args:
info: GraphQL context
shout_id (int): ID публикации (shout)
Returns:
dict: Contains either:
- draft: The created draft object with shout reference
- error: Error message if creation failed
Example:
>>> async def test_create_from_shout():
... context = {'user_id': '123', 'author': {'id': 1}}
... info = type('Info', (), {'context': context})()
... result = await create_draft_from_shout(None, info, 42)
... assert result.get('error') is None
... assert result['draft'].shout == 42
... return result
"""
author_dict = info.context.get("author") or {}
author_id = author_dict.get("id")
if not author_id or not isinstance(author_id, int):
return {"error": "Author ID is required"}
try:
with local_session() as session:
# Загружаем шаут с авторами и темами
shout = (
session.query(Shout)
.options(joinedload(Shout.authors), joinedload(Shout.topics))
.where(Shout.id == shout_id)
.first()
)
if not shout:
return {"error": f"Shout with id={shout_id} not found"}
# Проверяем, что пользователь является автором шаута
author_ids = [a.id for a in shout.authors]
if author_id not in author_ids:
return {"error": "You are not authorized to edit this shout"}
# Проверяем, нет ли уже черновика для этого шаута
existing_draft = session.query(Draft).where(Draft.shout == shout_id).first()
if existing_draft:
logger.info(f"Draft already exists for shout {shout_id}: draft_id={existing_draft.id}")
return {"draft": create_draft_dict(existing_draft)}
# Создаём новый черновик из шаута
now = int(time.time())
draft = Draft(
created_at=now,
created_by=author_id,
community=shout.community,
layout=shout.layout or "article",
title=shout.title or "",
subtitle=shout.subtitle,
body=shout.body or "",
lead=shout.lead,
slug=shout.slug,
cover=shout.cover,
cover_caption=shout.cover_caption,
seo=shout.seo,
media=shout.media,
lang=shout.lang or "ru",
shout=shout_id, # Связываем с существующим шаутом
)
session.add(draft)
session.flush()
# Копируем авторов из шаута
for author in shout.authors:
da = DraftAuthor(draft=draft.id, author=author.id)
session.add(da)
# Копируем темы из шаута
shout_topics = session.query(ShoutTopic).where(ShoutTopic.shout == shout_id).all()
for st in shout_topics:
dt = DraftTopic(draft=draft.id, topic=st.topic, main=st.main)
session.add(dt)
session.commit()
logger.info(f"Created draft {draft.id} from shout {shout_id}")
# Формируем результат
draft_dict = create_draft_dict(draft)
return {"draft": draft_dict}
except Exception as e:
logger.error(f"Failed to create draft from shout {shout_id}: {e}", exc_info=True)
return {"error": f"Failed to create draft from shout: {e!s}"}
def generate_teaser(body: str, limit: int = 300) -> str:
body_text = extract_text(body)
return ". ".join(body_text[:limit].split(". ")[:-1])

View File

@@ -286,7 +286,7 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
if thread == "followers":
# Mark follower notifications as seen
query_conditions = [
Notification.entity == NotificationEntity.FOLLOWER.value,
Notification.entity == NotificationEntity.AUTHOR.value,
]
if after_datetime:
query_conditions.append(Notification.created_at > after_datetime)

View File

@@ -24,6 +24,7 @@ type Mutation {
# draft
create_draft(draft_input: DraftInput!): CommonResult!
create_draft_from_shout(shout_id: Int!): CommonResult!
update_draft(draft_id: Int!, draft_input: DraftInput!): CommonResult!
delete_draft(draft_id: Int!): CommonResult!
# publication