fmt+refactor
All checks were successful
Deploy on push / deploy (push) Successful in 24s

This commit is contained in:
Untone 2024-02-23 19:35:40 +03:00
parent 14947225a6
commit e80b3ac770
9 changed files with 307 additions and 119 deletions

View File

@ -1,10 +1,5 @@
name: 'Deploy to core' name: 'Deploy on push'
on: on: [push]
push:
branches:
- main
- dev
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -25,6 +20,6 @@ jobs:
- name: Push to dokku - name: Push to dokku
uses: dokku/github-action@master uses: dokku/github-action@master
with: with:
branch: 'dev' branch: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'main' || 'dev' }}
git_remote_url: 'ssh://dokku@v2.discours.io:22/core' git_remote_url: ${{ github.ref == 'refs/heads/dev' && 'ssh://dokku@v2.discours.io:22/core' || 'ssh://dokku@staging.discours.io:22/core' }}
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

View File

@ -1,6 +1,5 @@
import json import json
import time import time
from typing import List
from sqlalchemy import and_, desc, select, or_, distinct, func from sqlalchemy import and_, desc, select, or_, distinct, func
from sqlalchemy.orm import aliased from sqlalchemy.orm import aliased
@ -131,8 +130,12 @@ def load_author_with_stats(q):
.count() .count()
) )
author.stat['rating'] = likes_count - dislikes_count author.stat['rating'] = likes_count - dislikes_count
author.stat['rating_shouts'] = count_author_shouts_rating(session, author.id) author.stat['rating_shouts'] = count_author_shouts_rating(
author.stat['rating_comments'] = count_author_comments_rating(session, author.id) session, author.id
)
author.stat['rating_comments'] = count_author_comments_rating(
session, author.id
)
author.stat['commented'] = comments_count author.stat['commented'] = comments_count
return author return author
@ -205,9 +208,7 @@ def load_authors_by(_, _info, by, limit, offset):
@query.field('get_author_follows') @query.field('get_author_follows')
def get_author_follows( def get_author_follows(_, _info, slug='', user=None, author_id=None):
_, _info, slug='', user=None, author_id=None
) -> List[Author]:
with local_session() as session: with local_session() as session:
if not user and (author_id or slug): if not user and (author_id or slug):
user_query_result = ( user_query_result = (
@ -270,28 +271,26 @@ def create_author(user_id: str, slug: str, name: str = ''):
@query.field('get_author_followers') @query.field('get_author_followers')
def get_author_followers(_, _info, slug): def get_author_followers(_, _info, slug):
author_alias = aliased(Author) author_alias = aliased(Author)
author_follower_alias = aliased(AuthorFollower) alias_author_followers = aliased(AuthorFollower)
shout_author_alias = aliased(ShoutAuthor) alias_author_authors = aliased(AuthorFollower)
alias_author_follower_followers = aliased(AuthorFollower)
alias_shout_author = aliased(ShoutAuthor)
q = ( q = (
select(author_alias) select(author_alias)
.join(author_follower_alias, author_follower_alias.author == author_alias.id) .join(alias_author_authors, alias_author_authors.follower_id == author_alias.id)
.join(Author, Author.id == author_follower_alias.follower) .join(
alias_author_followers, alias_author_followers.author_id == author_alias.id
)
.filter(author_alias.slug == slug) .filter(author_alias.slug == slug)
.add_columns( .add_columns(
func.count(distinct(shout_author_alias.shout)).label('shouts_stat'), func.count(distinct(alias_shout_author.shout)).label('shouts_stat'),
func.count(distinct(author_follower_alias.author)).label('authors_stat'), func.count(distinct(alias_author_authors.author_id)).label('authors_stat'),
func.count(distinct(author_follower_alias.follower)).label('followers_stat') func.count(distinct(alias_author_follower_followers.follower_id)).label(
) 'followers_stat'
.outerjoin(shout_author_alias, author_alias.id == shout_author_alias.author) ),
.outerjoin(
aliased(AuthorFollower, name="author_follower_1"),
author_follower_alias.follower == author_alias.id
)
.outerjoin(
aliased(AuthorFollower, name="author_follower_2"),
author_follower_alias.author == author_alias.id
) )
.outerjoin(alias_shout_author, author_alias.id == alias_shout_author.author_id)
.group_by(author_alias.id) .group_by(author_alias.id)
) )

View File

@ -60,10 +60,18 @@ def create_shout(_, info, inp):
'published_at': None, 'published_at': None,
'created_at': current_time, # Set created_at as Unix timestamp 'created_at': current_time, # Set created_at as Unix timestamp
} }
same_slug_shout = session.query(Shout).filter(Shout.slug == shout_dict.get('slug')).first() same_slug_shout = (
session.query(Shout)
.filter(Shout.slug == shout_dict.get('slug'))
.first()
)
c = 1 c = 1
while same_slug_shout is not None: while same_slug_shout is not None:
same_slug_shout = session.query(Shout).filter(Shout.slug == shout_dict.get('slug')).first() same_slug_shout = (
session.query(Shout)
.filter(Shout.slug == shout_dict.get('slug'))
.first()
)
c += 1 c += 1
shout_dict['slug'] += f'-{c}' shout_dict['slug'] += f'-{c}'
new_shout = Shout(**shout_dict) new_shout = Shout(**shout_dict)
@ -174,10 +182,18 @@ async def update_shout(_, info, shout_id, shout_input=None, publish=False):
if slug: if slug:
shout_by_id = session.query(Shout).filter(Shout.id == shout_id).first() shout_by_id = session.query(Shout).filter(Shout.id == shout_id).first()
if shout_by_id and slug != shout_by_id.slug: if shout_by_id and slug != shout_by_id.slug:
same_slug_shout = session.query(Shout).filter(Shout.slug == shout_input.get('slug')).first() same_slug_shout = (
session.query(Shout)
.filter(Shout.slug == shout_input.get('slug'))
.first()
)
c = 1 c = 1
while same_slug_shout is not None: while same_slug_shout is not None:
same_slug_shout = session.query(Shout).filter(Shout.slug == shout_input.get('slug')).first() same_slug_shout = (
session.query(Shout)
.filter(Shout.slug == shout_input.get('slug'))
.first()
)
c += 1 c += 1
slug += f'-{c}' slug += f'-{c}'
shout_input['slug'] = slug shout_input['slug'] = slug

