diff --git a/api/resolvers/item.js b/api/resolvers/item.js
index aca5dbeba..78ce17060 100644
--- a/api/resolvers/item.js
+++ b/api/resolvers/item.js
@@ -1089,10 +1089,32 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
item = { subName, userId: me.id, ...item }
const fwdUsers = await getForwardUsers(models, forward)
-
+ const hasPaidUpperTitleFee = old.upperTitleFeePaid
+ const titleUpperMult = !hasPaidUpperTitleFee && getUppercaseCharCountInTitle(item) > MAX_FREE_UPPER_CHARS_TITLE ? UPPER_CHARS_TITLE_FEE_MULT : 1
+ let additionalFeeMsats = 0
+ if (titleUpperMult > 1) {
+ const { _sum: { msats: paidMsats } } = await models.itemAct.aggregate({
+ _sum: {
+ msats: true
+ },
+ where: {
+ itemId: Number(item.id),
+ userId: me.id,
+ act: 'FEE'
+ }
+ })
+ // If the original post was a freebie, that doesn't mean this edit should be free
+ if (Number(paidMsats) === 0) {
+ additionalFeeMsats = titleUpperMult // implicit 1 sat fee, since the post was a freebie
+ item.freebie = false
+ } else {
+ additionalFeeMsats += (titleUpperMult - 1) * Number(paidMsats)
+ }
+ item.upperTitleFeePaid = true
+ }
item = await serializeInvoicable(
- models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB) AS "Item"`,
- JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options)),
+ models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB, $4::BIGINT) AS "Item"`,
+ JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), additionalFeeMsats),
{ models, lnd, hash, hmac, me }
)
@@ -1180,7 +1202,8 @@ export const SELECT =
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item".boost, "Item".msats,
"Item".ncomments, "Item"."commentMsats", "Item"."lastCommentAt", "Item"."weightedVotes",
"Item"."weightedDownVotes", "Item".freebie, "Item"."otsHash", "Item"."bountyPaidTo",
- ltree2text("Item"."path") AS "path", "Item"."weightedComments", "Item"."imgproxyUrls"`
+ ltree2text("Item"."path") AS "path", "Item"."weightedComments", "Item"."imgproxyUrls",
+ "Item"."upperTitleFeePaid"`
function topOrderByWeightedSats (me, models) {
return `ORDER BY ${orderByNumerator(models)} DESC NULLS LAST, "Item".id DESC`
diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js
index 1b10eaf02..618316039 100644
--- a/api/typeDefs/item.js
+++ b/api/typeDefs/item.js
@@ -115,6 +115,7 @@ export default gql`
parentOtsHash: String
forwards: [ItemForward]
imgproxyUrls: JSONObject
+ upperTitleFeePaid: Boolean
}
input ItemForwardInput {
diff --git a/components/bounty-form.js b/components/bounty-form.js
index f1b14bf46..8aee2dfe9 100644
--- a/components/bounty-form.js
+++ b/components/bounty-form.js
@@ -144,6 +144,7 @@ export function BountyForm ({
text='save'
ChildButton={SubmitButton}
variant='secondary'
+ hasPaidUpperTitleFee={item.upperTitleFeePaid}
/>
)
diff --git a/components/discussion-form.js b/components/discussion-form.js
index caf06a7b8..38f49d078 100644
--- a/components/discussion-form.js
+++ b/components/discussion-form.js
@@ -146,6 +146,7 @@ export function DiscussionForm ({
diff --git a/components/fee-button.js b/components/fee-button.js
index 3ebeaaaf1..99f803ed6 100644
--- a/components/fee-button.js
+++ b/components/fee-button.js
@@ -107,25 +107,10 @@ export default function FeeButton ({ parentId, hasImgLink, baseFee, ChildButton,
)
}
-function EditReceipt ({ cost, paidSats, addImgLink, boost, parentId, addTooManyCapitalsMultiplier }) {
+function EditReceipt ({ cost, paidSats, boost, parentId, addTooManyCapitalsMultiplier }) {
return (
- {addImgLink &&
- <>
-
- {numWithUnits(paidSats, { abbreviate: false })} |
- {parentId ? 'reply' : 'post'} fee |
-
-
- x 10 |
- image/link fee |
-
-
- - {numWithUnits(paidSats, { abbreviate: false })} |
- already paid |
-
- >}
{addTooManyCapitalsMultiplier > 1 &&
<>
@@ -157,20 +142,14 @@ function EditReceipt ({ cost, paidSats, addImgLink, boost, parentId, addTooManyC
)
}
-export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton, variant, text, alwaysShow, parentId }) {
+export function EditFeeButton ({ paidSats, ChildButton, variant, text, alwaysShow, parentId, hasPaidUpperTitleFee }) {
const formik = useFormikContext()
const boost = (formik?.values?.boost || 0) - (formik?.initialValues?.boost || 0)
- const addImgLink = hasImgLink && !hadImgLink
const tooManyCapitals = getUppercaseCharCountInTitle(formik?.values) > MAX_FREE_UPPER_CHARS_TITLE
- const hadTooManyCapitals = getUppercaseCharCountInTitle(formik?.initialValues) > MAX_FREE_UPPER_CHARS_TITLE
- let cost = (addImgLink ? paidSats * 9 : 0)
- if (tooManyCapitals && !hadTooManyCapitals) {
+ let cost = 0
+ if (tooManyCapitals && !hasPaidUpperTitleFee) {
// only apply cost if the capital letters threshold is newly exceeded
- if (cost === 0) {
- cost = paidSats * (UPPER_CHARS_TITLE_FEE_MULT - 1)
- } else {
- cost *= (UPPER_CHARS_TITLE_FEE_MULT - 1)
- }
+ cost = paidSats * (UPPER_CHARS_TITLE_FEE_MULT - 1)
}
cost += Number(boost)
@@ -186,7 +165,7 @@ export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton,
{cost > 0 && show &&
-
+
}
)
diff --git a/components/link-form.js b/components/link-form.js
index b3b947dc8..f880e548c 100644
--- a/components/link-form.js
+++ b/components/link-form.js
@@ -199,7 +199,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
diff --git a/components/poll-form.js b/components/poll-form.js
index 6e6788782..4eb656f89 100644
--- a/components/poll-form.js
+++ b/components/poll-form.js
@@ -108,7 +108,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
diff --git a/fragments/items.js b/fragments/items.js
index bb5d4f024..2be5f96c8 100644
--- a/fragments/items.js
+++ b/fragments/items.js
@@ -46,6 +46,7 @@ export const ITEM_FIELDS = gql`
uploadId
mine
imgproxyUrls
+ upperTitleFeePaid
}`
export const ITEM_FULL_FIELDS = gql`
diff --git a/pages/[name]/index.js b/pages/[name]/index.js
index a86ae4cdf..f7255524d 100644
--- a/pages/[name]/index.js
+++ b/pages/[name]/index.js
@@ -71,7 +71,7 @@ export function BioForm ({ handleDone, bio }) {
{bio?.text
?
: 1, jitem) INTO item;
INSERT INTO "ItemForward" ("itemId", "userId", "pct")
SELECT item.id, "userId", "pct" FROM jsonb_populate_recordset(NULL::"ItemForward", forward);
@@ -102,3 +106,86 @@ BEGIN
RETURN item;
END;
$$;
+
+DROP FUNCTION IF EXISTS update_item(JSONB, JSONB, JSONB);
+-- support an additional fee parameter to incur on edits
+CREATE OR REPLACE FUNCTION update_item(
+ jitem JSONB, forward JSONB, poll_options JSONB, additional_fee_msats BIGINT)
+RETURNS "Item"
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ user_msats INTEGER;
+ item "Item";
+ select_clause TEXT;
+BEGIN
+ PERFORM ASSERT_SERIALIZED();
+
+ item := jsonb_populate_record(NULL::"Item", jitem);
+
+ SELECT msats INTO user_msats FROM users WHERE id = item."userId";
+ IF additional_fee_msats > 0 AND additional_fee_msats > user_msats THEN
+ RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
+ END IF;
+
+ UPDATE users SET msats = user_msats - additional_fee_msats WHERE id = item."userId";
+
+ INSERT INTO "ItemAct" (msats, "itemId", "userId", act)
+ VALUES (additional_fee_msats, item.id, item."userId", 'FEE');
+
+ IF item.boost > 0 THEN
+ UPDATE "Item" SET boost = boost + item.boost WHERE id = item.id;
+ PERFORM item_act(item.id, item."userId", 'BOOST', item.boost);
+ END IF;
+
+ IF item.status IS NOT NULL THEN
+ UPDATE "Item" SET "statusUpdatedAt" = now_utc()
+ WHERE id = item.id AND status <> item.status;
+ END IF;
+
+ SELECT string_agg(quote_ident(key), ',') INTO select_clause
+ FROM jsonb_object_keys(jsonb_strip_nulls(jitem)) k(key)
+ WHERE key <> 'boost';
+
+ EXECUTE format($fmt$
+ UPDATE "Item" SET (%s) = (
+ SELECT %1$s
+ FROM jsonb_populate_record(NULL::"Item", %L)
+ ) WHERE id = %L RETURNING *
+ $fmt$, select_clause, jitem, item.id) INTO item;
+
+ -- Delete any old thread subs if the user is no longer a fwd recipient
+ DELETE FROM "ThreadSubscription"
+ WHERE "itemId" = item.id
+ -- they aren't in the new forward list
+ AND NOT EXISTS (SELECT 1 FROM jsonb_populate_recordset(NULL::"ItemForward", forward) as nf WHERE "ThreadSubscription"."userId" = nf."userId")
+ -- and they are in the old forward list
+ AND EXISTS (SELECT 1 FROM "ItemForward" WHERE "ItemForward"."itemId" = item.id AND "ItemForward"."userId" = "ThreadSubscription"."userId" );
+
+ -- Automatically subscribe any new forward recipients to the post
+ INSERT INTO "ThreadSubscription" ("itemId", "userId")
+ SELECT item.id, "userId" FROM jsonb_populate_recordset(NULL::"ItemForward", forward)
+ EXCEPT
+ SELECT item.id, "userId" FROM "ItemForward" WHERE "itemId" = item.id;
+
+ -- Delete all old forward entries, to recreate in next command
+ DELETE FROM "ItemForward" WHERE "itemId" = item.id;
+
+ INSERT INTO "ItemForward" ("itemId", "userId", "pct")
+ SELECT item.id, "userId", "pct" FROM jsonb_populate_recordset(NULL::"ItemForward", forward);
+
+ INSERT INTO "PollOption" ("itemId", "option")
+ SELECT item.id, "option" FROM jsonb_array_elements_text(poll_options) o("option");
+
+ -- if this is a job
+ IF item."maxBid" IS NOT NULL THEN
+ PERFORM run_auction(item.id);
+ END IF;
+
+ -- schedule imgproxy job
+ INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter)
+ VALUES ('imgproxy', jsonb_build_object('id', item.id), 21, true, now() + interval '5 seconds');
+
+ RETURN item;
+END;
+$$;
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 3cf4506ad..0a1578bd2 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -286,6 +286,7 @@ model Item {
bountyPaidTo Int[]
upvotes Int @default(0)
weightedComments Float @default(0)
+ upperTitleFeePaid Boolean @default(false)
Bookmark Bookmark[]
parent Item? @relation("ParentChildren", fields: [parentId], references: [id])
children Item[] @relation("ParentChildren")