diff --git a/hive/db/sql_scripts/hive_post_operations.sql b/hive/db/sql_scripts/hive_post_operations.sql index ecbee293bb10d0390c3cf6295f6f2dacc0db6a48..80eebba514f3588ca2686c8313cdda6499220ab9 100644 --- a/hive/db/sql_scripts/hive_post_operations.sql +++ b/hive/db/sql_scripts/hive_post_operations.sql @@ -25,6 +25,54 @@ BEGIN END $function$; + +DROP FUNCTION IF EXISTS process_community_post; +CREATE OR REPLACE FUNCTION process_community_post(_block_num hive_posts.block_num%TYPE, _community_support_start_block hive_posts.block_num%TYPE, _community_id hive_posts.community_id%TYPE, _community_name hive_permlink_data.permlink%TYPE, _author_id hive_posts.author_id%TYPE, is_comment bool) +RETURNS TABLE(is_muted bool, community_id hive_posts.community_id%TYPE) +LANGUAGE plpgsql +as + $$ +declare + __community_type_id SMALLINT; + __role_id SMALLINT; + __member_role CONSTANT SMALLINT := 2; + __community_type_topic CONSTANT SMALLINT := 1; + __community_type_journal CONSTANT SMALLINT := 2; + __community_type_council CONSTANT SMALLINT := 3; + __is_muted bool := FALSE; + __community_id hivemind_app.hive_posts.community_id%TYPE; +BEGIN + IF _block_num < _community_support_start_block THEN + __is_muted := FALSE; + __community_id := NULL; + ELSE + IF _community_id IS NOT NULL THEN + SELECT type_id INTO __community_type_id FROM hivemind_app.hive_communities WHERE id = _community_id; + ELSE + SELECT type_id, id INTO __community_type_id, _community_id from hivemind_app.hive_communities where name = _community_name; + END IF; + + IF __community_type_id = __community_type_topic THEN + __is_muted := TRUE; + ELSE + IF __community_type_id = __community_type_journal AND is_comment = TRUE THEN + __is_muted := TRUE; + ELSE + select role_id into __role_id from hivemind_app.hive_roles where hivemind_app.hive_roles.community_id = _community_id AND account_id = _author_id; + IF __community_type_id = __community_type_journal AND is_comment = FALSE AND __role_id IS NOT NULL AND __role_id >= __member_role THEN + __is_muted := TRUE; + ELSIF __community_type_id = __community_type_council AND __role_id IS NOT NULL AND __role_id >= __member_role THEN + __is_muted := TRUE; + END IF; + END IF; + END IF; + END IF; + + RETURN QUERY SELECT __is_muted, __community_id; + END; +$$; + + DROP FUNCTION IF EXISTS hivemind_app.process_hive_post_operation; ; CREATE OR REPLACE FUNCTION hivemind_app.process_hive_post_operation( @@ -58,17 +106,14 @@ if _parent_author != '' THEN root_id, is_muted, is_valid, author_id, permlink_id, created_at, updated_at, sc_hot, sc_trend, active, payout_at, cashout_time, counter_deleted, block_num, block_num_created) SELECT php.id AS parent_id, php.depth + 1 AS depth, - (CASE - WHEN _block_num > _community_support_start_block THEN - COALESCE(php.community_id, (select hc.id from hivemind_app.hive_communities hc where hc.name = _parent_permlink)) - ELSE NULL - END) AS community_id, + pcp.community_id AS community_id, COALESCE(php.category_id, (select hcg.id from hivemind_app.hive_category_data hcg where hcg.category = _parent_permlink)) AS category_id, (CASE(php.root_id) WHEN 0 THEN php.id ELSE php.root_id END) AS root_id, - php.is_muted AS is_muted, php.is_valid AS is_valid, + pcp.is_muted AS is_muted, + php.is_valid AS is_valid, ha.id AS author_id, hpd.id AS permlink_id, _date AS created_at, _date AS updated_at, hivemind_app.calculate_time_part_of_hot(_date) AS sc_hot, @@ -77,6 +122,7 @@ if _parent_author != '' THEN _block_num as block_num, _block_num as block_num_created FROM hivemind_app.hive_accounts ha, hivemind_app.hive_permlink_data hpd, + hivemind_app.process_community_post(_block_num, _community_support_start_block, NULL, _parent_permlink, ha.id, false) pcp, hivemind_app.hive_posts php INNER JOIN hivemind_app.hive_accounts pha ON pha.id = php.author_id INNER JOIN hivemind_app.hive_permlink_data phpd ON phpd.id = php.permlink_id @@ -106,14 +152,10 @@ ELSE active, payout_at, cashout_time, counter_deleted, block_num, block_num_created, tags_ids) SELECT 0 AS parent_id, 0 AS depth, - (CASE - WHEN _block_num > _community_support_start_block THEN - (select hc.id FROM hivemind_app.hive_communities hc WHERE hc.name = _parent_permlink) - ELSE NULL - END) AS community_id, + pcp.community_id AS community_id, (SELECT hcg.id FROM hivemind_app.hive_category_data hcg WHERE hcg.category = _parent_permlink) AS category_id, 0 as root_id, -- will use id as root one if no parent - false AS is_muted, true AS is_valid, + pcp.is_muted AS is_muted, true AS is_valid, ha.id AS author_id, hpd.id AS permlink_id, _date AS created_at, _date AS updated_at, hivemind_app.calculate_time_part_of_hot(_date) AS sc_hot, @@ -125,6 +167,7 @@ ELSE FROM hivemind_app.prepare_tags( ARRAY_APPEND(_metadata_tags, _parent_permlink ) ) ) as tags_ids FROM hivemind_app.hive_accounts ha, + hivemind_app.process_community_post(_block_num, _community_support_start_block, NULL, _parent_permlink, author_id, false) pcp, hivemind_app.hive_permlink_data hpd WHERE ha.name = _author and hpd.permlink = _permlink diff --git a/hive/indexer/community.py b/hive/indexer/community.py index a3559e69fabf1f9b5376c2b15fe04b9d886c9d44..f56988064f201ea8476c20729400e12f275ce5e8 100644 --- a/hive/indexer/community.py +++ b/hive/indexer/community.py @@ -33,6 +33,7 @@ class Role(IntEnum): TYPE_TOPIC = 1 TYPE_JOURNAL = 2 TYPE_COUNCIL = 3 +valid_types = [TYPE_TOPIC, TYPE_JOURNAL, TYPE_COUNCIL] # https://en.wikipedia.org/wiki/ISO_639-1 LANGS = ( @@ -103,6 +104,12 @@ def read_key_dict(obj, key): assert isinstance(obj[key], dict), f'key `{key}` not a dict' return obj[key] +def read_key_integer(op, key): + """Reads a key from dict, ensuring valid bool if present.""" + if key in op: + assert isinstance(op[key], int), 'must be int: %s' % key + return op[key] + return None class Community: """Handles hive community registration and operations.""" @@ -123,8 +130,7 @@ class Community: This method checks for any valid community names and inserts them. """ - # if not re.match(r'^hive-[123]\d{4,6}$', name): - if not re.match(r'^hive-[1]\d{4,6}$', name): + if not re.match(r'^hive-[123]\d{4,6}$', name): return type_id = int(name[5]) _id = Accounts.get_id(name) @@ -372,24 +378,22 @@ class CommunityOp: # Account-level actions elif action == 'setRole': - DB.query( - f"""INSERT INTO {SCHEMA_NAME}.hive_roles - (account_id, community_id, role_id, created_at) - VALUES (:account_id, :community_id, :role_id, :date) - ON CONFLICT (account_id, community_id) - DO UPDATE SET role_id = :role_id """, + subscribed = DB.query_one( + f"""SELECT * FROM {SCHEMA_NAME}.set_community_role_or_title(:community_id, :account_id, :role_id, NULL::varchar, CAST(:date AS timestamp ))""", **params, ) + if not subscribed: + log.info("set role failed account '%s' must be subscribed to the community", params['account']) + return self._notify('set_role', payload=Role(self.role_id).name) elif action == 'setUserTitle': - DB.query( - f"""INSERT INTO {SCHEMA_NAME}.hive_roles - (account_id, community_id, title, created_at) - VALUES (:account_id, :community_id, :title, :date) - ON CONFLICT (account_id, community_id) - DO UPDATE SET title = :title""", + subscribed = DB.query_one( + f"""SELECT * FROM {SCHEMA_NAME}.set_community_role_or_title(:community_id, :account_id, NULL::integer , :title, CAST(:date AS timestamp ))""", **params, ) + if not subscribed: + log.info("set role failed account '%s' must be subscribed to the community", params['account']) + return self._notify('set_label', payload=self.title) # Post-level actions @@ -535,7 +539,7 @@ class CommunityOp: def _read_props(self): # TODO: assert props changed? props = read_key_dict(self.op, 'props') - valid = ['title', 'about', 'lang', 'is_nsfw', 'description', 'flag_text', 'settings'] + valid = ['title', 'about', 'lang', 'is_nsfw', 'description', 'flag_text', 'settings', 'type_id'] assert_keys_match(props.keys(), valid, allow_missing=True) out = {} @@ -560,6 +564,10 @@ class CommunityOp: avatar_url = settings['avatar_url'] assert not avatar_url or _valid_url_proto(avatar_url) out['avatar_url'] = avatar_url + if 'type_id' in props: + community_type = read_key_integer(props, 'type_id') + assert community_type in valid_types, 'invalid community type' + out['type_id'] = community_type assert out, 'props were blank' self.props = out diff --git a/hive/indexer/posts.py b/hive/indexer/posts.py index adfe7aa774925f2a161d55007f015a750725dd49..dd913c65569c17daa89a087f8ba3cd0618f8b6bf 100644 --- a/hive/indexer/posts.py +++ b/hive/indexer/posts.py @@ -89,8 +89,7 @@ class Posts(DbAdapterHolder): return result = dict(row) - # TODO we need to enhance checking related community post validation and honor is_muted. - error = cls._verify_post_against_community(op, result['community_id'], result['is_valid'], result['is_muted']) + error = cls._verify_post_against_community(op, result['community_id'], result['is_valid']) img_url = None if 'image' in md: @@ -392,12 +391,11 @@ class Posts(DbAdapterHolder): Votes.drop_votes_of_deleted_comment(op) @classmethod - def _verify_post_against_community(cls, op, community_id, is_valid, is_muted): + def _verify_post_against_community(cls, op, community_id, is_valid): error = None if community_id and is_valid and not Community.is_post_valid(community_id, op): error = 'not authorized' - # is_valid = False # TODO: reserved for future blacklist status? - is_muted = True + #is_valid = False # TODO: reserved for future blacklist status? return error @classmethod diff --git a/mock_data/block_data/community_op/mock_block_data_community.json b/mock_data/block_data/community_op/mock_block_data_community.json index e671852f6f9ef1f999e3c48c114f069db8575ce6..8fde8fcf44471ec562eca43caf6ec5d799b11cdd 100644 --- a/mock_data/block_data/community_op/mock_block_data_community.json +++ b/mock_data/block_data/community_op/mock_block_data_community.json @@ -970,6 +970,17 @@ "json": "[\"subscribe\",{\"community\":\"hive-167892\"}]" } }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "test-safari" + ], + "id": "community", + "json": "[\"subscribe\",{\"community\":\"hive-167892\"}]" + } + }, { "type": "custom_json_operation", "value": { @@ -4745,6 +4756,126 @@ "json_metadata": "", "extensions": [] } + }, + { + "type": "account_create_operation", + "value": { + "creator": "howo", + "new_account_name": "hive-111119", + "owner": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "active": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "posting": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "memo_key": "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + "json_metadata": "", + "extensions": [] + } + }, + { + "type": "account_create_operation", + "value": { + "creator": "howo", + "new_account_name": "hive-211119", + "owner": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "active": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "posting": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "memo_key": "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + "json_metadata": "", + "extensions": [] + } + }, + { + "type": "account_create_operation", + "value": { + "creator": "howo", + "new_account_name": "hive-311119", + "owner": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "active": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "posting": { + "weight_threshold": 1, + "account_auths": [], + "key_auths": [ + [ + "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + 1 + ] + ] + }, + "memo_key": "STM8JH4fTJr73FQimysjmXCEh2UvRwZsG6ftjxsVTmYCeEehZgh25", + "json_metadata": "", + "extensions": [] + } } ] } @@ -4866,6 +4997,120 @@ "id": "community", "json": "[\"setRole\",{\"community\":\"hive-199999\",\"account\":\"isoldmember\",\"role\":\"member\"}]" } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "ismember" + ], + "id": "community", + "json": "[\"subscribe\",{\"community\":\"hive-111119\"}]" + } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "ismember" + ], + "id": "community", + "json": "[\"subscribe\",{\"community\":\"hive-211119\"}]" + } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "ismember" + ], + "id": "community", + "json": "[\"subscribe\",{\"community\":\"hive-311119\"}]" + } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "hive-111119" + ], + "id": "community", + "json": "[\"setRole\",{\"community\":\"hive-111119\",\"account\":\"ismember\",\"role\":\"member\"}]" + } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "hive-211119" + ], + "id": "community", + "json": "[\"setRole\",{\"community\":\"hive-211119\",\"account\":\"ismember\",\"role\":\"member\"}]" + } + }, + { + "type": "custom_json_operation", + "value": { + "required_auths": [], + "required_posting_auths": [ + "hive-311119" + ], + "id": "community", + "json": "[\"setRole\",{\"community\":\"hive-311119\",\"account\":\"ismember\",\"role\":\"member\"}]" + } + }, + { + "type": "comment_operation", + "value": { + "parent_author": "", + "parent_permlink": "hive-111119", + "author": "ismember", + "permlink": "ismember-hive-111119", + "title": "ismember This post will not be muted", + "body": "lorem ipsum", + "json_metadata": "{}" + } + }, + { + "type": "comment_operation", + "value": { + "parent_author": "", + "parent_permlink": "hive-111119", + "author": "notmember", + "permlink": "notmember-hive-111119", + "title": "notmember This post will not be muted", + "body": "lorem ipsum", + "json_metadata": "{}" + } + }, + { + "type": "comment_operation", + "value": { + "parent_author": "ismember", + "parent_permlink": "ismember-hive-111119", + "author": "ismember", + "permlink": "re-ismember-hive-111119", + "title": "ismember This comment will not be muted", + "body": "lorem ipsum", + "json_metadata": "{}" + } + }, + { + "type": "comment_operation", + "value": { + "parent_author": "ismember", + "parent_permlink": "ismember-hive-111119", + "author": "notmember", + "permlink": "re-2-ismember-hive-111119", + "title": "notmember This comment will not be muted", + "body": "lorem ipsum", + "json_metadata": "{}" + } } ] } diff --git a/scripts/ci/start-api-benchmarks.sh b/scripts/ci/start-api-benchmarks.sh index 49e4faa53ab939e7edc15f03af9293aa9c77c1a0..9742b2796bd4acfe8b6cdff0426058fae5d0e665 100755 --- a/scripts/ci/start-api-benchmarks.sh +++ b/scripts/ci/start-api-benchmarks.sh @@ -15,7 +15,7 @@ echo "HIVEMIND_PORT: ${HIVEMIND_PORT}" echo "ITERATIONS: ${ITERATIONS}" echo "JOBS: ${JOBS}" -# since it working inside docker it shoud be fine to hardcode it to tmp +# since it working inside docker it should be fine to hardcode it to tmp export HIVEMIND_BENCHMARKS_IDS_FILE=/tmp/test_ids.csv export TAVERN_DISABLE_COMPARATOR=true diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.pat.json b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.pat.json new file mode 100644 index 0000000000000000000000000000000000000000..09930b67193de4f1e998b3736939bbf1552e7d4e --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.pat.json @@ -0,0 +1,26 @@ +{ + "about": "", + "avatar_url": "", + "context": {}, + "created_at": "2016-09-15 19:47:51", + "description": "", + "flag_text": "", + "id": 92530, + "is_nsfw": false, + "lang": "en", + "name": "hive-111119", + "num_authors": 0, + "num_pending": 0, + "settings": {}, + "subscribers": 0, + "sum_pending": 0, + "team": [ + [ + "hive-111119", + "owner", + "" + ] + ], + "title": "@hive-111119", + "type_id": 1 +} diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.tavern.yaml b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.tavern.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cfdabae2064b2a55d2e32d2da661dbcbca3bd637 --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-111119.tavern.yaml @@ -0,0 +1,28 @@ +--- + test_name: Hivemind + + marks: + - patterntest + # Communities not implemented under 5 mln blocks, but some were created by mock mechanism, therefore they are in result. + + includes: + - !include ../../common.yaml + + stages: + - name: test + request: + url: "{service.proto:s}://{service.server:s}:{service.port}/" + method: POST + headers: + content-type: application/json + json: + jsonrpc: "2.0" + id: 1 + method: "bridge.get_community" + params: {"name":"hive-111119"} + response: + status_code: 200 + verify_response_with: + function: validate_response:compare_response_with_pattern + extra_kwargs: + ignore_tags: "<bridge community>" diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.pat.json b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.pat.json new file mode 100644 index 0000000000000000000000000000000000000000..cf5ea15ca52eae952054dc0882f69dec9e122f2d --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.pat.json @@ -0,0 +1,26 @@ +{ + "about": "", + "avatar_url": "", + "context": {}, + "created_at": "2016-09-15 19:47:51", + "description": "", + "flag_text": "", + "id": 92531, + "is_nsfw": false, + "lang": "en", + "name": "hive-211119", + "num_authors": 0, + "num_pending": 0, + "settings": {}, + "subscribers": 0, + "sum_pending": 0, + "team": [ + [ + "hive-211119", + "owner", + "" + ] + ], + "title": "@hive-211119", + "type_id": 2 +} diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.tavern.yaml b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.tavern.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8bcd01236b7bcc59ff5b1949fcf34348ee2b966e --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-211119.tavern.yaml @@ -0,0 +1,28 @@ +--- + test_name: Hivemind + + marks: + - patterntest + # Communities not implemented under 5 mln blocks, but some were created by mock mechanism, therefore they are in result. + + includes: + - !include ../../common.yaml + + stages: + - name: test + request: + url: "{service.proto:s}://{service.server:s}:{service.port}/" + method: POST + headers: + content-type: application/json + json: + jsonrpc: "2.0" + id: 1 + method: "bridge.get_community" + params: {"name":"hive-211119"} + response: + status_code: 200 + verify_response_with: + function: validate_response:compare_response_with_pattern + extra_kwargs: + ignore_tags: "<bridge community>" diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.pat.json b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.pat.json new file mode 100644 index 0000000000000000000000000000000000000000..b749778a7287a2f211fef4d58a5e4198b5744843 --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.pat.json @@ -0,0 +1,26 @@ +{ + "about": "", + "avatar_url": "", + "context": {}, + "created_at": "2016-09-15 19:47:51", + "description": "", + "flag_text": "", + "id": 92532, + "is_nsfw": false, + "lang": "en", + "name": "hive-311119", + "num_authors": 0, + "num_pending": 0, + "settings": {}, + "subscribers": 0, + "sum_pending": 0, + "team": [ + [ + "hive-311119", + "owner", + "" + ] + ], + "title": "@hive-311119", + "type_id": 3 +} diff --git a/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.tavern.yaml b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.tavern.yaml new file mode 100644 index 0000000000000000000000000000000000000000..623ee80c503247888c41892105d447858e68d3b3 --- /dev/null +++ b/tests/api_tests/hivemind/tavern/bridge_api_patterns/get_community/hive-311119.tavern.yaml @@ -0,0 +1,28 @@ +--- + test_name: Hivemind + + marks: + - patterntest + # Communities not implemented under 5 mln blocks, but some were created by mock mechanism, therefore they are in result. + + includes: + - !include ../../common.yaml + + stages: + - name: test + request: + url: "{service.proto:s}://{service.server:s}:{service.port}/" + method: POST + headers: + content-type: application/json + json: + jsonrpc: "2.0" + id: 1 + method: "bridge.get_community" + params: {"name":"hive-311119"} + response: + status_code: 200 + verify_response_with: + function: validate_response:compare_response_with_pattern + extra_kwargs: + ignore_tags: "<bridge community>"