View File

@ -2,19 +2,18 @@ import json
import time import time
from typing import List from typing import List
from sqlalchemy import select, or_, func from sqlalchemy import select, or_
from sqlalchemy.orm import aliased
from sqlalchemy.sql import and_ from sqlalchemy.sql import and_
from orm.author import Author, AuthorFollower from orm.author import Author, AuthorFollower
# from orm.community import Community # from orm.community import Community
from orm.reaction import Reaction from orm.reaction import Reaction
from orm.shout import Shout, ShoutReactionsFollower, ShoutAuthor, ShoutTopic from orm.shout import Shout, ShoutReactionsFollower
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from resolvers.community import community_follow, community_unfollow from resolvers.community import community_follow, community_unfollow
from resolvers.topic import topic_follow, topic_unfollow from resolvers.topic import topic_follow, topic_unfollow
from resolvers.stat import get_authors_with_stat from resolvers.stat import get_authors_with_stat, query_follows
from services.auth import login_required from services.auth import login_required
from services.db import local_session from services.db import local_session
from services.follows import DEFAULT_FOLLOWS from services.follows import DEFAULT_FOLLOWS
@ -86,61 +85,6 @@ async def unfollow(_, info, what, slug):
return {} return {}
def query_follows(user_id: str):
logger.debug(f'query follows for {user_id} from database')
topics = []
authors = []
with local_session() as session:
author = session.query(Author).filter(Author.user == user_id).first()
if isinstance(author, Author):
author_id = author.id
aliased_author = aliased(Author)
aliased_author_followers = aliased(AuthorFollower)
aliased_author_authors = aliased(AuthorFollower)
authors = (
session.query(
aliased_author,
func.count(func.distinct(ShoutAuthor.shout)).label("shouts_stat"),
func.count(func.distinct(AuthorFollower.author)).label("authors_stat"),
func.count(func.distinct(AuthorFollower.follower)).label("followers_stat")
)
.select_from(aliased_author)
.filter(AuthorFollower.author == aliased_author.id)
.join(AuthorFollower, AuthorFollower.follower == aliased_author.id)
.outerjoin(ShoutAuthor, ShoutAuthor.author == author_id)
.outerjoin(aliased_author_authors, AuthorFollower.follower == author_id)
.outerjoin(aliased_author_followers, AuthorFollower.author == author_id)
.group_by(aliased_author.id)
.all()
)
aliased_shout_authors = aliased(ShoutAuthor)
aliased_topic_followers = aliased(TopicFollower)
aliased_topic = aliased(Topic)
topics = (
session.query(
aliased_topic,
func.count(func.distinct(ShoutTopic.shout)).label("shouts_stat"),
func.count(func.distinct(ShoutAuthor.author)).label("authors_stat"),
func.count(func.distinct(TopicFollower.follower)).label("followers_stat")
)
.select_from(aliased_topic)
.join(TopicFollower, TopicFollower.topic == aliased_topic.id)
.outerjoin(ShoutTopic, aliased_topic.id == ShoutTopic.topic)
.outerjoin(aliased_shout_authors, ShoutTopic.shout == aliased_shout_authors.shout)
.outerjoin(aliased_topic_followers, aliased_topic_followers.topic == aliased_topic.id)
.group_by(aliased_topic.id)
.all()
)
return {
'topics': topics,
'authors': authors,
'communities': [{'id': 1, 'name': 'Дискурс', 'slug': 'discours'}],
}
async def get_follows_by_user_id(user_id: str): async def get_follows_by_user_id(user_id: str):
if user_id: if user_id:
author = await redis.execute('GET', f'user:{user_id}:author') author = await redis.execute('GET', f'user:{user_id}:author')

