From 6d05682cea3e286f546cc7adb0a6df7b3344cb46 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Wed, 5 Mar 2025 22:53:27 +0000 Subject: [PATCH 1/8] issue #787: exclude posts in own subs from spam detection --- .../migrations/20250305175301/migration.sql | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 prisma/migrations/20250305175301/migration.sql diff --git a/prisma/migrations/20250305175301/migration.sql b/prisma/migrations/20250305175301/migration.sql new file mode 100644 index 000000000..21bea07a8 --- /dev/null +++ b/prisma/migrations/20250305175301/migration.sql @@ -0,0 +1,47 @@ +-- exclude posts in own subs from spam detection +CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL) +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +DECLARE + repeats INTEGER; + self_replies INTEGER; +BEGIN + -- no fee escalation + IF within = interval '0' THEN + RETURN 0; + END IF; + + SELECT count(*) INTO repeats + FROM "Item" + JOIN "Sub" ON "Sub"."name" = "Item"."subName" + WHERE ( + (parent_id IS NULL AND "parentId" IS NULL) + OR + ("parentId" = parent_id AND user_id <> (SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId")) + ) + AND "Item"."userId" = user_id + AND "bio" = 'f' + AND "Sub"."userId" <> user_id + AND "Item".created_at > now_utc() - within; + + IF parent_id IS NULL THEN + RETURN repeats; + END IF; + + WITH RECURSIVE base AS ( + SELECT "Item".id, "Item"."parentId", "Item"."userId" + FROM "Item" + WHERE id = parent_id + AND "userId" = user_id + AND created_at > now_utc() - within + AND user_id <> (SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId") + UNION ALL + SELECT "Item".id, "Item"."parentId", "Item"."userId" + FROM base p + JOIN "Item" ON "Item".id = p."parentId" AND "Item"."userId" = p."userId" AND "Item".created_at > now_utc() - within) + SELECT count(*) INTO self_replies FROM base; + + RETURN repeats + self_replies; +END; +$$; From 991c6bc5ef98eccd93754d28f25de0936136a412 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Fri, 7 Mar 2025 21:19:04 +0000 Subject: [PATCH 2/8] Update prisma/migrations/20250305175301/migration.sql Co-authored-by: ekzyis --- prisma/migrations/20250305175301/migration.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/migrations/20250305175301/migration.sql b/prisma/migrations/20250305175301/migration.sql index 21bea07a8..30834a3ab 100644 --- a/prisma/migrations/20250305175301/migration.sql +++ b/prisma/migrations/20250305175301/migration.sql @@ -14,7 +14,7 @@ BEGIN SELECT count(*) INTO repeats FROM "Item" - JOIN "Sub" ON "Sub"."name" = "Item"."subName" + LEFT JOIN "Sub" ON "Sub"."name" = "Item"."subName" WHERE ( (parent_id IS NULL AND "parentId" IS NULL) OR @@ -22,7 +22,7 @@ BEGIN ) AND "Item"."userId" = user_id AND "bio" = 'f' - AND "Sub"."userId" <> user_id + AND ("Sub"."name" IS NULL OR "Sub"."userId" <> user_id) AND "Item".created_at > now_utc() - within; IF parent_id IS NULL THEN From 5b6cddf1caf03d2332d81cfe168e7cfb8de89fd2 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Fri, 14 Mar 2025 17:13:22 +0000 Subject: [PATCH 3/8] pass sub to item_spam() to make owner except from escalation --- api/paidAction/itemCreate.js | 2 +- api/resolvers/item.js | 6 +-- api/typeDefs/item.js | 2 +- components/fee-button.js | 6 +-- components/post.js | 2 +- components/reply.js | 4 +- .../migrations/20250314104901/migration.sql | 52 +++++++++++++++++++ 7 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 prisma/migrations/20250314104901/migration.sql diff --git a/api/paidAction/itemCreate.js b/api/paidAction/itemCreate.js index b8e8f4169..d6932cdff 100644 --- a/api/paidAction/itemCreate.js +++ b/api/paidAction/itemCreate.js @@ -42,7 +42,7 @@ export async function getCost ({ subName, parentId, uploadIds, boost = 0, bio }, const [{ cost }] = await models.$queryRaw` SELECT ${baseCost}::INTEGER * POWER(10, item_spam(${parseInt(parentId)}::INTEGER, ${me?.id ?? USER_ID.anon}::INTEGER, - ${me?.id && !bio ? ITEM_SPAM_INTERVAL : ANON_ITEM_SPAM_INTERVAL}::INTERVAL)) + ${me?.id && !bio ? ITEM_SPAM_INTERVAL : ANON_ITEM_SPAM_INTERVAL}::INTERVAL, ${subName}::TEXT)) * ${me ? 1 : 100}::INTEGER + (SELECT "nUnpaid" * "uploadFeesMsats" FROM upload_fees(${me?.id || USER_ID.anon}::INTEGER, ${uploadIds}::INTEGER[])) diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 2ff086337..5695edb43 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -368,11 +368,11 @@ function typeClause (type) { export default { Query: { - itemRepetition: async (parent, { parentId }, { me, models }) => { + itemRepetition: async (parent, { parentId, sub }, { me, models }) => { if (!me) return 0 // how many of the parents starting at parentId belong to me - const [{ item_spam: count }] = await models.$queryRawUnsafe(`SELECT item_spam($1::INTEGER, $2::INTEGER, '${ITEM_SPAM_INTERVAL}')`, - Number(parentId), Number(me.id)) + const [{ item_spam: count }] = await models.$queryRawUnsafe(`SELECT item_spam($1::INTEGER, $2::INTEGER, '${ITEM_SPAM_INTERVAL}', $3::TEXT)`, + Number(parentId), Number(me.id), sub) return count }, diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index 730f61830..34a6dbb0e 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -10,7 +10,7 @@ export default gql` search(q: String, sub: String, cursor: String, what: String, sort: String, when: String, from: String, to: String): Items auctionPosition(sub: String, id: ID, boost: Int): Int! boostPosition(sub: String, id: ID, boost: Int): BoostPositions! - itemRepetition(parentId: ID): Int! + itemRepetition(parentId: ID, sub: String): Int! } type BoostPositions { diff --git a/components/fee-button.js b/components/fee-button.js index d490c4499..74e682ec8 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -37,10 +37,10 @@ export function postCommentBaseLineItems ({ baseCost = 1, comment = false, me }) } } -export function postCommentUseRemoteLineItems ({ parentId } = {}) { +export function postCommentUseRemoteLineItems ({ sub, parentId } = {}) { const query = parentId - ? gql`{ itemRepetition(parentId: "${parentId}") }` - : gql`{ itemRepetition }` + ? gql`{ itemRepetition(parentId: "${parentId}", sub: "${sub}") }` + : gql`{ itemRepetition(sub: "${sub}") }` return function useRemoteLineItems () { const [line, setLine] = useState({}) diff --git a/components/post.js b/components/post.js index 6245d1a16..787a78aec 100644 --- a/components/post.js +++ b/components/post.js @@ -150,7 +150,7 @@ export function PostForm ({ type, sub, children }) { return ( {children} diff --git a/components/reply.js b/components/reply.js index ec12d0a5f..b45996a72 100644 --- a/components/reply.js +++ b/components/reply.js @@ -71,7 +71,7 @@ export default forwardRef(function Reply ({ // no lag for itemRepetition if (!item.mine && me) { cache.updateQuery({ - query: gql`{ itemRepetition(parentId: "${parentId}") }` + query: gql`{ itemRepetition(parentId: "${parentId}", sub: "${sub?.name}") }` }, data => { return { itemRepetition: (data?.itemRepetition || 0) + 1 @@ -162,7 +162,7 @@ export default forwardRef(function Reply ({
(SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId")) + ) + AND "Item"."userId" = user_id + AND "bio" = 'f' + AND ("Sub"."name" IS NULL OR "Sub"."userId" <> user_id) + AND "Item".created_at > now_utc() - within; + + IF parent_id IS NULL THEN + RETURN repeats * 1000 + 333; + END IF; + + WITH RECURSIVE base AS ( + SELECT "Item".id, "Item"."parentId", "Item"."userId" + FROM "Item" + WHERE id = parent_id + AND "userId" = user_id + AND created_at > now_utc() - within + AND user_id <> (SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId") + UNION ALL + SELECT "Item".id, "Item"."parentId", "Item"."userId" + FROM base p + JOIN "Item" ON "Item".id = p."parentId" AND "Item"."userId" = p."userId" AND "Item".created_at > now_utc() - within) + SELECT count(*) INTO self_replies FROM base; + + RETURN repeats * 1000000 + self_replies * 1000 + 444; +END; +$$; From cdbd6151665df72bf452f869eeaa3ab02a826e7d Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Fri, 14 Mar 2025 17:37:27 +0000 Subject: [PATCH 4/8] remove debug values from sql --- prisma/migrations/20250314104901/migration.sql | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/prisma/migrations/20250314104901/migration.sql b/prisma/migrations/20250314104901/migration.sql index 57c33c999..d2d8d95f7 100644 --- a/prisma/migrations/20250314104901/migration.sql +++ b/prisma/migrations/20250314104901/migration.sql @@ -1,5 +1,4 @@ -- exclude posts in own subs from spam detection -DROP FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL, subName TEXT); CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL, subName TEXT) RETURNS INTEGER LANGUAGE plpgsql @@ -10,11 +9,11 @@ DECLARE BEGIN -- no fee escalation IF within = interval '0' THEN - RETURN 111; + RETURN 0; END IF; IF subName IS NOT NULL AND user_id = (SELECT "Sub"."userId" FROM "Sub" WHERE "Sub"."name" = subName) THEN - RETURN 222; + RETURN 0; END IF; SELECT count(*) INTO repeats @@ -31,7 +30,7 @@ BEGIN AND "Item".created_at > now_utc() - within; IF parent_id IS NULL THEN - RETURN repeats * 1000 + 333; + RETURN repeats; END IF; WITH RECURSIVE base AS ( @@ -47,6 +46,6 @@ BEGIN JOIN "Item" ON "Item".id = p."parentId" AND "Item"."userId" = p."userId" AND "Item".created_at > now_utc() - within) SELECT count(*) INTO self_replies FROM base; - RETURN repeats * 1000000 + self_replies * 1000 + 444; + RETURN repeats + self_replies; END; $$; From 55e7a0a699915ef971cdf4f59ffa8966c6bbc73e Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Fri, 14 Mar 2025 17:40:10 +0000 Subject: [PATCH 5/8] rename migration folder with details --- .../migrations/20250305175301/migration.sql | 47 ------------------- .../migration.sql | 0 2 files changed, 47 deletions(-) delete mode 100644 prisma/migrations/20250305175301/migration.sql rename prisma/migrations/{20250314104901 => 20250314104901_pass_sub_to_item_spam}/migration.sql (100%) diff --git a/prisma/migrations/20250305175301/migration.sql b/prisma/migrations/20250305175301/migration.sql deleted file mode 100644 index 30834a3ab..000000000 --- a/prisma/migrations/20250305175301/migration.sql +++ /dev/null @@ -1,47 +0,0 @@ --- exclude posts in own subs from spam detection -CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL) -RETURNS INTEGER -LANGUAGE plpgsql -AS $$ -DECLARE - repeats INTEGER; - self_replies INTEGER; -BEGIN - -- no fee escalation - IF within = interval '0' THEN - RETURN 0; - END IF; - - SELECT count(*) INTO repeats - FROM "Item" - LEFT JOIN "Sub" ON "Sub"."name" = "Item"."subName" - WHERE ( - (parent_id IS NULL AND "parentId" IS NULL) - OR - ("parentId" = parent_id AND user_id <> (SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId")) - ) - AND "Item"."userId" = user_id - AND "bio" = 'f' - AND ("Sub"."name" IS NULL OR "Sub"."userId" <> user_id) - AND "Item".created_at > now_utc() - within; - - IF parent_id IS NULL THEN - RETURN repeats; - END IF; - - WITH RECURSIVE base AS ( - SELECT "Item".id, "Item"."parentId", "Item"."userId" - FROM "Item" - WHERE id = parent_id - AND "userId" = user_id - AND created_at > now_utc() - within - AND user_id <> (SELECT i."userId" FROM "Item" i WHERE i.id = "Item"."rootId") - UNION ALL - SELECT "Item".id, "Item"."parentId", "Item"."userId" - FROM base p - JOIN "Item" ON "Item".id = p."parentId" AND "Item"."userId" = p."userId" AND "Item".created_at > now_utc() - within) - SELECT count(*) INTO self_replies FROM base; - - RETURN repeats + self_replies; -END; -$$; diff --git a/prisma/migrations/20250314104901/migration.sql b/prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql similarity index 100% rename from prisma/migrations/20250314104901/migration.sql rename to prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql From 9411bf5ffe17b7a27ed9503c2519c0fd99631a57 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Fri, 14 Mar 2025 17:43:03 +0000 Subject: [PATCH 6/8] fix lint: space before closing curly brace --- components/post.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/post.js b/components/post.js index 787a78aec..3fe4e5879 100644 --- a/components/post.js +++ b/components/post.js @@ -150,7 +150,7 @@ export function PostForm ({ type, sub, children }) { return ( {children} From d5ab6085a58c49f0f2ccf8f1ccb6bd5a64163463 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Tue, 18 Mar 2025 10:31:04 +0000 Subject: [PATCH 7/8] sub may be undefined, adjust SQL function parameter name --- components/fee-button.js | 9 +++++++-- components/post.js | 2 +- .../20250314104901_pass_sub_to_item_spam/migration.sql | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/components/fee-button.js b/components/fee-button.js index 74e682ec8..4fbd6c2c6 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -38,9 +38,14 @@ export function postCommentBaseLineItems ({ baseCost = 1, comment = false, me }) } export function postCommentUseRemoteLineItems ({ sub, parentId } = {}) { - const query = parentId + const query = parentId && sub ? gql`{ itemRepetition(parentId: "${parentId}", sub: "${sub}") }` - : gql`{ itemRepetition(sub: "${sub}") }` + : (parentId + ? gql`{ itemRepetition(parentId: "${parentId}") }` + : (sub + ? gql`{ itemRepetition(sub: "${sub}") }` + : gql`{ itemRepetition }` + )) return function useRemoteLineItems () { const [line, setLine] = useState({}) diff --git a/components/post.js b/components/post.js index 3fe4e5879..7be73d8fb 100644 --- a/components/post.js +++ b/components/post.js @@ -150,7 +150,7 @@ export function PostForm ({ type, sub, children }) { return ( {children} diff --git a/prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql b/prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql index d2d8d95f7..1d4930f5b 100644 --- a/prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql +++ b/prisma/migrations/20250314104901_pass_sub_to_item_spam/migration.sql @@ -1,5 +1,5 @@ -- exclude posts in own subs from spam detection -CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL, subName TEXT) +CREATE OR REPLACE FUNCTION item_spam(parent_id INTEGER, user_id INTEGER, within INTERVAL, sub_name TEXT) RETURNS INTEGER LANGUAGE plpgsql AS $$ @@ -12,7 +12,7 @@ BEGIN RETURN 0; END IF; - IF subName IS NOT NULL AND user_id = (SELECT "Sub"."userId" FROM "Sub" WHERE "Sub"."name" = subName) THEN + IF sub_name IS NOT NULL AND user_id = (SELECT "Sub"."userId" FROM "Sub" WHERE "Sub"."name" = sub_name) THEN RETURN 0; END IF; From 52d6b9208ecb0f4f5c40f41a0aa996c4f7e5d968 Mon Sep 17 00:00:00 2001 From: Scroogey-SN Date: Tue, 18 Mar 2025 10:37:34 +0000 Subject: [PATCH 8/8] fix lint: indent --- components/fee-button.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/fee-button.js b/components/fee-button.js index 4fbd6c2c6..98b56c7b9 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -41,11 +41,11 @@ export function postCommentUseRemoteLineItems ({ sub, parentId } = {}) { const query = parentId && sub ? gql`{ itemRepetition(parentId: "${parentId}", sub: "${sub}") }` : (parentId - ? gql`{ itemRepetition(parentId: "${parentId}") }` - : (sub - ? gql`{ itemRepetition(sub: "${sub}") }` - : gql`{ itemRepetition }` - )) + ? gql`{ itemRepetition(parentId: "${parentId}") }` + : (sub + ? gql`{ itemRepetition(sub: "${sub}") }` + : gql`{ itemRepetition }` + )) return function useRemoteLineItems () { const [line, setLine] = useState({})