From 79f7c914d7c92419c4e0692df4c43fa4902331e2 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 20 Nov 2024 23:59:11 +0300 Subject: [PATCH] v0.4.7 --- CHANGELOG.md | 17 ++++++++ README.md | 87 +++++++++++++++++++++++++++++++++++------ docs/follower.md | 94 +++++++++++++++++++++++++++++++++++++++++++++ docs/load_shouts.md | 89 ++++++++++++++++++++++++++++++++++-------- docs/rating.md | 82 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 6 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 docs/follower.md create mode 100644 docs/rating.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 319b46ba..1360017a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +#### [0.4.7] +- `get_my_rates_shouts` resolver added with: + - `shout_id` and `my_rate` fields in response + - filters by `Reaction.deleted_at.is_(None)` + - filters by `Reaction.kind.in_([ReactionKind.LIKE.value, ReactionKind.DISLIKE.value])` + - filters by `Reaction.reply_to.is_(None)` + - uses `local_session()` context manager + - returns empty list on errors +- SQLAlchemy syntax updated: + - `select()` statement fixed for newer versions + - `Reaction` model direct selection instead of labeled columns + - proper row access with `row[0].shout` and `row[0].kind` +- GraphQL resolver fixes: + - added root parameter `_` to match schema + - proper async/await handling with `@login_required` + - error logging added via `logger.error()` + #### [0.4.6] - login_accepted decorator added - `docs` added diff --git a/README.md b/README.md index 6f1ef42a..4cfe4a4d 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,46 @@ -# discoursio-api +# GraphQL API Backend -## Техстек +Backend service providing GraphQL API for content management system with reactions, ratings and comments. -- sqlalchemy -- redis -- ariadne -- starlette -- granian +## Core Features -## Локальная разработка +### Shouts (Posts) +- CRUD operations via GraphQL mutations +- Rich filtering and sorting options +- Support for multiple authors and topics +- Rating system with likes/dislikes +- Comments and nested replies +- Bookmarks and following -Запустите API-сервер с ключом `dev`: +### Reactions System +- `ReactionKind` types: LIKE, DISLIKE, COMMENT +- Rating calculation for shouts and comments +- User-specific reaction tracking +- Reaction stats and aggregations +- Nested comments support + +### Authors & Topics +- Author profiles with stats +- Topic categorization and hierarchy +- Following system for authors/topics +- Activity tracking and stats +- Community features + +## Tech Stack + +- **(Python)[https://www.python.org/]** 3.12+ +- **GraphQL** with [Ariadne](https://ariadnegraphql.org/) +- **(SQLAlchemy)[https://docs.sqlalchemy.org/en/20/orm/]** +- **(PostgreSQL)[https://www.postgresql.org/]/(SQLite)[https://www.sqlite.org/]** support +- **(Starlette)[https://www.starlette.io/]** for ASGI server +- **(Redis)[https://redis.io/]** for caching + +## Development + +### Setup + + +Start API server with `dev` key: ```shell mkdir .venv @@ -20,10 +50,43 @@ poetry update poetry run server.py dev ``` -### Полезные команды +### Useful Commands ```shell -poetry run ruff check . --fix --select I # линтер и сортировка импортов -poetry run ruff format . --line-length=120 # форматирование кода +# Linting and import sorting +poetry run ruff check . --fix --select I + +# Code formatting +poetry run ruff format . --line-length=120 + +# Run tests +poetry run pytest + +# Type checking +poetry run mypy . ``` +### Code Style + +We use: +- Ruff for linting and import sorting +- Line length: 120 characters +- Python type hints +- Docstrings for public methods + +### GraphQL Development + +Test queries in GraphQL Playground at `http://localhost:8000`: + +```graphql +# Example query +query GetShout($slug: String) { + get_shout(slug: $slug) { + id + title + main_author { + name + } + } +} +``` diff --git a/docs/follower.md b/docs/follower.md new file mode 100644 index 00000000..ce349f74 --- /dev/null +++ b/docs/follower.md @@ -0,0 +1,94 @@ +# Following System + +## Overview +System supports following different entity types: +- Authors +- Topics +- Communities +- Shouts (Posts) + +## GraphQL API + +### Mutations + +#### follow +Follow an entity (author/topic/community/shout). + +**Parameters:** +- `what: String!` - Entity type (`AUTHOR`, `TOPIC`, `COMMUNITY`, `SHOUT`) +- `slug: String` - Entity slug +- `entity_id: Int` - Optional entity ID + +**Returns:** +```typescript +{ + authors?: Author[] // For AUTHOR type + topics?: Topic[] // For TOPIC type + communities?: Community[] // For COMMUNITY type + shouts?: Shout[] // For SHOUT type + error?: String // Error message if any +} +``` + +#### unfollow +Unfollow an entity. + +**Parameters:** Same as `follow` + +**Returns:** Same as `follow` + +### Queries + +#### get_shout_followers +Get list of users who reacted to a shout. + +**Parameters:** +- `slug: String` - Shout slug +- `shout_id: Int` - Optional shout ID + +**Returns:** +```typescript +Author[] // List of authors who reacted +``` + +## Caching System + +### Supported Entity Types +- Authors: `cache_author`, `get_cached_follower_authors` +- Topics: `cache_topic`, `get_cached_follower_topics` +- Communities: No cache +- Shouts: No cache + +### Cache Flow +1. On follow/unfollow: + - Update entity in cache + - Update follower's following list +2. Cache is updated before notifications + +## Notifications + +- Sent when author is followed/unfollowed +- Contains: + - Follower info + - Author ID + - Action type ("follow"/"unfollow") + +## Error Handling + +- Unauthorized access check +- Entity existence validation +- Duplicate follow prevention +- Full error logging +- Transaction safety with `local_session()` + +## Database Schema + +### Follower Tables +- `AuthorFollower` +- `TopicFollower` +- `CommunityFollower` +- `ShoutReactionsFollower` + +Each table contains: +- `follower` - ID of following user +- `{entity_type}` - ID of followed entity \ No newline at end of file diff --git a/docs/load_shouts.md b/docs/load_shouts.md index 4d026d32..d0a6d897 100644 --- a/docs/load_shouts.md +++ b/docs/load_shouts.md @@ -1,23 +1,80 @@ -## Reader resolvers +# Система загрузки публикаций -### load_shouts_by +## Особенности реализации -Если graphql запрос содержит ожидаемые поля `stat`, `authors` или `topics`, то будут выполнены дополнительные подзапросы. +### Базовый запрос +- Автоматически подгружает основного автора +- Добавляет основную тему публикации +- Поддерживает гибкую систему фильтрации +- Оптимизирует запросы на основе запрошенных полей -#### Параметры +### Статистика +- Подсчёт лайков/дислайков +- Количество комментариев +- Дата последней реакции +- Статистика подгружается только при запросе поля `stat` -- `filters` - словарь с фильтрами - - `featured` - фильтрует публикации, одобренные для показа на главной, по умолчанию не применяется - - `topics` - список идентификаторов тем, по умолчанию не применяется - - `authors` - список идентификаторов авторов, по умолчанию не применяется - - `after` - unixtime после которого будут выбраны публикации, по умолчанию не применяется - - `layouts` - список идентификаторов форматов, по умолчанию не применяется -- `order_by` может быть `rating`, `comments_count`, `last_reacted_at`. По умолчанию применяется сортировка по `published_at` -- `order_by_desc` определяет порядок сортировки, по умолчанию применяется `desc` -- `offset` и `limit` определяют смещение и ограничение, по умолчанию `0` и `10` +### Оптимизация производительности +- Ленивая загрузка связанных данных +- Кэширование результатов на 5 минут +- Пакетная загрузка авторов и тем +- Использование подзапросов для сложных выборок -### load_shouts_feed, load_shouts_followed, load_shouts_followed_by, load_shouts_discussed, load_shouts_reacted +## Типы лент -Параметры аналогичны `load_shouts_by`, но применяются дополнительные фильтры: +### Случайные топовые посты (load_shouts_random_top) +**Преимущества:** +- Разнообразный контент +- Быстрая выборка из кэша топовых постов +- Настраиваемый размер пула для выборки -- `reacted` - фильтр наличия реакции пользователя \ No newline at end of file +**Ограничения:** +- Обновление раз в 5 минут +- Максимальный размер пула: 100 постов +- Учитываются только лайки/дислайки (без комментариев) + +### Неоцененные посты (load_shouts_unrated) +**Преимущества:** +- Помогает найти новый контент +- Равномерное распределение оценок +- Случайный порядок выдачи + +**Ограничения:** +- Только посты с менее чем 3 реакциями +- Не учитываются комментарии +- Без сортировки по рейтингу + +### Закладки (load_shouts_bookmarked) +**Преимущества:** +- Персонализированная выборка +- Быстрый доступ к сохраненному +- Поддержка всех фильтров + +**Ограничения:** +- Требует авторизации +- Ограничение на количество закладок +- Кэширование отключено + +## Важные моменты + +### Пагинация +- Стандартный размер страницы: 10 +- Максимальный размер: 100 +- Поддержка курсор-пагинации + +### Кэширование +- TTL: 5 минут +- Инвалидация при изменении поста +- Отдельный кэш для каждого типа сортировки + +### Сортировка +- По рейтингу (лайки минус дислайки) +- По количеству комментариев +- По дате последней реакции +- По дате публикации (по умолчанию) + +### Безопасность +- Проверка прав доступа +- Фильтрация удаленного контента +- Защита от SQL-инъекций +- Валидация входных данных \ No newline at end of file diff --git a/docs/rating.md b/docs/rating.md new file mode 100644 index 00000000..bee0e775 --- /dev/null +++ b/docs/rating.md @@ -0,0 +1,82 @@ +# Rating System + +## GraphQL Resolvers + +### Queries + +#### get_my_rates_shouts +Get user's reactions (LIKE/DISLIKE) for specified posts. + +**Parameters:** +- `shouts: [Int!]!` - array of shout IDs + +**Returns:** +```typescript +[{ + shout_id: Int + my_rate: ReactionKind // LIKE or DISLIKE +}] +``` + +#### get_my_rates_comments +Get user's reactions (LIKE/DISLIKE) for specified comments. + +**Parameters:** +- `comments: [Int!]!` - array of comment IDs + +**Returns:** +```typescript +[{ + comment_id: Int + my_rate: ReactionKind // LIKE or DISLIKE +}] +``` + +### Mutations + +#### rate_author +Rate another author (karma system). + +**Parameters:** +- `rated_slug: String!` - author's slug +- `value: Int!` - rating value (positive/negative) + +## Rating Calculation + +### Author Rating Components + +#### Shouts Rating +- Calculated from LIKE/DISLIKE reactions on author's posts +- Each LIKE: +1 +- Each DISLIKE: -1 +- Excludes deleted reactions +- Excludes comment reactions + +#### Comments Rating +- Calculated from LIKE/DISLIKE reactions on author's comments +- Each LIKE: +1 +- Each DISLIKE: -1 +- Only counts reactions to COMMENT type reactions +- Excludes deleted reactions + +#### Legacy Karma +- Based on direct author ratings via `rate_author` mutation +- Stored in `AuthorRating` table +- Each positive rating: +1 +- Each negative rating: -1 + +### Helper Functions + +- `count_author_comments_rating()` - Calculate comment rating +- `count_author_shouts_rating()` - Calculate posts rating +- `get_author_rating_old()` - Get legacy karma rating +- `get_author_rating_shouts()` - Get posts rating (optimized) +- `get_author_rating_comments()` - Get comments rating (optimized) +- `add_author_rating_columns()` - Add rating columns to author query + +## Notes + +- All ratings exclude deleted content +- Reactions are unique per user/content +- Rating calculations are optimized with SQLAlchemy +- System supports both direct author rating and content-based rating \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 27abc13c..9bd7df02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "0.4.6" +version = "0.4.7" description = "core module for discours.io" authors = ["discoursio devteam"] license = "MIT"