From 6340836f237f0c3f2a6694d8e95ed2a55011ba8f Mon Sep 17 00:00:00 2001 From: Caleb Jacob Date: Mon, 16 Oct 2023 13:44:10 -0600 Subject: [PATCH 1/3] Show all apps on app library search when no query is entered, fix stale state inside onQueryChange --- src/AppLibrary/Search.jsx | 48 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/AppLibrary/Search.jsx b/src/AppLibrary/Search.jsx index 9ad2faf5..0771ec06 100644 --- a/src/AppLibrary/Search.jsx +++ b/src/AppLibrary/Search.jsx @@ -1,15 +1,7 @@ -function search(query) { - if (!query) { - State.update({ - isLoading: false, - results: [], - }); - return; - } - +function search({ currentPage, query, results }) { const body = { query, - page: state.currentPage, + page: currentPage, filters: "categories:widget AND tags:app AND NOT _tags:hidden", }; @@ -23,8 +15,9 @@ function search(query) { .then((res) => { State.update({ isLoading: false, - results: [...state.results, ...res.body.hits], + results: [...results, ...res.body.hits], totalPages: res.body.nbPages, + totalResults: res.body.nbHits, }); }) .catch((error) => { @@ -39,6 +32,7 @@ function handleOnInput() { State.update({ currentPage: 0, totalPages: 0, + totalResults: 0, isLoading: true, results: [], }); @@ -48,7 +42,12 @@ function handleOnQueryChange(query) { State.update({ query, }); - search(query); + + search({ + currentPage: 0, + results: [], + query, + }); } function loadMore() { @@ -57,17 +56,34 @@ function loadMore() { isLoading: true, }); - search(state.query); + search({ + currentPage: state.currentPage, + results: state.results, + query: state.query, + }); } State.init({ currentPage: 0, totalPages: 0, + totalResults: 0, isLoading: false, query: "", results: [], }); +if (!state.isLoading && state.results.length === 0 && state.query === "") { + State.update({ + isLoading: true, + }); + + search({ + currentPage: 0, + results: [], + query: "", + }); +} + const Wrapper = styled.div` display: flex; width: 100%; @@ -91,11 +107,11 @@ const Text = styled.p` const ContentGrid = styled.div` display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem; @media (max-width: 650px) { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); } `; @@ -116,7 +132,7 @@ return ( {state.results.length > 0 && ( <> - Showing {state.results.length} results + {state.query && Showing {state.totalResults} results} {state.results.map((result) => { From 4e83e20bc9c44305574f18299b1b7be7f7ad23f3 Mon Sep 17 00:00:00 2001 From: Charles Garrett Date: Mon, 16 Oct 2023 15:55:25 -0400 Subject: [PATCH 2/3] chore: fix alignment issue --- src/NearOrg/Notifications/Notification.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/NearOrg/Notifications/Notification.jsx b/src/NearOrg/Notifications/Notification.jsx index 7e92331c..1dfd599b 100644 --- a/src/NearOrg/Notifications/Notification.jsx +++ b/src/NearOrg/Notifications/Notification.jsx @@ -226,10 +226,14 @@ return ( - - {profile.name || accountId.split(".near")[0]} - - {notificationMessage[type]} + {/*{componentName}*/} From 1a74dba6a218be722fdbe488573a18ce618f7ab4 Mon Sep 17 00:00:00 2001 From: Roshaan Siddiqui Date: Mon, 16 Oct 2023 19:28:08 -0500 Subject: [PATCH 3/3] Revert "Merge pull request #394 from near/375-filter-activityfeed-based-on-new-moderation-format" This reverts commit 609cb1261abc9ad262b85f400ffdc550b5a4d8c1, reversing changes made to d1554f5707ca97f1dc596a8e4b58224bdfa38b3c. --- indexers/README.md | 7 - indexers/moderation/moderation.js | 248 ------------ indexers/moderation/moderation.sql | 43 --- indexers/social_feed/social_feed.js | 466 ----------------------- indexers/social_feed/social_feed.sql | 73 ---- indexers/verifications/verifications.js | 77 ---- indexers/verifications/verifications.sql | 15 - src/Moderation/ModerateAccounts.jsx | 2 +- src/Moderation/ModerateComments.jsx | 9 +- src/Moderation/ModeratePosts.jsx | 9 +- src/Posts.jsx | 91 +++-- src/Posts/Menu.jsx | 174 ++------- src/Posts/Post.jsx | 45 +-- 13 files changed, 133 insertions(+), 1126 deletions(-) delete mode 100644 indexers/README.md delete mode 100644 indexers/moderation/moderation.js delete mode 100644 indexers/moderation/moderation.sql delete mode 100644 indexers/social_feed/social_feed.js delete mode 100644 indexers/social_feed/social_feed.sql delete mode 100644 indexers/verifications/verifications.js delete mode 100644 indexers/verifications/verifications.sql diff --git a/indexers/README.md b/indexers/README.md deleted file mode 100644 index 649be3ab..00000000 --- a/indexers/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# QueryApi Indexers used by components - -Several components use QueryApi Indexers as a source of data, specifically the ActivityFeed Posts & 'Following' Posts. - -These indexers can be modified at https://near.org/dataplatform.near/widget/QueryApi.App under the dataplatform.near account. - -Each indexer has a .sql schema file and a .js file which contains the Indexer Function code. diff --git a/indexers/moderation/moderation.js b/indexers/moderation/moderation.js deleted file mode 100644 index f256fd25..00000000 --- a/indexers/moderation/moderation.js +++ /dev/null @@ -1,248 +0,0 @@ -import { Block } from "@near-lake/primitives"; -/** - * Note: We only support javascript at the moment. We will support Rust, Typescript in a further release. - */ - -/** - * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. - * context is a global variable that contains helper methods. - * context.db is a subfield which contains helper methods to interact with your database. - * - * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers - * - * @param {block} Block - A Near Protocol Block - */ -async function getBlock(block: Block) { - function base64decode(encodedValue) { - let buff = Buffer.from(encodedValue, "base64"); - return JSON.parse(buff.toString("utf-8")); - } - - function parseIndexAction(actionName, userData) { - const returnValues = []; - const actionData = - userData.index && userData.index[actionName] - ? userData.index[actionName] - : null; - if (actionData) { - const actionDataParsed = JSON.parse(actionData); - const actionsArray = Array.isArray(actionDataParsed) - ? actionDataParsed - : [actionDataParsed]; - for (let a of actionsArray) { - const value = a.value; - if (value && value.path) { - const fullPath = value.path.split("/"); - const account = fullPath[0]; - const path = - fullPath.length > 1 ? "/" + fullPath.slice(1).join("/") : null; - const { blockHeight, operation } = value; - const label = value.label ?? "report"; - returnValues.push({ account, path, blockHeight, label, operation }); - } - } - } - return returnValues; - } - - const calls = block.actions().flatMap((action) => - action.operations - .map((operation) => operation["FunctionCall"]) - .filter((operation) => operation?.methodName === "set") - .map((functionCallOperation) => ({ - ...functionCallOperation, - args: base64decode(functionCallOperation.args), - })) - .filter((functionCall) => { - const accountId = Object.keys(functionCall.args.data)[0]; - return ( - functionCall.args.data[accountId].moderate || - (functionCall.args.data[accountId].index && - functionCall.args.data[accountId].index.moderate) || - // ignore index.moderation because it duplicates the non-index moderate key - (functionCall.args.data[accountId].index && - functionCall.args.data[accountId].index.flag) // backwards compatibility - ); - }), - ); - - for (let index in calls) { - let call = calls[index]; - console.log("call", call); - try { - const accountId = Object.keys(call.args.data)[0]; - const userData = call.args.data[accountId]; - - // user self-moderation data and reported feed - const reportedObjects = []; // build up an array of reported objects - const userModeration = userData.moderate; - if(userModeration) { - const moderatedAccounts = Object.keys(userModeration); - for (let account of moderatedAccounts) { - let value = userModeration[account]; - if (typeof value === "string") { - const label = value; - reportedObjects.push({ account, label }); - } else { - for (let path of Object.keys(value)) { - value = value[path]; - if (typeof value === "string") { - const label = value; - reportedObjects.push({ account, path, label }); - } else { - for (let blockHeight of Object.keys(value)) { - value = value[blockHeight]; - if (typeof value === "string") { - const label = value; - reportedObjects.push({ account, path, blockHeight, label }); - } else { - const label = value.label; - const expiration = value.expiration; - reportedObjects.push({ - account, - path, - blockHeight, - label, - expiration, - }); - } - } - } - } - } - } - } - - const userFlags = parseIndexAction("flag", userData); // backwards compatibility - if (userFlags && userFlags.length > 0) { - reportedObjects.push(userFlags); - } - - console.log("Reported objects", reportedObjects); - - const group = "near.org"; - for (let report of reportedObjects) { - const label = report.label; - const moderated_account_id = report.account; - const moderated_path = report.path?.replace(/\./g, "/"); - const moderated_blockheight = parseInt(report.blockHeight); - const mutationData = { - report: { - group, - account_id: accountId, - moderated_account_id, - moderated_path, - moderated_blockheight, - label, - }, - }; - context.graphql( - `mutation insertReport($report: dataplatform_near_moderation_moderation_reporting_insert_input!) { - insert_dataplatform_near_moderation_moderation_reporting_one( object: $report - ) { group account_id moderated_account_id moderated_path moderated_blockheight label } - }`, - mutationData, - ); - // pending upgrade to Postgres 15 for NULLS NOT DISTINCT - // on_conflict: {constraint: moderation_reporting_pkey, update_columns: [label, expiration]} - - // requires QueryApi V2 - // context.db.ModerationReporting.insert({ - // group, - // account_id: accountId, - // moderated_account_id, - // moderated_path, - // moderated_blockheight, - // label, - // }); - } - - // moderator decisions - const moderatorsForGroup = ["bosmod.near", "flatirons.near"]; // moderator, testing. future query placeholder - if (moderatorsForGroup.includes(accountId)) { - const decisions = parseIndexAction("moderate", userData); - if (decisions) { - for (let decision of decisions) { - console.log("decision", decision); - const operation = decision.operation; - const label = decision.label; - const moderated_account_id = decision.account; - const moderated_path = decision.path; - const moderated_blockheight = decision.blockHeight; - - if (operation) { - switch (operation) { - case "delete": - const pathDescriminator = moderated_path - ? `{_eq: "${moderated_path}"}` - : `{_is_null: true}`; - const blockHeightDescriminator = moderated_blockheight - ? `{_eq: ${moderated_blockheight}}` - : `{_is_null: true}`; - const mutation = `mutation { - delete_dataplatform_near_moderation_moderation_decisions( - where: {moderated_account_id: {_eq: "${moderated_account_id}"}, moderated_path: ${pathDescriminator}, moderated_blockheight: ${blockHeightDescriminator}} - ) { - returning { - moderated_account_id - moderated_path - moderated_blockheight - } - } - }`; - console.log("mutation", mutation); - context.graphql(mutation, {}); - break; - case "update": - // todo - console.log( - "Update operation is not yet implemented", - decision, - ); - break; - default: - console.log( - "Invalid moderation operation", - operation, - decision, - ); - } - } else { - const mutationData = { - decision: { - group, - moderator_account_id: accountId, - moderated_account_id, - moderated_path, - moderated_blockheight, - label, - }, - }; - context.graphql( - `mutation insertDecision($decision: dataplatform_near_moderation_moderation_decisions_insert_input!) { - insert_dataplatform_near_moderation_moderation_decisions_one( object: $decision - ) { group moderator_account_id moderated_account_id moderated_path moderated_blockheight label } - }`, - mutationData, - ); - // pending upgrade to Postgres 15 for NULLS NOT DISTINCT - // on_conflict: {constraint: moderation_decisions_pkey, update_columns: [label, expiration, notes]} - - // requires QueryApi V2 - // context.db.ModerationDecisions.insert({ - // group, - // moderator_account_id: accountId, - // moderated_account_id, - // moderated_path, - // moderated_blockheight, - // label, - // }); - } - } - } - } - } catch (error) { - console.log("Caught error", error); - } - } -} diff --git a/indexers/moderation/moderation.sql b/indexers/moderation/moderation.sql deleted file mode 100644 index 3ed81d55..00000000 --- a/indexers/moderation/moderation.sql +++ /dev/null @@ -1,43 +0,0 @@ -CREATE TABLE - "moderation_decisions" ( - "group" text NOT NULL, - "moderator_account_id" text NOT NULL, - "moderated_account_id" text NOT NULL, - "moderated_path" text NULL, - "moderated_blockheight" integer NULL, - "base_content_account" text NULL, -- original poster when comment is being moderated - "base_content_path" text NULL, -- /post/main, /post/comment, etc - "base_content_blockheight" text NULL, -- original post blockheight - "label" text NOT NULL, - "expiration" timestamp NULL, - "notes" text NULL -); - -CREATE INDEX - moderation_decisions_content_idx ON moderation_decisions ( - "moderated_account_id", - "moderated_path", - "moderated_blockheight" - ); - -CREATE TABLE - "moderation_reporting" ( - "group" text NOT NULL, - "account_id" text NOT NULL, - "moderated_account_id" text NOT NULL, - "moderated_path" text NULL, - "moderated_blockheight" integer NULL, - "base_content_account" text NULL, -- original poster when comment is being moderated - "base_content_path" text NULL, -- /post/main, /post/comment, etc - "base_content_blockheight" text NULL, -- original post blockheight - "label" text NOT NULL, - "expiration" timestamp NULL, - "notes" text NULL -); - -CREATE INDEX - moderation_reporting_content_idx ON moderation_reporting ( - "moderated_account_id", - "moderated_path", - "moderated_blockheight" - ); diff --git a/indexers/social_feed/social_feed.js b/indexers/social_feed/social_feed.js deleted file mode 100644 index df5982c2..00000000 --- a/indexers/social_feed/social_feed.js +++ /dev/null @@ -1,466 +0,0 @@ -import { Block } from "@near-lake/primitives"; -/** - * Note: We only support javascript at the moment. We will support Rust, Typescript in a further release. - */ - -/** - * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. - * context is a global variable that contains helper methods. - * context.db is a subfield which contains helper methods to interact with your database. - * - * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers - * - * @param {block} Block - A Near Protocol Block - */ -async function getBlock(block: Block) { - function base64decode(encodedValue) { - let buff = Buffer.from(encodedValue, "base64"); - return JSON.parse(buff.toString("utf-8")); - } - - async function handlePostCreation( - accountId, - blockHeight, - blockTimestamp, - receiptId, - content, - ) { - try { - const mutationData = { - post: { - account_id: accountId, - block_height: blockHeight, - block_timestamp: blockTimestamp, - content: content, - receipt_id: receiptId, - }, - }; - - // Call GraphQL mutation to insert a new post - await context.graphql( - `mutation createPost($post: dataplatform_near_social_feed_posts_insert_input!){ - insert_dataplatform_near_social_feed_posts_one( - object: $post - ) { - account_id - block_height - } - }`, - mutationData, - ); - - console.log(`Post by ${accountId} has been added to the database`); - } catch (e) { - console.log( - `Failed to store post by ${accountId} to the database (perhaps it already stored)`, - ); - } - } - - async function handleCommentCreation( - accountId, - blockHeight, - blockTimestamp, - receiptId, - commentString, - ) { - const comment = JSON.parse(commentString); - const postAuthor = comment.item.path.split("/")[0]; - const postBlockHeight = comment.item.blockHeight; - - // find post to retrieve Id or print a warning that we don't have it - try { - // Call GraphQL query to fetch posts that match specified criteria - const posts = await context.graphql( - `query getPosts($accountId: String = "$accountId", $blockHeight: numeric = "$blockHeight"){ - dataplatform_near_social_feed_posts( - where: { - account_id: {_eq: $accountId}, - block_height: {_eq: $blockHeight} - }, - limit: 1 - ) { - account_id - accounts_liked - block_height - block_timestamp - content - id - } - }`, - { - accountId: postAuthor, - blockHeight: postBlockHeight, - }, - ); - console.log(`posts: ${JSON.stringify(posts)}`); - if (posts.dataplatform_near_social_feed_posts.length === 0) { - return; - } - - const post = posts.dataplatform_near_social_feed_posts[0]; - - try { - delete comment["item"]; - const mutationData = { - comment: { - account_id: accountId, - receipt_id: receiptId, - block_height: blockHeight, - block_timestamp: blockTimestamp, - content: JSON.stringify(comment), - post_id: post.id, - }, - }; - // Call GraphQL mutation to insert a new comment - await context.graphql( - `mutation createComment($comment: dataplatform_near_social_feed_comments_insert_input!){ - insert_dataplatform_near_social_feed_comments_one( - object: $comment - ) { - account_id - receipt_id - block_height - block_timestamp - content - post_id - } - }`, - mutationData, - ); - - // Update last comment timestamp in Post table - const currentTimestamp = Date.now(); - await context.graphql( - `mutation SetLastCommentUpdated { - update_dataplatform_near_social_feed_posts( - where: {id: {_eq: ${post.id}}} - _set: {last_comment_timestamp: ${currentTimestamp}} - ) - { - returning { - id - } - } - } - `, - {}, - ); - console.log(`Comment by ${accountId} has been added to the database`); - } catch (e) { - console.log( - `Failed to store comment to the post ${postAuthor}/${postBlockHeight} by ${accountId} perhaps it has already been stored. Error ${e}`, - ); - } - } catch (e) { - console.log( - `Failed to store comment to the post ${postAuthor}/${postBlockHeight} as we don't have the post stored.`, - ); - } - } - - async function handleLike( - accountId, - blockHeight, - blockTimestamp, - receiptId, - likeContent, - ) { - const like = JSON.parse(likeContent); - const likeAction = like.value.type; // like or unlike - const [itemAuthor, _, itemType] = like.key.path.split("/", 3); - const itemBlockHeight = like.key.blockHeight; - console.log("handling like", receiptId, accountId); - switch (itemType) { - case "main": - try { - const posts = await context.graphql( - `query getPosts($accountId: String = "$accountId", $blockHeight: numeric = "$blockHeight"){ - dataplatform_near_social_feed_posts( - where: { - account_id: {_eq: $accountId}, - block_height: {_eq: $blockHeight} - }, - limit: 1 - ) { - account_id - accounts_liked - block_height - block_timestamp - content - id - } - }`, - { - accountId: itemAuthor, - blockHeight: itemBlockHeight, - }, - ); - if (posts.dataplatform_near_social_feed_posts.length == 0) { - return; - } - - const post = posts.dataplatform_near_social_feed_posts[0]; - switch (likeAction) { - case "like": - await _handlePostLike( - post.id, - accountId, - blockHeight, - blockTimestamp, - receiptId, - ); - break; - case "unlike": - default: - await _handlePostUnlike(post.id, accountId); - break; - } - } catch (e) { - console.log( - `Failed to store like to post ${itemAuthor}/${itemBlockHeight} as we don't have it stored in the first place.`, - ); - } - break; - case "comment": - // Comment - console.log(`Likes to comments are not supported yet. Skipping`); - break; - default: - // something else - console.log(`Got unsupported like type "${itemType}". Skipping...`); - break; - } - } - - async function _handlePostLike( - postId, - likeAuthorAccountId, - likeBlockHeight, - blockTimestamp, - receiptId, - ) { - try { - const posts = await context.graphql( - `query getPosts($postId: Int!) { - dataplatform_near_social_feed_posts(where: { id: { _eq: $postId } }) { - id - account_id - block_height - block_timestamp - content - accounts_liked - } - }`, - { postId: postId }, - ); - if (posts.dataplatform_near_social_feed_posts.length == 0) { - return; - } - const post = posts.dataplatform_near_social_feed_posts[0]; - let accountsLiked = - post.accounts_liked.length === 0 - ? post.accounts_liked - : JSON.parse(post.accounts_liked); - - if (accountsLiked.indexOf(likeAuthorAccountId) === -1) { - accountsLiked.push(likeAuthorAccountId); - } - - // Call GraphQL mutation to update a post's liked accounts list - await context.graphql( - `mutation updatePost($postId: Int!, $likedAccount: jsonb){ - update_dataplatform_near_social_feed_posts( - where: {id: {_eq: $postId}} - _set: {accounts_liked: $likedAccount} - ) { - returning { - id - } - } - }`, - { - postId: postId, - likedAccount: JSON.stringify(accountsLiked), - }, - ); - - const postLikeMutation = { - postLike: { - post_id: postId, - account_id: likeAuthorAccountId, - block_height: likeBlockHeight, - block_timestamp: blockTimestamp, - receipt_id: receiptId, - }, - }; - // Call GraphQL mutation to insert a new like for a post - await context.graphql( - ` - mutation InsertLike($postLike: dataplatform_near_social_feed_post_likes_insert_input!) { - insert_dataplatform_near_social_feed_post_likes_one(object: $postLike) { - post_id - } - } - `, - postLikeMutation, - ); - } catch (e) { - console.log(`Failed to store like to in the database: ${e}`); - } - } - - async function _handlePostUnlike(postId, likeAuthorAccountId) { - try { - const posts = await context.graphql( - `query getPosts($postId: Int!) { - dataplatform_near_social_feed_posts(where: { id: { _eq: $postId } }) { - id - account_id - block_height - block_timestamp - content - accounts_liked - } - }`, - { postId: postId }, - ); - if (posts.dataplatform_near_social_feed_posts.length == 0) { - return; - } - const post = posts.dataplatform_near_social_feed_posts[0]; - let accountsLiked = - post.accounts_liked.length === 0 - ? post.accounts_liked - : JSON.parse(post.accounts_liked); - - console.log(accountsLiked); - - let indexOfLikeAuthorAccountIdInPost = - accountsLiked.indexOf(likeAuthorAccountId); - if (indexOfLikeAuthorAccountIdInPost > -1) { - accountsLiked.splice(indexOfLikeAuthorAccountIdInPost, 1); - // Call GraphQL mutation to update a post's liked accounts list - await context.graphql( - `mutation updatePost($postId: Int!, $likedAccount: jsonb){ - update_dataplatform_near_social_feed_posts( - where: {id: {_eq: $postId}} - _set: {accounts_liked: $likedAccount} - ) { - returning { - id - } - } - }`, - { - postId: postId, - likedAccount: JSON.stringify(accountsLiked), - }, - ); - } - // Call GraphQL mutation to delete a like for a post - await context.graphql( - `mutation deletePostLike($accountId: String!, $postId: Int!){ - delete_dataplatform_near_social_feed_post_likes( - where: { - _and: [ - {account_id: {_eq: $accountId}}, - {post_id: {_eq: $postId}} - ] - } - ) { - returning { - post_id - account_id - } - } - }`, - { - accountId: likeAuthorAccountId, - postId: postId, - }, - ); - } catch (e) { - console.log(`Failed to delete like from the database: ${e}`); - } - } - - // Add your code here - const SOCIAL_DB = "social.near"; - - const nearSocialPosts = block - .actions() - .filter((action) => action.receiverId === SOCIAL_DB) - .flatMap((action) => - action.operations - .map((operation) => operation["FunctionCall"]) - .filter((operation) => operation?.methodName === "set") - .map((functionCallOperation) => ({ - ...functionCallOperation, - args: base64decode(functionCallOperation.args), - receiptId: action.receiptId, // providing receiptId as we need it - })) - .filter((functionCall) => { - const accountId = Object.keys(functionCall.args.data)[0]; - return ( - Object.keys(functionCall.args.data[accountId]).includes("post") || - Object.keys(functionCall.args.data[accountId]).includes("index") - ); - }), - ); - - if (nearSocialPosts.length > 0) { - console.log("Found Near Social Posts in Block..."); - const blockHeight = block.blockHeight; - const blockTimestamp = block.header().timestampNanosec; - await Promise.all( - nearSocialPosts.map(async (postAction) => { - const accountId = Object.keys(postAction.args.data)[0]; - console.log(`ACCOUNT_ID: ${accountId}`); - - // if creates a post - if ( - postAction.args.data[accountId].post && - Object.keys(postAction.args.data[accountId].post).includes("main") - ) { - console.log("Creating a post..."); - await handlePostCreation( - accountId, - blockHeight, - blockTimestamp, - postAction.receiptId, - postAction.args.data[accountId].post.main, - ); - } else if ( - postAction.args.data[accountId].post && - Object.keys(postAction.args.data[accountId].post).includes("comment") - ) { - // if creates a comment - await handleCommentCreation( - accountId, - blockHeight, - blockTimestamp, - postAction.receiptId, - postAction.args.data[accountId].post.comment, - ); - } else if ( - Object.keys(postAction.args.data[accountId]).includes("index") - ) { - // Probably like or unlike action is happening - if ( - Object.keys(postAction.args.data[accountId].index).includes("like") - ) { - console.log("handling like"); - await handleLike( - accountId, - blockHeight, - blockTimestamp, - postAction.receiptId, - postAction.args.data[accountId].index.like, - ); - } - } - }), - ); - } -} diff --git a/indexers/social_feed/social_feed.sql b/indexers/social_feed/social_feed.sql deleted file mode 100644 index bd06788f..00000000 --- a/indexers/social_feed/social_feed.sql +++ /dev/null @@ -1,73 +0,0 @@ -CREATE TABLE - "posts" ( - "id" SERIAL NOT NULL, - "account_id" VARCHAR NOT NULL, - "block_height" DECIMAL(58, 0) NOT NULL, - "receipt_id" VARCHAR NOT NULL, - "content" TEXT NOT NULL, - "block_timestamp" DECIMAL(20, 0) NOT NULL, - "accounts_liked" JSONB NOT NULL DEFAULT '[]', - "last_comment_timestamp" DECIMAL(20, 0), - CONSTRAINT "posts_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE - "comments" ( - "id" SERIAL NOT NULL, - "post_id" SERIAL NOT NULL, - "account_id" VARCHAR NOT NULL, - "block_height" DECIMAL(58, 0) NOT NULL, - "content" TEXT NOT NULL, - "block_timestamp" DECIMAL(20, 0) NOT NULL, - "receipt_id" VARCHAR NOT NULL, - CONSTRAINT "comments_pkey" PRIMARY KEY ("id") -); - -CREATE TABLE - "post_likes" ( - "post_id" SERIAL NOT NULL, - "account_id" VARCHAR NOT NULL, - "block_height" DECIMAL(58, 0), - "block_timestamp" DECIMAL(20, 0) NOT NULL, - "receipt_id" VARCHAR NOT NULL, - CONSTRAINT "post_likes_pkey" PRIMARY KEY ("post_id", "account_id") -); - -CREATE UNIQUE INDEX "posts_account_id_block_height_key" ON "posts" ("account_id" ASC, "block_height" ASC); - -CREATE UNIQUE INDEX "comments_post_id_account_id_block_height_key" ON "comments" ( - "post_id" ASC, - "account_id" ASC, - "block_height" ASC - ); - -CREATE INDEX - "posts_last_comment_timestamp_idx" ON "posts" ("last_comment_timestamp" DESC); - -ALTER TABLE - "comments" - ADD - CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; - -ALTER TABLE - "post_likes" - ADD - CONSTRAINT "post_likes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE CASCADE ON UPDATE NO ACTION; - -CREATE VIEW moderated_posts as -SELECT * FROM dataplatform_near_social_feed.posts p -WHERE NOT EXISTS ( - SELECT * FROM dataplatform_near_moderation.moderation_decisions d - WHERE p.account_id = d.moderated_account_id - AND (d.moderated_path IS NULL OR (d.moderated_path = '/post/main' AND d.moderated_blockheight = p.block_height)) -); - -CREATE VIEW moderated_comments as -SELECT * FROM dataplatform_near_social_feed.comments p -WHERE NOT EXISTS ( - SELECT * FROM dataplatform_near_moderation.moderation_decisions d - WHERE p.account_id = d.moderated_account_id - AND (d.moderated_path IS NULL OR (d.moderated_path = '/post/comment' AND d.moderated_blockheight = p.block_height)) -); - --- note, relationships manually added in Hasura between moderated_posts->moderated_comments and moderated_posts->verifications \ No newline at end of file diff --git a/indexers/verifications/verifications.js b/indexers/verifications/verifications.js deleted file mode 100644 index 5409a33f..00000000 --- a/indexers/verifications/verifications.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Block } from "@near-lake/primitives"; -/** - * Note: We only support javascript at the moment. We will support Rust, Typescript in a further release. - */ - -/** - * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. - * context is a global variable that contains helper methods. - * context.db is a subfield which contains helper methods to interact with your database. - * - * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers - * - * @param {block} Block - A Near Protocol Block - */ -async function getBlock(block: Block) { - function base64decode(encodedValue) { - let buff = Buffer.from(encodedValue, "base64"); - return JSON.parse(buff.toString("utf-8")); - } - const calls = block.actions().flatMap((action) => - action.operations - .map((operation) => operation["FunctionCall"]) - .filter((operation) => operation?.methodName === "sbt_mint") - .map((functionCallOperation) => ({ - ...functionCallOperation, - args: base64decode(functionCallOperation.args), - })), - ); - - for (let index in calls) { - let call = calls[index]; - console.log("call", call); - try { - if (call.args.token_spec) { - const accountId = call.args.token_spec[0][0]; - const issuance = call.args.token_spec[0][1][0]; - if (accountId && issuance) { - const level = issuance.class ? issuance.class.toString() : ""; - const expires = new Date(issuance.expires_at).toISOString(); - const provider = "i-am-human.app"; - console.log( - "found verification:", - accountId, - level, - expires, - provider, - ); - - const mutationData = { - account: { - account_id: accountId, - human_valid_until: expires, - human_verification_level: level, - human_provider: provider, - }, - }; - - await context.graphql( - `mutation createAccount($account: dataplatform_near_verifications_account_insert_input!){ - insert_dataplatform_near_verifications_account_one( - object: $account - on_conflict: {constraint: account_pkey, update_columns: [human_valid_until,human_verification_level, human_provider]} - ) { account_id} - }`, - mutationData, - ); - } else { - console.log("No account_id or issuance in this token spec"); - } - } else { - console.log("No token spec in this mint operation"); - } - } catch (error) { - console.log("Caught error", error); - } - } -} diff --git a/indexers/verifications/verifications.sql b/indexers/verifications/verifications.sql deleted file mode 100644 index b47bb8c1..00000000 --- a/indexers/verifications/verifications.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE - "account" ( - "account_id" TEXT NOT NULL, - "human_valid_until" timestamp NULL, - "human_verification_level" text NULL, - "human_provider" text NULL, - "kyc_valid_until" timestamp NULL, - "kyc_verification_level" text NULL, - "kyc_provider" text NULL, - "social_trust_score_1" integer NULL, - "social_trust_provider_1" text NULL, - "social_trust_score_2" integer NULL, - "social_trust_provider_2" text NULL, - PRIMARY KEY ("account_id") -) diff --git a/src/Moderation/ModerateAccounts.jsx b/src/Moderation/ModerateAccounts.jsx index 769e18d1..21450622 100644 --- a/src/Moderation/ModerateAccounts.jsx +++ b/src/Moderation/ModerateAccounts.jsx @@ -30,7 +30,7 @@ const moderatedObjects = context.accountId ? Social.index("moderate", moderationStream, { subscribe: true, order: "desc", - })?.filter((f) => f.accountId === moderatorAccount) + }).filter((f) => f.accountId === moderatorAccount) : []; const modifiedModerations = {}; // track update & delete operations diff --git a/src/Moderation/ModerateComments.jsx b/src/Moderation/ModerateComments.jsx index cadddea8..649c497a 100644 --- a/src/Moderation/ModerateComments.jsx +++ b/src/Moderation/ModerateComments.jsx @@ -18,7 +18,7 @@ function blockComment(accountId, blockHeight) { key: moderationStream, value: { path: `${account}${objectPath}`, - blockHeight: parseInt(block), + blockHeight: block, label: "moderate", }, }); @@ -29,7 +29,7 @@ function unblockComment(accountId, blockHeight) { key: moderationStream, value: { path: `${accountId}${objectPath}`, - blockHeight: parseInt(blockHeight), + blockHeight: blockHeight, label: null, operation: "delete", }, @@ -40,8 +40,9 @@ const moderatedObjects = context.accountId ? Social.index("moderate", moderationStream, { subscribe: true, order: "desc", - })?.filter((f) => f.accountId === moderatorAccount) - : []; + }) + : // .filter((f) => f.accountId === moderatorAccount) + []; const modifiedModerations = {}; // track update & delete operations return ( diff --git a/src/Moderation/ModeratePosts.jsx b/src/Moderation/ModeratePosts.jsx index b9905dcf..155d1f5a 100644 --- a/src/Moderation/ModeratePosts.jsx +++ b/src/Moderation/ModeratePosts.jsx @@ -18,7 +18,7 @@ function blockPost(accountId, blockHeight) { key: moderationStream, value: { path: `${account}${objectPath}`, - blockHeight: parseInt(block), + blockHeight: block, label: "moderate", }, }); @@ -29,7 +29,7 @@ function unblockPost(accountId, blockHeight) { key: moderationStream, value: { path: `${accountId}${objectPath}`, - blockHeight: parseInt(blockHeight), + blockHeight: blockHeight, label: null, operation: "delete", }, @@ -40,8 +40,9 @@ const moderatedObjects = context.accountId ? Social.index("moderate", moderationStream, { subscribe: true, order: "desc", - })?.filter((f) => f.accountId === moderatorAccount) - : []; + }) + : // .filter((f) => f.accountId === moderatorAccount) + []; const modifiedModerations = {}; // track update & delete operations return ( diff --git a/src/Posts.jsx b/src/Posts.jsx index aeea94f9..c315fd32 100644 --- a/src/Posts.jsx +++ b/src/Posts.jsx @@ -26,7 +26,7 @@ const queryFollowedAccountsList = () => { const graph = Social.keys(`${context.accountId}/graph/follow/*`, "final"); if (graph !== null) { const followedAccounts = Object.keys( - graph[context.accountId].graph.follow || {}, + graph[context.accountId].graph.follow || {} ); State.update({ accountsFollowing: followedAccounts }); } @@ -45,31 +45,48 @@ const selectTab = (selectedTab) => { loadMorePosts(); }; -// get the full list of posts that the current user has flagged so -// they can be hidden +let gatewayModeratedUsersRaw = Social.get( + `${moderatorAccount}/moderate/users`, + "optimistic", + { + subscribe: true, + } +); + const selfFlaggedPosts = context.accountId ? Social.index("flag", "main", { accountId: context.accountId, }) : []; -// V2 self moderation data, structure is like: +const selfModeration = context.accountId + ? Social.index("moderate", "main", { + accountId: context.accountId, + }) + : []; + +if (gatewayModeratedUsersRaw === null) { + // haven't loaded filter list yet, return early + return ""; +} + +const gatewayModeratedUsers = gatewayModeratedUsersRaw + ? JSON.parse(gatewayModeratedUsersRaw) + : []; + +// get the full list of posts that the current user has flagged so +// they can be hidden + +// expecting moderation structure for accounts and posts like // { moderate: { -// "account1.near": "report", +// "account1.near": "block", // "account2.near": { -// ".post.main": { // slashes are not allowed in keys -// "100000123": "spam", // post ids are account/blockHeight -// } +// "100000123": "spam", // }, // } // } -const selfModeration = context.accountId - ? Social.getr(`${context.accountId}/moderate`, "optimistic") - : []; -const postsModerationKey = ".post.main"; -const matchesModeration = (moderated, socialDBObjectType, item) => { - if (!moderated) return false; - const accountFound = moderated[item.account_id]; +function matchesModeration(moderated, item) { + let accountFound = moderated[accountId]; if (typeof accountFound === "undefined") { return false; } @@ -77,18 +94,19 @@ const matchesModeration = (moderated, socialDBObjectType, item) => { return true; } // match posts - const posts = accountFound[postsModerationKey]; - return posts && typeof posts[item.block_height] !== "undefined"; -}; + return typeof accountFound[item.block_height] !== "undefined"; +} const shouldFilter = (item) => { return ( + gatewayModeratedUsers.includes(item.account_id) || selfFlaggedPosts.find((flagged) => { return ( flagged?.value?.blockHeight === item.block_height && flagged?.value?.path.includes(item.account_id) ); - }) || matchesModeration(selfModeration, postsModerationKey, item) + }) || + matchesModeration(selfModeration, item) ); }; function fetchGraphQL(operationsDoc, operationName, variables) { @@ -129,7 +147,7 @@ const createQuery = (type) => { const indexerQueries = ` query GetPostsQuery($offset: Int, $limit: Int) { - dataplatform_near_social_feed_moderated_posts(order_by: [${querySortOption} { block_height: desc }], offset: $offset, limit: $limit) { + dataplatform_near_social_feed_posts(order_by: [${querySortOption} { block_height: desc }], offset: $offset, limit: $limit) { account_id block_height block_timestamp @@ -149,14 +167,14 @@ query GetPostsQuery($offset: Int, $limit: Int) { human_verification_level } } - dataplatform_near_social_feed_moderated_posts_aggregate { + dataplatform_near_social_feed_posts_aggregate(order_by: [${querySortOption} { block_height: desc }], offset: $offset){ aggregate { count } } } query GetFollowingPosts($offset: Int, $limit: Int) { - dataplatform_near_social_feed_moderated_posts(where: {${queryFilter}}, order_by: [{ block_height: desc }], offset: $offset, limit: $limit) { + dataplatform_near_social_feed_posts(where: {${queryFilter}}, order_by: [{ block_height: desc }], offset: $offset, limit: $limit) { account_id block_height block_timestamp @@ -177,7 +195,7 @@ query GetFollowingPosts($offset: Int, $limit: Int) { } } - dataplatform_near_social_feed_moderated_posts_aggregate(where: {${queryFilter}}) { + dataplatform_near_social_feed_posts_aggregate(where: {${queryFilter}}, order_by: [{ block_height: desc }], offset: $offset) { aggregate { count } @@ -189,10 +207,13 @@ query GetFollowingPosts($offset: Int, $limit: Int) { const loadMorePosts = () => { const queryName = - state.selectedTab === "following" ? "GetFollowingPosts" : "GetPostsQuery"; + state.selectedTab == "following" ? "GetFollowingPosts" : "GetPostsQuery"; const type = state.selectedTab; - if (state.selectedTab === "following" && !state.accountsFollowing) { + if ( + state.selectedTab == "following" && + !state.accountsFollowing + ) { return; } fetchGraphQL(createQuery(type), queryName, { @@ -206,16 +227,15 @@ const loadMorePosts = () => { } let data = result.body.data; if (data) { - const newPosts = data.dataplatform_near_social_feed_moderated_posts; + const newPosts = data.dataplatform_near_social_feed_posts; const postsCountLeft = - data.dataplatform_near_social_feed_moderated_posts_aggregate.aggregate - .count; + data.dataplatform_near_social_feed_posts_aggregate.aggregate.count; if (newPosts.length > 0) { let filteredPosts = newPosts.filter((i) => !shouldFilter(i)); filteredPosts = filteredPosts.map((post) => { const prevComments = post.comments; const filteredComments = prevComments.filter( - (comment) => !shouldFilter(comment), + (comment) => !shouldFilter(comment) ); post.comments = filteredComments; return post; @@ -245,13 +265,18 @@ if ( queryFollowedAccountsList(); } -if (!state.initLoadPostsAll && selfFlaggedPosts && selfModeration) { +if ( + !state.initLoadPostsAll && + selfFlaggedPosts && + selfModeration && + gatewayModeratedUsers +) { loadMorePosts(); State.update({ initLoadPostsAll: true }); } if ( - state.initLoadPostsAll === true && + state.initLoadPostsAll == true && !state.initLoadPosts && state.accountsFollowing ) { @@ -421,7 +446,7 @@ return ( - {state.selectedTab === "all" && ( + {state.selectedTab == "all" && ( Sort by: diff --git a/src/Posts/Menu.jsx b/src/Posts/Menu.jsx index 38d48128..0973e232 100644 --- a/src/Posts/Menu.jsx +++ b/src/Posts/Menu.jsx @@ -1,137 +1,45 @@ -const accountId = props.accountId; -const blockHeight = props.blockHeight; -const parentFunctions = props.parentFunctions; +const Item = styled.div` + padding: 0; + .btn { + width: 100%; + border:0; + text-align: left; + &:hover, + &:focus { + background-color: #ECEDEE; + text-decoration: none; + outline: none; + } -const confirmationMessages = { - hidePost: { - header: "Post Hidden", - detail: "This post will no longer be shown to you.", - }, - hideAccount: { - header: "Account Muted", - detail: "All posts from this account will be no longer be shown to you.", - }, - reportPost: { - header: "Post Reported for Moderation", - detail: - "The item will no longer be shown to you and will be reviewed. Thanks for helping our Content Moderators.", - }, - reportAccount: { - header: "Account Reported for Moderation", - detail: - "All posts from this account will no longer be shown to you and will be reviewed. Thanks for helping our Content Moderators.", - }, -}; -const moderatePost = (account, block, action, messageKey) => { - const data = { - moderate: { - [account]: { - ".post.main": { - [block]: action, - }, - }, - }, - }; - if (action === "report") { - data.index = { - moderation: JSON.stringify({ - key: "reported", - value: { - path: `${account}/post/main`, - blockHeight: block, - label: action, - }, - }), - }; - } - Social.set(data, { - onCommit: () => { - parentFunctions.resolveHidePost(confirmationMessages[messageKey]); - }, - }); -}; + i { + color: #7E868C; + } -const moderateAccount = (account, action, message) => { - const data = { - moderate: { - [account]: action, - }, - }; - if (action === "report") { - data.index = { - moderation: JSON.stringify({ - key: "reported", - value: { - path: account, - label: action, - }, - }), - }; + span { + font-weight: 500; + } } - Social.set(data, { - onCommit: () => { - alert(message); - }, - }); -}; +`; + return ( - , - items: [ - { - name: "Hide", - iconLeft: "ph-bold ph-eye-slash", - disabled: !context.accountId || context.accountId === accountId, - subMenuProps: { - items: [ - { - name: "Hide this Post", - iconLeft: "ph-bold ph-eye-slash", - onSelect: () => - moderatePost(accountId, blockHeight, "hide", "hidePost"), - }, - { - name: "Mute " + accountId, - iconLeft: "ph-bold ph-ear-slash", - onSelect: () => - moderateAccount(accountId, "hide", "hideAccount"), - }, - ], - }, - }, - { - name: ( - <> - - Report - - ), - disabled: !context.accountId || context.accountId === accountId, - subMenuProps: { - items: [ - { - name: "Report this Post", - iconLeft: "ph-bold ph-warning-octagon", - onSelect: () => - moderatePost(accountId, blockHeight, "report", "reportPost"), - }, - { - name: "Report " + accountId, - iconLeft: "ph-bold ph-warning-octagon", - onSelect: () => - moderateAccount(accountId, "report", "reportAccount"), - }, - ], - }, - }, - // { - // name: "Edit", - // iconLeft: "ph-bold ph-pencil me-1", - // onSelect: parentFunctions.toggleEdit, - // }, - ], - }} - /> -); +
+ +
    + + {props.elements.map(e => { + return (
  • + + {e} + +
  • ) + })} +
+
+) \ No newline at end of file diff --git a/src/Posts/Post.jsx b/src/Posts/Post.jsx index cd359f6b..535ecb89 100644 --- a/src/Posts/Post.jsx +++ b/src/Posts/Post.jsx @@ -10,8 +10,6 @@ const showFlagAccountFeature = props.showFlagAccountFeature; State.init({ hasBeenFlagged: false, - showFlaggedToast: false, - flaggedMessage: { header: "", detail: "" }, postExists: true, comments: props.comments ?? undefined, content: JSON.parse(props.content) ?? undefined, @@ -173,14 +171,6 @@ const CommentWrapper = styled.div` } `; -const resolveHidePost = (message) => { - State.update({ - hasBeenFlagged: true, - showFlaggedToast: true, - flaggedMessage: message, - }); -}; - const renderComment = (a) => { return (
@@ -206,11 +196,12 @@ if (state.hasBeenFlagged) { src={`${REPL_ACCOUNT}/widget/DIG.Toast`} props={{ type: "info", - title: state.flaggedMessage.header, - description: state.flaggedMessage.detail, - open: state.showFlaggedToast, + title: "Flagged for moderation", + description: + "Thanks for helping our Content Moderators. The item you flagged will be reviewed.", + open: state.hasBeenFlagged, onOpenChange: () => { - State.update({ showFlaggedToast: false }); + State.update({ hasBeenFlagged: false }); }, duration: 5000, }} @@ -255,19 +246,19 @@ return ( />
-
+ {false && ( + + Edit + , + ], }} /> -
+ )}
@@ -336,6 +327,16 @@ return ( url: postUrl, }} /> + { + State.update({ hasBeenFlagged: true }); + }, + }} + /> )} {state.showReply && (