diff --git a/api/resolvers/item.js b/api/resolvers/item.js index b10fe33f..bbf62521 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -920,30 +920,6 @@ export default { return (msats && msatsToSats(msats)) || 0 }, - bountyPaidTo: async (item, args, { models }) => { - return [] - // if (!item.bounty) { - // return [] - // } - - // const paidTo = await models.$queryRaw` - // SELECT "Item"."id" - // FROM "ItemAct" - // JOIN "Item" ON "ItemAct"."itemId" = "Item"."id" - // WHERE "ItemAct"."userId" = ${item.userId} - // AND "Item"."rootId" = ${item.id} - // AND "Item"."userId" <> ${item.userId} - // AND act IN ('TIP', 'FEE') - // GROUP BY "Item"."id" - // HAVING coalesce(sum("ItemAct"."msats"), 0) >= ${item.bounty * 1000} - // ` - - // if (paidTo.length === 0) { - // return [] - // } - - // return paidTo.map(i => i.id) - }, meDontLike: async (item, args, { me, models }) => { if (!me) return false @@ -1139,7 +1115,7 @@ export const SELECT = "Item".company, "Item".location, "Item".remote, "Item"."deletedAt", "Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".msats, "Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes", - "Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", ltree2text("Item"."path") AS "path"` + "Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", "Item"."bountyPaidTo", ltree2text("Item"."path") AS "path"` async function newTimedOrderByWeightedSats (me, models, num) { return ` diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index 3369e653..f5e9f1b5 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -90,7 +90,7 @@ export default gql` mine: Boolean! boost: Int! bounty: Int - bountyPaidTo: [Int]! + bountyPaidTo: [Int] sats: Int! commentSats: Int! lastCommentAt: String diff --git a/components/pay-bounty.js b/components/pay-bounty.js index 1373d990..cc2325bc 100644 --- a/components/pay-bounty.js +++ b/components/pay-bounty.js @@ -51,7 +51,7 @@ export default function PayBounty ({ children, item }) { id: `Item:${item.root.id}`, fields: { bountyPaidTo (existingPaidTo = []) { - return [...existingPaidTo, Number(item.id)] + return [...(existingPaidTo || []), Number(item.id)] } } }) diff --git a/prisma/migrations/20230126213820_bounty_paid_to/migration.sql b/prisma/migrations/20230126213820_bounty_paid_to/migration.sql new file mode 100644 index 00000000..4d0e345c --- /dev/null +++ b/prisma/migrations/20230126213820_bounty_paid_to/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Item" ADD COLUMN "bountyPaidTo" INTEGER[]; diff --git a/prisma/migrations/20230126214014_bounty_paid_denorm/migration.sql b/prisma/migrations/20230126214014_bounty_paid_denorm/migration.sql new file mode 100644 index 00000000..ca972d9c --- /dev/null +++ b/prisma/migrations/20230126214014_bounty_paid_denorm/migration.sql @@ -0,0 +1,123 @@ +-- This is an empty migration. + +-- This is an empty migration. +CREATE OR REPLACE FUNCTION bounty_paid_after_act(item_id INTEGER, user_id INTEGER) +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +DECLARE + root_id INTEGER; + item_bounty INTEGER; + sats_paid INTEGER; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + -- get root item + SELECT "rootId" INTO root_id FROM "Item" WHERE id = item_id; + + -- check if root item is 1. a bounty, 2. actor is the OP, 3. hasn't paid yet + SELECT bounty + INTO item_bounty + FROM "Item" + WHERE id = root_id + AND "userId" = user_id + AND ("bountyPaidTo" IS NULL OR item_id <> any ("bountyPaidTo")); + + -- if it is get the bounty amount + IF item_bounty IS NOT NULL THEN + -- check if the cumulative sats sent to this item by user_id is >= to bounty + SELECT coalesce(sum("ItemAct"."msats"), 0)/1000 + INTO sats_paid + FROM "ItemAct" + WHERE "ItemAct"."userId" = user_id + AND "ItemAct"."itemId" = item_id + AND "ItemAct".act IN ('TIP','FEE'); + IF sats_paid >= item_bounty THEN + UPDATE "Item" + SET "bountyPaidTo" = array_append("bountyPaidTo", item_id) + WHERE id = root_id; + END IF; + END IF; + + RETURN 0; +END; +$$; + +-- This is an empty migration. +CREATE OR REPLACE FUNCTION item_act(item_id INTEGER, user_id INTEGER, act "ItemActType", act_sats INTEGER) +RETURNS INTEGER +LANGUAGE plpgsql +AS $$ +DECLARE + user_msats BIGINT; + act_msats BIGINT; + fee_msats BIGINT; + item_act_id INTEGER; + referrer_id INTEGER; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + act_msats := act_sats * 1000; + SELECT msats, "referrerId" INTO user_msats, referrer_id FROM users WHERE id = user_id; + IF act_msats > user_msats THEN + RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS'; + END IF; + + -- deduct msats from actor + UPDATE users SET msats = msats - act_msats WHERE id = user_id; + + IF act = 'VOTE' THEN + RAISE EXCEPTION 'SN_UNSUPPORTED'; + END IF; + + IF act = 'TIP' THEN + -- call to influence weightedVotes ... we need to do this before we record the acts because + -- the priors acts are taken into account + PERFORM weighted_votes_after_tip(item_id, user_id, act_sats); + + -- take 10% and insert as FEE + fee_msats := CEIL(act_msats * 0.1); + act_msats := act_msats - fee_msats; + + INSERT INTO "ItemAct" (msats, "itemId", "userId", act, created_at, updated_at) + VALUES (fee_msats, item_id, user_id, 'FEE', now_utc(), now_utc()) + RETURNING id INTO item_act_id; + + -- add sats to actee's balance and stacked count + UPDATE users + SET msats = msats + act_msats, "stackedMsats" = "stackedMsats" + act_msats + WHERE id = (SELECT COALESCE("fwdUserId", "userId") FROM "Item" WHERE id = item_id) + RETURNING "referrerId" INTO referrer_id; + + -- leave the rest as a tip + INSERT INTO "ItemAct" (msats, "itemId", "userId", act, created_at, updated_at) + VALUES (act_msats, item_id, user_id, 'TIP', now_utc(), now_utc()); + + -- call to denormalize sats and commentSats + PERFORM sats_after_tip(item_id, user_id, act_msats + fee_msats); + -- denormalize bounty paid + PERFORM bounty_paid_after_act(item_id, user_id); + ELSE -- BOOST, POLL, DONT_LIKE_THIS, STREAM + -- call to influence if DONT_LIKE_THIS weightedDownVotes + IF act = 'DONT_LIKE_THIS' THEN + -- make sure they haven't done this before + IF EXISTS (SELECT 1 FROM "ItemAct" WHERE "itemId" = item_id AND "userId" = user_id AND "ItemAct".act = 'DONT_LIKE_THIS') THEN + RAISE EXCEPTION 'SN_DUPLICATE'; + END IF; + + PERFORM weighted_downvotes_after_act(item_id, user_id, act_sats); + END IF; + + INSERT INTO "ItemAct" (msats, "itemId", "userId", act, created_at, updated_at) + VALUES (act_msats, item_id, user_id, act, now_utc(), now_utc()) + RETURNING id INTO item_act_id; + END IF; + + -- they have a referrer and the referrer isn't the one tipping them + IF referrer_id IS NOT NULL AND user_id <> referrer_id THEN + PERFORM referral_act(referrer_id, item_act_id); + END IF; + + RETURN 0; +END; +$$; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 91fc9096..6a2a44e1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -231,7 +231,6 @@ model Item { pin Pin? @relation(fields: [pinId], references: [id]) pinId Int? boost Int @default(0) - bounty Int? uploadId Int? upload Upload? paidImgLink Boolean @default(false) @@ -240,6 +239,10 @@ model Item { otsHash String? otsFile Bytes? + // bounties + bounty Int? + bountyPaidTo Int[] + // is free post or bio freebie Boolean @default(false) bio Boolean @default(false)