View File

@ -1,4 +1,4 @@
from sqlalchemy import func, distinct from sqlalchemy import func, select, distinct, alias
from sqlalchemy.orm import aliased from sqlalchemy.orm import aliased
from orm.topic import TopicFollower, Topic from orm.topic import TopicFollower, Topic
@ -14,10 +14,20 @@ def add_topic_stat_columns(q):
q = ( q = (
q.outerjoin(ShoutTopic, aliased_topic.id == ShoutTopic.topic) q.outerjoin(ShoutTopic, aliased_topic.id == ShoutTopic.topic)
.add_columns(func.count(distinct(ShoutTopic.shout)).label('shouts_stat')) .add_columns(func.count(distinct(ShoutTopic.shout)).label('shouts_stat'))
.outerjoin(aliased_shout_authors, ShoutTopic.shout == aliased_shout_authors.shout) .outerjoin(
.add_columns(func.count(distinct(aliased_shout_authors.author)).label('authors_stat')) aliased_shout_authors, ShoutTopic.shout == aliased_shout_authors.shout
.outerjoin(aliased_topic_followers, aliased_topic.id == aliased_topic_followers.topic) )
.add_columns(func.count(distinct(aliased_topic_followers.follower)).label('followers_stat')) .add_columns(
func.count(distinct(aliased_shout_authors.author)).label('authors_stat')
)
.outerjoin(
aliased_topic_followers, aliased_topic.id == aliased_topic_followers.topic
)
.add_columns(
func.count(distinct(aliased_topic_followers.follower)).label(
'followers_stat'
)
)
) )
q = q.group_by(aliased_topic.id) q = q.group_by(aliased_topic.id)
@ -27,15 +37,28 @@ def add_topic_stat_columns(q):
def add_author_stat_columns(q): def add_author_stat_columns(q):
aliased_author_authors = aliased(AuthorFollower, name='af_authors') aliased_author_authors = aliased(AuthorFollower, name='af_authors')
aliased_author_followers = aliased(AuthorFollower, name='af_followers') # Добавлен второй псевдоним aliased_author_followers = aliased(
AuthorFollower, name='af_followers'
) # Добавлен второй псевдоним
aliased_author = aliased(Author) aliased_author = aliased(Author)
q = ( q = (
q.outerjoin(ShoutAuthor, aliased_author.id == ShoutAuthor.author) q.outerjoin(ShoutAuthor, aliased_author.id == ShoutAuthor.author)
.add_columns(func.count(distinct(ShoutAuthor.shout)).label('shouts_stat')) .add_columns(func.count(distinct(ShoutAuthor.shout)).label('shouts_stat'))
.outerjoin(aliased_author_authors, aliased_author_authors.follower == aliased_author.id) .outerjoin(
.add_columns(func.count(distinct(aliased_author_authors.author)).label('authors_stat')) aliased_author_authors, aliased_author_authors.follower == aliased_author.id
.outerjoin(aliased_author_followers, aliased_author_followers.author == aliased_author.id) # Используется второй псевдоним )
.add_columns(func.count(distinct(aliased_author_followers.follower)).label('followers_stat')) # Используется второй псевдоним .add_columns(
func.count(distinct(aliased_author_authors.author)).label('authors_stat')
)
.outerjoin(
aliased_author_followers,
aliased_author_followers.author == aliased_author.id,
) # Используется второй псевдоним
.add_columns(
func.count(distinct(aliased_author_followers.follower)).label(
'followers_stat'
)
) # Используется второй псевдоним
) )
q = q.group_by(aliased_author.id) q = q.group_by(aliased_author.id)
@ -50,7 +73,7 @@ def execute_with_ministat(q):
entity.stat = { entity.stat = {
'shouts': shouts_stat, 'shouts': shouts_stat,
'authors': authors_stat, 'authors': authors_stat,
'followers': followers_stat 'followers': followers_stat,
} }
records.append(entity) records.append(entity)
@ -65,3 +88,145 @@ def get_authors_with_stat(q):
def get_topics_with_stat(q): def get_topics_with_stat(q):
q = add_topic_stat_columns(q) q = add_topic_stat_columns(q)
return execute_with_ministat(q) return execute_with_ministat(q)
def query_follows(author_id: int):
subquery_shout_author = (
select(
[
ShoutAuthor.author,
func.count(distinct(ShoutAuthor.shout)).label('shouts_stat'),
]
)
.group_by(ShoutAuthor.author)
.where(ShoutAuthor.author == author_id)
.alias()
)
subquery_author_followers = (
select(
[
AuthorFollower.author,
func.count(distinct(AuthorFollower.author)).label('authors_stat'),
]
)
.group_by(AuthorFollower.author)
.where(AuthorFollower.author == author_id)
.alias()
)
subquery_author_followers = (
select(
[
AuthorFollower.follower,
func.count(distinct(AuthorFollower.follower)).label('followers_stat'),
]
)
.group_by(AuthorFollower.follower)
.where(AuthorFollower.follower == author_id)
.alias()
)
subq_shout_author_alias = alias(subquery_shout_author)
subq_author_followers_alias = alias(
subquery_author_followers, name='subq_author_followers'
)
subq_author_authors_alias = alias(
subquery_author_followers, name='subq_author_authors'
)
authors_query = (
select(
[
Author.id,
subq_shout_author_alias.c.shouts_stat,
subq_author_authors_alias.c.authors_stat,
subq_author_followers_alias.c.followers_stat,
]
)
.select_from(Author)
.outerjoin(
subq_shout_author_alias, Author.id == subq_shout_author_alias.c.author
)
.outerjoin(
subq_author_authors_alias, Author.id == subq_author_followers_alias.c.author
)
.outerjoin(
subq_author_followers_alias,
Author.id == subq_author_followers_alias.c.follower,
)
)
authors = execute_with_ministat(authors_query)
subquery_shout_topic = (
select(
[
ShoutTopic.topic,
func.count(distinct(ShoutTopic.shout)).label('shouts_stat'),
]
)
.group_by(ShoutTopic.topic)
.alias()
)
subquery_shout_topic_authors = (
select(
[
ShoutTopic.topic,
func.count(distinct(ShoutTopic.author)).label('authors_stat'),
]
)
.group_by(ShoutTopic.topic)
.alias()
)
subquery_topic_followers = (
select(
[
TopicFollower.topic,
func.count(distinct(TopicFollower.follower)).label('followers_stat'),
]
)
.group_by(TopicFollower.topic_id)
.alias()
)
subq_shout_topic_alias = alias(subquery_shout_topic)
subq_shout_topic_authors_alias = alias(
subquery_shout_topic_authors, name='subq_shout_topic_authors'
)
subq_topic_followers_alias = alias(
subquery_topic_followers, name='subq_topic_followers'
)
topics_query = (
select(
[
Topic.id,
subq_shout_topic_alias.columns.shouts_stat,
subq_shout_topic_authors_alias.columns.authors_stat,
subq_topic_followers_alias.columns.followers_stat,
]
)
.select_from(Topic)
.outerjoin(
subq_shout_topic_alias, Topic.id == subq_shout_topic_alias.columns.topic_id
)
.outerjoin(
subq_shout_topic_authors_alias,
Topic.id == subq_shout_topic_authors_alias.columns.topic_id,
)
.outerjoin(
subq_topic_followers_alias,
Topic.id == subq_topic_followers_alias.columns.topic_id,
)
)
topics = execute_with_ministat(topics_query)
return {
'topics': topics,
'authors': authors,
'communities': [{'id': 1, 'name': 'Дискурс', 'slug': 'discours'}],
}

