async def get_post_header(context, author, permlink): """Fetch basic post data""" db = context['db'] valid_account(author) valid_permlink(permlink) sql = """ SELECT hp.id, ha_a.name as author, hpd_p.permlink as permlink, hcd.category as category, depth FROM hive_posts hp INNER JOIN hive_accounts ha_a ON ha_a.id = hp.author_id INNER JOIN hive_permlink_data hpd_p ON hpd_p.id = hp.permlink_id LEFT JOIN hive_category_data hcd ON hcd.id = hp.category_id WHERE ha_a.name = :author AND hpd_p.permlink = :permlink AND counter_deleted = 0 """ row = await db.query_row(sql, author=author, permlink=permlink) assert row, 'Post {}/{} does not exist'.format(author, permlink) return dict(author=row['author'], permlink=row['permlink'], category=row['category'], depth=row['depth'])
async def get_discussions_by_blog(context, tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Retrieve account's blog posts, including reblogs.""" assert not filter_tags, 'filter_tags not supported' tag = valid_account(tag) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) limit = valid_limit(limit, 100, 20) truncate_body = valid_truncate(truncate_body) sql = "SELECT * FROM bridge_get_account_posts_by_blog( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER, False )" db = context['db'] result = await db.query_all(sql, account=tag, author=start_author, permlink=start_permlink, limit=limit) posts_by_id = [] for row in result: row = dict(row) post = _condenser_post_object(row, truncate_body=truncate_body) post['active_votes'] = await find_votes_impl( db, post['author'], post['permlink'], VotesPresentation.CondenserApi) posts_by_id.append(post) return posts_by_id
async def get_active_votes(context, author: str, permlink: str): """ Returns all votes for the given post. """ valid_account(author) valid_permlink(permlink) db = context['db'] return await find_votes_impl(db, author, permlink, VotesPresentation.ActiveVotes)
async def find_votes(context, author: str, permlink: str): """ Returns all votes for the given post """ valid_account(author) valid_permlink(permlink) return { 'votes': await find_votes_impl(context['db'], author, permlink, VotesPresentation.DatabaseApi) }
async def unread_notifications(context, account, min_score=25): """Load notification status for a named account.""" db = context['db'] valid_account(account) min_score = valid_score(min_score, 100, 25) sql = """SELECT * FROM get_number_of_unread_notifications( :account, (:min_score)::SMALLINT)""" row = await db.query_row(sql, account=account, min_score=min_score) return dict(lastread=str(row['lastread_at']), unread=row['unread'])
async def get_discussion(context, author: str, permlink: str, observer: str = ''): """Modified `get_state` thread implementation.""" db = context['db'] author = valid_account(author) permlink = valid_permlink(permlink) observer = valid_account(observer, allow_empty=True) sql = "SELECT * FROM bridge_get_discussion(:author,:permlink,:observer)" rows = await db.query_all(sql, author=author, permlink=permlink, observer=observer) if not rows or len(rows) == 0: return {} root_id = rows[0]['id'] all_posts = {} root_post = _bridge_post_object(rows[0]) root_post['active_votes'] = await find_votes_impl( db, rows[0]['author'], rows[0]['permlink'], VotesPresentation.BridgeApi) root_post = append_statistics_to_post(root_post, rows[0], False) root_post['replies'] = [] all_posts[root_id] = root_post parent_to_children_id_map = {} for index in range(1, len(rows)): parent_id = rows[index]['parent_id'] if parent_id not in parent_to_children_id_map: parent_to_children_id_map[parent_id] = [] parent_to_children_id_map[parent_id].append(rows[index]['id']) post = _bridge_post_object(rows[index]) post['active_votes'] = await find_votes_impl( db, rows[index]['author'], rows[index]['permlink'], VotesPresentation.BridgeApi) post = append_statistics_to_post(post, rows[index], False) post['replies'] = [] all_posts[post['post_id']] = post for key in parent_to_children_id_map: children = parent_to_children_id_map[key] post = all_posts[key] for child_id in children: post['replies'].append(_ref(all_posts[child_id])) #result has to be in form of dictionary of dictionaries {post_ref: post} results = {} for key in all_posts: post_ref = _ref(all_posts[key]) results[post_ref] = all_posts[key] return results
async def get_content_replies(context, author: str, permlink: str): """Get a list of post objects based on parent.""" db = context['db'] valid_account(author) valid_permlink(permlink) parent_id = await cursor.get_post_id(db, author, permlink) if parent_id: child_ids = await cursor.get_child_ids(db, parent_id) if child_ids: return await load_posts(db, child_ids) return []
async def get_content(context, author: str, permlink: str): """Get a single post object.""" db = context['db'] valid_account(author) valid_permlink(permlink) post_id = await cursor.get_post_id(db, author, permlink) if not post_id: return {'id': 0, 'author': '', 'permlink': ''} posts = await load_posts(db, [post_id]) assert posts, 'post was not found in cache' return posts[0]
async def get_account_posts(context, sort, account, start_author='', start_permlink='', limit=20, observer=None): """Get posts for an account -- blog, feed, comments, or replies.""" valid_sorts = ['blog', 'feed', 'posts', 'comments', 'replies', 'payout'] assert sort in valid_sorts, 'invalid account sort' assert account, 'account is required' db = context['db'] account = valid_account(account) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) start = (start_author, start_permlink) limit = valid_limit(limit, 100) # pylint: disable=unused-variable observer_id = await get_account_id(db, observer) if observer else None # TODO if sort == 'blog': ids = await cursor.pids_by_blog(db, account, *start, limit) posts = await load_posts(context['db'], ids) for post in posts: if post['author'] != account: post['reblogged_by'] = [account] return posts elif sort == 'feed': res = await cursor.pids_by_feed_with_reblog(db, account, *start, limit) return await load_posts_reblogs(context['db'], res) elif sort == 'posts': start = start if start_permlink else (account, None) assert account == start[ 0], 'comments - account must match start author' ids = await cursor.pids_by_posts(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'comments': start = start if start_permlink else (account, None) assert account == start[ 0], 'comments - account must match start author' ids = await cursor.pids_by_comments(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'replies': start = start if start_permlink else (account, None) ids = await cursor.pids_by_replies(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'payout': start = start if start_permlink else (account, None) ids = await cursor.pids_by_payout(db, account, *start, limit) return await load_posts(context['db'], ids)
async def get_profile(context, account, observer=None): """Load account/profile data.""" db = context['db'] account = valid_account(account) observer = valid_account(observer, allow_empty=True) ret = await load_profiles(db, [valid_account(account)]) assert ret, 'Account \'{}\' does not exist'.format(account) # should not be needed observer_id = await get_account_id(db, observer) if observer else None if observer_id: await _follow_contexts(db, {ret[0]['id']: ret[0]}, observer_id, True) return ret[0]
async def get_discussions_by_feed(context, tag: str = None, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Retrieve account's personalized feed.""" assert tag, '`tag` cannot be blank' assert not filter_tags, 'filter_tags not supported' res = await cursor.pids_by_feed_with_reblog( context['db'], valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts_reblogs(context['db'], res, truncate_body=truncate_body)
async def get_account_posts(context, sort:str, account:str, start_author:str='', start_permlink:str='', limit:int=20, observer:str=None): """Get posts for an account -- blog, feed, comments, or replies.""" supported_sort_list = ['blog', 'feed', 'posts', 'comments', 'replies', 'payout'] assert sort in supported_sort_list, "Unsupported sort, valid sorts: {}".format(", ".join(supported_sort_list)) db = context['db'] account = valid_account(account) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) observer = valid_account(observer, allow_empty=True) limit = valid_limit(limit, 100, 20) sql = None account_posts = True # set when only posts (or reblogs) of given account are supposed to be in results if sort == 'blog': sql = "SELECT * FROM bridge_get_account_posts_by_blog( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER, True )" elif sort == 'feed': sql = "SELECT * FROM bridge_get_by_feed_with_reblog((:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER)" elif sort == 'posts': sql = "SELECT * FROM bridge_get_account_posts_by_posts( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" elif sort == 'comments': sql = "SELECT * FROM bridge_get_account_posts_by_comments( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" elif sort == 'replies': account_posts = False sql = "SELECT * FROM bridge_get_account_posts_by_replies( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT, True )" elif sort == 'payout': sql = "SELECT * FROM bridge_get_account_posts_by_payout( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" sql_result = await db.query_all(sql, account=account, author=start_author, permlink=start_permlink, limit=limit ) posts = [] for row in sql_result: post = _bridge_post_object(row) post['active_votes'] = await find_votes_impl(db, row['author'], row['permlink'], VotesPresentation.BridgeApi) if sort == 'blog': if post['author'] != account: post['reblogged_by'] = [account] elif sort == 'feed': reblogged_by = set(row['reblogged_by']) reblogged_by.discard(row['author']) # Eliminate original author of reblogged post if reblogged_by: reblogged_by_list = list(reblogged_by) reblogged_by_list.sort() post['reblogged_by'] = reblogged_by_list post = append_statistics_to_post(post, row, False if account_posts else row['is_pinned']) posts.append(post) return posts
async def get_following(context, account: str, start: str, follow_type: str = None, limit: int = None, **kwargs): """Get all accounts `account` follows. (EOL)""" # `type` reserved word workaround if not follow_type and 'type' in kwargs: follow_type = kwargs['type'] if not follow_type: follow_type = 'blog' following = await cursor.get_following( context['db'], valid_account(account), valid_account(start, allow_empty=True), valid_follow_type(follow_type), valid_limit(limit, 1000)) return [_legacy_follower(account, name, follow_type) for name in following]
async def get_discussions_by_feed(context, tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None, observer: str = None): """Retrieve account's personalized feed.""" assert not filter_tags, 'filter_tags not supported' return await get_discussions_by_feed_impl( context['db'], valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100, 20), valid_truncate(truncate_body), observer)
async def get_follow_count(context, account: str): """Get follow count stats. (EOL)""" count = await cursor.get_follow_counts(context['db'], valid_account(account)) return dict(account=account, following_count=count['following'], follower_count=count['followers'])
async def get_discussions_by_comments(context, start_author: str, start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Get comments by made by author.""" assert not filter_tags, 'filter_tags not supported' start_author = valid_account(start_author) start_permlink = valid_permlink(start_permlink, allow_empty=True) limit = valid_limit(limit, 100, 20) truncate_body = valid_truncate(truncate_body) posts = [] db = context['db'] sql = "SELECT * FROM bridge_get_account_posts_by_comments( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" result = await db.query_all(sql, account=start_author, author=start_author if start_permlink else '', permlink=start_permlink, limit=limit) for row in result: row = dict(row) post = _condenser_post_object(row, truncate_body=truncate_body) post['active_votes'] = await find_votes_impl( db, post['author'], post['permlink'], VotesPresentation.CondenserApi) posts.append(post) return posts
async def list_communities(context, last='', limit=100, query=None, sort='rank', observer=None): """List all communities, paginated. Returns lite community list.""" # pylint: disable=too-many-arguments, too-many-locals last = valid_community(last, True) limit = valid_limit(limit, 100, 100) supported_sort_list = ['rank', 'new', 'subs'] assert sort in supported_sort_list, "Unsupported sort, valid sorts: {}".format( ", ".join(supported_sort_list)) observer = valid_account(observer, True) search = query db = context['db'] sql = "SELECT * FROM bridge_list_communities_by_" + \ sort + "( (:observer)::VARCHAR, (:last)::VARCHAR, (:search)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, observer=observer, last=last, search=search, limit=limit) return remove_empty_admins_field(rows)
async def get_post(context, author, permlink, observer=None): """Fetch a single post""" # pylint: disable=unused-variable #TODO: `observer` logic for user-post state db = context['db'] valid_account(author) valid_account(observer, allow_empty=True) valid_permlink(permlink) sql = "SELECT * FROM bridge_get_post( (:author)::VARCHAR, (:permlink)::VARCHAR )" result = await db.query_all(sql, author=author, permlink=permlink) post = _bridge_post_object(result[0]) post['active_votes'] = await find_votes_impl(db, author, permlink, VotesPresentation.BridgeApi) post = append_statistics_to_post(post, result[0], False) return post
async def get_follow_list(context, observer, follow_type='blacklisted'): """ For given observer gives directly blacklisted/muted accounts or list of blacklists/mute lists followed by observer """ observer = valid_account(observer) valid_types = dict(blacklisted=1, follow_blacklist=2, muted=4, follow_muted=8) assert follow_type in valid_types, "Unsupported follow_type, valid values: {}".format(", ".join(valid_types.keys())) db = context['db'] results = [] if follow_type == 'follow_blacklist' or follow_type == 'follow_muted': blacklists_for_user = await Mutes.get_blacklists_for_observer(observer, context, follow_type == 'follow_blacklist', follow_type == 'follow_muted') for row in blacklists_for_user: metadata = safe_db_profile_metadata(row['posting_json_metadata'], row['json_metadata']) #list_data = await get_profile(context, row['list']) #metadata = list_data["metadata"]["profile"] blacklist_description = metadata["blacklist_description"] if "blacklist_description" in metadata else '' muted_list_description = metadata["muted_list_description"] if "muted_list_description" in metadata else '' results.append({'name': row['list'], 'blacklist_description': blacklist_description, 'muted_list_description': muted_list_description}) else: # blacklisted or muted blacklisted_for_user = await Mutes.get_blacklisted_for_observer(observer, context, valid_types[follow_type]) for account in blacklisted_for_user.keys(): results.append({'name': account, 'blacklist_description': '', 'muted_list_description': ''}) return results
async def list_all_subscriptions(context, account): """Lists all communities `account` subscribes to, plus role and title in each.""" db = context['db'] account = valid_account(account) sql = "SELECT * FROM bridge_list_all_subscriptions( (:account)::VARCHAR )" rows = await db.query_all(sql, account=account) return [(r[0], r[1], r[2], r[3]) for r in rows]
async def list_subscribers(context, community, last='', limit=100): """Lists subscribers of `community`.""" community = valid_community(community) last = valid_account(last, True) limit = valid_limit(limit, 100, 100) db = context['db'] sql = "SELECT * FROM bridge_list_subscribers( (:community)::VARCHAR, (:last)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, community=community, last=last, limit=limit) return [(r[0], r[1], r[2], json_date(r[3])) for r in rows]
async def get_follow_count(context, account: str): """Get follow count stats. (EOL)""" db = context['db'] account = valid_account(account) sql = "SELECT * FROM condenser_get_follow_count( (:account)::VARCHAR )" counters = await db.query_row(sql, account=account) return dict(account=account, following_count=counters[0], follower_count=counters[1])
async def get_replies_by_last_update(context, start_author: str = None, start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Get all replies made to any of author's posts.""" assert start_author, '`start_author` cannot be blank' ids = await cursor.pids_by_replies_to_account( context['db'], valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts(context['db'], ids, truncate_body=truncate_body)
async def get_community_context(context, name, account): """For a community/account: returns role, title, subscribed state""" db = context['db'] name = valid_community(name) account = valid_account(account) sql = "SELECT * FROM bridge_get_community_context( (:account)::VARCHAR, (:name)::VARCHAR )" row = await db.query_row(sql, account=account, name=name) return dict(row['bridge_get_community_context'])
async def get_discussion(context, author, permlink): """Modified `get_state` thread implementation.""" db = context['db'] author = valid_account(author) permlink = valid_permlink(permlink) root_id = await _get_post_id(db, author, permlink) if not root_id: return {} return await _load_discussion(db, root_id)
async def list_community_roles(context, community, last='', limit=50): """List community account-roles (anyone with non-guest status).""" db = context['db'] community = valid_community(community) last = valid_account(last, True) limit = valid_limit(limit, 1000, 50) sql = "SELECT * FROM bridge_list_community_roles( (:community)::VARCHAR, (:last)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, community=community, last=last, limit=limit) return [(r['name'], r['role'], r['title']) for r in rows]
async def get_post(context, author, permlink, observer=None): """Fetch a single post""" # pylint: disable=unused-variable #TODO: `observer` logic db = context['db'] observer_id = await get_account_id(db, observer) if observer else None pid = await _get_post_id(db, valid_account(author), valid_permlink(permlink)) posts = await load_posts(db, [pid]) assert len(posts) == 1, 'cache post not found' return posts[0]
async def get_profile(context, account, observer=None): """Load account/profile data.""" db = context['db'] ret = await load_profiles(db, [valid_account(account)]) if not ret: return None observer_id = await get_account_id(db, observer) if observer else None if observer_id: await _follow_contexts(db, {ret[0]['id']: ret[0]}, observer_id, True) return ret[0]
async def account_notifications(context, account, min_score=25, last_id=None, limit=100): """Load notifications for named account.""" db = context['db'] valid_account(account) min_score = valid_score(min_score, 100, 25) last_id = valid_number(last_id, 0, "last_id") limit = valid_limit(limit, 100, 100) sql_query = "SELECT * FROM account_notifications( (:account)::VARCHAR, (:min_score)::SMALLINT, (:last_id)::BIGINT, (:limit)::SMALLINT )" rows = await db.query_all(sql_query, account=account, min_score=min_score, last_id=last_id, limit=limit) return [_render(row) for row in rows]
async def _get_content_replies_impl(db, fat_node_style, author: str, permlink: str): """Get a list of post objects based on parent.""" valid_account(author) valid_permlink(permlink) sql = "SELECT * FROM condenser_get_content_replies(:author, :permlink)" result = await db.query_all(sql, author=author, permlink=permlink) posts = [] for row in result: row = dict(row) post = _condenser_post_object(row, get_content_additions=fat_node_style) post['active_votes'] = await find_votes_impl( db, row['author'], row['permlink'], VotesPresentation.ActiveVotes if fat_node_style else VotesPresentation.CondenserApi) posts.append(post) return posts