### ✨ 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:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user