diff --git a/hive/db/schema.py b/hive/db/schema.py index 58c8001771adc39c0b53a4ec8e293528f281d381..fa8e25bd1ad3c57cb4e8d57bdc5bdc9c051842ca 100644 --- a/hive/db/schema.py +++ b/hive/db/schema.py @@ -1600,7 +1600,9 @@ def setup(db): "bridge_get_account_posts_by_replies.sql", "bridge_get_relationship_between_accounts.sql", "bridge_get_post.sql", + "condenser_api_post_type.sql", "condenser_api_post_ex_type.sql", + "condenser_get_blog.sql", "condenser_get_content.sql", "condenser_get_discussions_by_created.sql", "condenser_get_discussions_by_blog.sql", diff --git a/hive/db/sql_scripts/condenser_api_post_type.sql b/hive/db/sql_scripts/condenser_api_post_type.sql new file mode 100644 index 0000000000000000000000000000000000000000..ece5017675c5ab1b3f3f5c667512527592fc6406 --- /dev/null +++ b/hive/db/sql_scripts/condenser_api_post_type.sql @@ -0,0 +1,32 @@ +DROP TYPE IF EXISTS condenser_api_post CASCADE; +-- type for regular condenser_api posts +CREATE TYPE condenser_api_post AS ( + id INT, + entry_id INT, -- used for paging with offset (otherwise can be any value) + author VARCHAR(16), + permlink VARCHAR(255), + author_rep BIGINT, + title VARCHAR(512), + body TEXT, + category VARCHAR(255), + depth SMALLINT, + promoted DECIMAL(10,3), + payout DECIMAL(10,3), + pending_payout DECIMAL(10,3), + payout_at TIMESTAMP, + is_paidout BOOLEAN, + children INT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + reblogged_at TIMESTAMP, -- used when post data is combined with hive_feed_cache (otherwise can be date) + rshares NUMERIC, + json TEXT, + parent_author VARCHAR(16), + parent_permlink_or_category VARCHAR(255), + curator_payout_value VARCHAR(30), + max_accepted_payout VARCHAR(30), + percent_hbd INT, + beneficiaries JSON, + url TEXT, + root_title VARCHAR(512) +); diff --git a/hive/db/sql_scripts/condenser_get_blog.sql b/hive/db/sql_scripts/condenser_get_blog.sql new file mode 100644 index 0000000000000000000000000000000000000000..f59dfe6c90ecef8c7187a0294610717c00ca90a2 --- /dev/null +++ b/hive/db/sql_scripts/condenser_get_blog.sql @@ -0,0 +1,129 @@ +DROP FUNCTION IF EXISTS condenser_get_blog_helper CASCADE; +CREATE FUNCTION condenser_get_blog_helper( in _blogger VARCHAR, in _last INT, in _limit INT, + out _account_id INT, out _offset INT, out _new_limit INT ) +AS +$function$ +BEGIN + _account_id = find_account_id( _blogger, True ); + IF _last < 0 THEN -- caller wants "most recent" page + SELECT INTO _last ( SELECT COUNT(1) - 1 FROM hive_feed_cache hfc WHERE hfc.account_id = _account_id ); + _offset = _last - _limit + 1; + IF _offset < 0 THEN + _offset = 0; + END IF; + _new_limit = _limit; + ELSIF _last + 1 < _limit THEN -- bad call, but recoverable + _offset = 0; + _new_limit = _last + 1; + ELSE -- normal call + _offset = _last - _limit + 1; + _new_limit = _limit; + END IF; +END +$function$ +language plpgsql STABLE; + +DROP FUNCTION IF EXISTS condenser_get_blog; +-- blog posts [ _last - _limit + 1, _last ] oldest first (reverted by caller) +CREATE FUNCTION condenser_get_blog( in _blogger VARCHAR, in _last INT, in _limit INT ) +RETURNS SETOF condenser_api_post +AS +$function$ +DECLARE + __account_id INT; + __offset INT; +BEGIN + SELECT h.* INTO __account_id, __offset, _limit FROM condenser_get_blog_helper( _blogger, _last, _limit ) h; + RETURN QUERY SELECT + hp.id, + blog.entry_id::INT, + hp.author, + hp.permlink, + hp.author_rep, + hp.title, + hp.body, + hp.category, + hp.depth, + hp.promoted, + hp.payout, + hp.pending_payout, + hp.payout_at, + hp.is_paidout, + hp.children, + hp.created_at, + hp.updated_at, + ( + CASE hp.author_id = __account_id + WHEN True THEN '1970-01-01T00:00:00'::timestamp + ELSE blog.created_at + END + ) as reblogged_at, + hp.rshares, + hp.json, + hp.parent_author, + hp.parent_permlink_or_category, + hp.curator_payout_value, + hp.max_accepted_payout, + hp.percent_hbd, + hp.beneficiaries, + hp.url, + hp.root_title + FROM + ( + SELECT + hfc.created_at, hfc.post_id, row_number() over (ORDER BY hfc.created_at ASC, hfc.post_id ASC) - 1 as entry_id + FROM + hive_feed_cache hfc + WHERE + hfc.account_id = __account_id + ORDER BY hfc.created_at ASC, hfc.post_id ASC + LIMIT _limit + OFFSET __offset + ) as blog + JOIN hive_posts_view hp ON hp.id = blog.post_id + ORDER BY blog.created_at ASC, blog.post_id ASC; +END +$function$ +language plpgsql STABLE; + +DROP FUNCTION IF EXISTS condenser_get_blog_entries; +-- blog entries [ _last - _limit + 1, _last ] oldest first (reverted by caller) +CREATE FUNCTION condenser_get_blog_entries( in _blogger VARCHAR, in _last INT, in _limit INT ) +RETURNS TABLE( entry_id INT, author hive_accounts.name%TYPE, permlink hive_permlink_data.permlink%TYPE, reblogged_at TIMESTAMP ) +AS +$function$ +DECLARE + __account_id INT; + __offset INT; +BEGIN + SELECT h.* INTO __account_id, __offset, _limit FROM condenser_get_blog_helper( _blogger, _last, _limit ) h; + RETURN QUERY SELECT + blog.entry_id::INT, + ha.name as author, + hpd.permlink, + ( + CASE hp.author_id = __account_id + WHEN True THEN '1970-01-01T00:00:00'::timestamp + ELSE blog.created_at + END + ) as reblogged_at + FROM + ( + SELECT + hfc.created_at, hfc.post_id, row_number() over (ORDER BY hfc.created_at ASC, hfc.post_id ASC) - 1 as entry_id + FROM + hive_feed_cache hfc + WHERE + hfc.account_id = __account_id + ORDER BY hfc.created_at ASC, hfc.post_id ASC + LIMIT _limit + OFFSET __offset + ) as blog + JOIN hive_posts hp ON hp.id = blog.post_id + JOIN hive_accounts ha ON ha.id = hp.author_id + JOIN hive_permlink_data hpd ON hpd.id = hp.permlink_id + ORDER BY blog.created_at ASC, blog.post_id ASC; +END +$function$ +language plpgsql STABLE; + diff --git a/hive/server/condenser_api/cursor.py b/hive/server/condenser_api/cursor.py index 03f6d673012f62df94bbbd71e234a006554359a8..66f8a279b792d8f0ac1751e037ad0f2b173dc5db 100644 --- a/hive/server/condenser_api/cursor.py +++ b/hive/server/condenser_api/cursor.py @@ -145,42 +145,6 @@ async def pids_by_blog(db, account: str, start_author: str = '', return await db.query_col(sql, account_id=account_id, start_id=start_id, limit=limit) -async def pids_by_blog_by_index(db, account: str, start_index: int, limit: int = 20): - """Get post_ids for an author's blog (w/ reblogs), paged by index/limit. - - Examples: - (acct, 2) = returns blog entries 0 up to 2 (3 oldest) - (acct, 0) = returns all blog entries (limit 0 means return all?) - (acct, 2, 1) = returns 1 post starting at idx 2 - (acct, 2, 3) = returns 3 posts: idxs (2,1,0) - """ - - if start_index in (-1, 0): - sql = """SELECT COUNT(*) - 1 FROM hive_posts_view hp - WHERE hp.author = :account""" - start_index = await db.query_one(sql, account=account) - if start_index < 0: - return (0, []) - if limit > start_index + 1: - limit = start_index + 1 - - offset = start_index - limit + 1 - assert offset >= 0, ('start_index and limit combination is invalid (%d, %d)' - % (start_index, limit)) - - sql = """ - SELECT hp.id - FROM hive_posts_view hp - WHERE hp.author = :account - ORDER BY hp.created_at - LIMIT :limit - OFFSET :offset - """ - - ids = await db.query_col(sql, account=account, limit=limit, offset=offset) - return (start_index, list(reversed(ids))) - - async def pids_by_blog_without_reblog(db, account: str, start_permlink: str = '', limit: int = 20): """Get a list of post_ids for an author's blog without reblogs.""" diff --git a/hive/server/condenser_api/methods.py b/hive/server/condenser_api/methods.py index f2e717d86ff4859dbfa6c24d3d8a7cc8cc3e8a80..8a5db2981d0760dee204efefbedd64b0ad705b93 100644 --- a/hive/server/condenser_api/methods.py +++ b/hive/server/condenser_api/methods.py @@ -8,6 +8,7 @@ from hive.server.condenser_api.objects import _mute_votes, _condenser_post_objec from hive.server.common.helpers import ( ApiError, return_error_info, + json_date, valid_account, valid_permlink, valid_tag, @@ -176,7 +177,7 @@ async def _get_content_impl(db, fat_node_style, author: str, permlink: str, obse if not observer: post['active_votes'] = _mute_votes(post['active_votes'], Mutes.all()) else: - blacklists_for_user = await Mutes.get_blacklists_for_observer(observer, context) + blacklists_for_user = await Mutes.get_blacklists_for_observer(observer, {'db':db}) post['active_votes'] = _mute_votes(post['active_votes'], blacklists_for_user.keys()) return post @@ -452,8 +453,43 @@ async def get_blog(context, account: str, start_entry_id: int = 0, limit: int = """Get posts for an author's blog (w/ reblogs), paged by index/limit. Equivalent to get_discussions_by_blog, but uses offset-based pagination. + + Examples: (ABW: old description and examples were misleading as in many cases code worked differently, also now more cases actually work that gave error earlier) + (acct, -1, limit) for limit 1..500 - returns latest (no more than) limit posts + (acct, 0) - returns latest single post (ABW: this is a bug but I left it here because I'm afraid it was actively used - it should return oldest post) + (acct, 0, limit) for limit 1..500 - same as (acct, -1, limit) - see above + (acct, last_idx) for positive last_idx - returns last_idx oldest posts, or posts in range [last_idx..last_idx-500) when last_idx >= 500 + (acct, last_idx, limit) for positive last_idx and limit 1..500 - returns posts in range [last_idx..last_idx-limit) """ - return await _get_blog(context['db'], account, start_entry_id, limit) + db = context['db'] + + account = valid_account(account) + if not start_entry_id: + start_entry_id = -1 + start_entry_id = valid_offset(start_entry_id) + if not limit: + limit = max(start_entry_id + 1, 1) + limit = min(limit, 500) + limit = valid_limit(limit, 500, None) + + sql = "SELECT * FROM condenser_get_blog(:account, :last, :limit)" + result = await db.query_all(sql, account=account, last=start_entry_id, limit=limit) + + muted_accounts = Mutes.all() + out = [] + for row in result: + row = dict(row) + post = _condenser_post_object(row) + + post['active_votes'] = await find_votes_impl(db, row['author'], row['permlink'], VotesPresentation.CondenserApi) + post['active_votes'] = _mute_votes(post['active_votes'], muted_accounts) + + out.append({"blog": account, + "entry_id": row['entry_id'], + "comment": post, + "reblogged_on": json_date(row['reblogged_at'])}) + + return list(reversed(out)) @return_error_info @nested_query_compat @@ -462,53 +498,30 @@ async def get_blog_entries(context, account: str, start_entry_id: int = 0, limit Interface identical to get_blog, but returns minimalistic post references. """ + db = context['db'] - entries = await _get_blog(context['db'], account, start_entry_id, limit) - for entry in entries: - # replace the comment body with just author/permlink - post = entry.pop('comment') - entry['author'] = post['author'] - entry['permlink'] = post['permlink'] - - return entries - -async def _get_blog(db, account: str, start_index: int, limit: int = None): - """Get posts for an author's blog (w/ reblogs), paged by index/limit. - - Examples: - (acct, 2) = returns blog entries 0 up to 2 (3 oldest) - (acct, 0) = returns all blog entries (limit 0 means return all?) - (acct, 2, 1) = returns 1 post starting at idx 2 - (acct, 2, 3) = returns 3 posts: idxs (2,1,0) - (acct, -1, 10) = returns latest 10 posts - """ - - if start_index is None: - start_index = 0 - + account = valid_account(account) + if not start_entry_id: + start_entry_id = -1 + start_entry_id = valid_offset(start_entry_id) if not limit: - limit = start_index + 1 + limit = max(start_entry_id + 1, 1) + limit = min(limit, 500) + limit = valid_limit(limit, 500, None) - start_index, ids = await cursor.pids_by_blog_by_index( - db, - valid_account(account), - valid_offset(start_index), - valid_limit(limit, 500, None)) + sql = "SELECT * FROM condenser_get_blog_entries(:account, :last, :limit)" + result = await db.query_all(sql, account=account, last=start_entry_id, limit=limit) out = [] - - idx = int(start_index) - for post in await load_posts(db, ids): - reblog = post['author'] != account - reblog_on = post['created'] if reblog else "1970-01-01T00:00:00" - + for row in result: + row = dict(row) out.append({"blog": account, - "entry_id": idx, - "comment": post, - "reblogged_on": reblog_on}) - idx -= 1 + "entry_id": row['entry_id'], + "author": row['author'], + "permlink": row['permlink'], + "reblogged_on": json_date(row['reblogged_at'])}) - return out + return list(reversed(out)) @return_error_info async def get_active_votes(context, author: str, permlink: str): diff --git a/hive/server/condenser_api/objects.py b/hive/server/condenser_api/objects.py index 8b2539f7d7837816510e4c9f96e98cb174d2f79b..d2e4483ca345dae33f6c59a0b94144e0b8974c2a 100644 --- a/hive/server/condenser_api/objects.py +++ b/hive/server/condenser_api/objects.py @@ -87,6 +87,7 @@ async def load_posts_keyed(db, ids, truncate_body=0): post = _condenser_post_object(row, truncate_body=truncate_body) post['active_votes'] = await find_votes_impl(db, row['author'], row['permlink'], VotesPresentation.CondenserApi) + post['active_votes'] = _mute_votes(post['active_votes'], muted_accounts) posts_by_id[row['id']] = post return posts_by_id diff --git a/tests/tests_api b/tests/tests_api index 3d3daf0c67b9d429be51b2d66543a57c0f8fcf29..3f308b5d98b924cde5e33c65ae64ba96cb3c786d 160000 --- a/tests/tests_api +++ b/tests/tests_api @@ -1 +1 @@ -Subproject commit 3d3daf0c67b9d429be51b2d66543a57c0f8fcf29 +Subproject commit 3f308b5d98b924cde5e33c65ae64ba96cb3c786d