View File

@ -34,12 +34,7 @@ async def check_auth(req):
logger.debug(f'{token}') logger.debug(f'{token}')
query_name = 'validate_jwt_token' query_name = 'validate_jwt_token'
operation = 'ValidateToken' operation = 'ValidateToken'
variables = { variables = {'params': {'token_type': 'access_token', 'token': token}}
'params': {
'token_type': 'access_token',
'token': token,
}
}
gql = { gql = {
'query': f'query {operation}($params: ValidateJWTTokenInput!) {{' 'query': f'query {operation}($params: ValidateJWTTokenInput!) {{'

View File

@ -33,9 +33,7 @@ def after_cursor_execute(conn, cursor, statement, parameters, context, executema
elapsed = time.time() - conn.query_start_time elapsed = time.time() - conn.query_start_time
del conn.query_start_time del conn.query_start_time
if elapsed > 0.9: # Adjust threshold as needed if elapsed > 0.9: # Adjust threshold as needed
logger.debug( logger.debug(f"\n{statement}\n{'*' * math.floor(elapsed)} {elapsed:.3f} s")
f"\n{statement}\n{'*' * math.floor(elapsed)} {elapsed:.3f} s"
)
def local_session(src=''): def local_session(src=''):

View File

@ -12,9 +12,7 @@ from services.rediscache import redis
DEFAULT_FOLLOWS = { DEFAULT_FOLLOWS = {
'topics': [], 'topics': [],
'authors': [], 'authors': [],
'communities': [ 'communities': [{'slug': 'discours', 'name': 'Дискурс', 'id': 1, 'desc': ''}],
{'slug': 'discours', 'name': 'Дискурс', 'id': 1, 'desc': ''}
],
} }

View File

@ -0,0 +1,78 @@
from unittest.mock import Mock
from resolvers.stat import query_follows
def test_query_follows():
user_id = 'user123'
# Mocking database session and ORM models
mock_session = Mock()
mock_Author = Mock()
mock_ShoutAuthor = Mock()
mock_AuthorFollower = Mock()
mock_Topic = Mock()
mock_ShoutTopic = Mock()
mock_TopicFollower = Mock()
# Mocking expected query results
expected_result = {
'topics': [(1, 5, 10, 15), (2, 8, 12, 20)], # Example topics query result
'authors': [(101, 3, 6, 9), (102, 4, 7, 11)], # Example authors query result
'communities': [{'id': 1, 'name': 'Дискурс', 'slug': 'discours'}],
}
# Set up mocks to return expected results when queried
mock_session.query().select_from().outerjoin().all.side_effect = [
expected_result['authors'], # Authors query result
expected_result['topics'], # Topics query result
]
# Call the function to test
result = query_follows(
user_id,
session=mock_session,
Author=mock_Author,
ShoutAuthor=mock_ShoutAuthor,
AuthorFollower=mock_AuthorFollower,
Topic=mock_Topic,
ShoutTopic=mock_ShoutTopic,
TopicFollower=mock_TopicFollower,
)
# Assertions
assert result['topics'] == expected_result['topics']
assert result['authors'] == expected_result['authors']
assert result['communities'] == expected_result['communities']
# Assert that mock session was called with expected queries
expected_queries = [
mock_session.query(
mock_Author.id,
mock_ShoutAuthor.author_id,
mock_AuthorFollower.author_id,
mock_AuthorFollower.follower_id,
)
.select_from(mock_Author)
.outerjoin(mock_ShoutAuthor, mock_Author.id == mock_ShoutAuthor.author_id)
.outerjoin(mock_AuthorFollower, mock_Author.id == mock_AuthorFollower.author_id)
.outerjoin(
mock_AuthorFollower, mock_Author.id == mock_AuthorFollower.follower_id
)
.all,
mock_session.query(
mock_Topic.id,
mock_ShoutTopic.topic_id,
mock_ShoutTopic.topic_id,
mock_TopicFollower.topic_id,
)
.select_from(mock_Topic)
.outerjoin(mock_ShoutTopic, mock_Topic.id == mock_ShoutTopic.topic_id)
.outerjoin(mock_ShoutTopic, mock_Topic.id == mock_ShoutTopic.topic_id)
.outerjoin(mock_TopicFollower, mock_Topic.id == mock_TopicFollower.topic_id)
.all,
]
mock_session.query.assert_has_calls(expected_queries)
# Run the test
test_query_follows()