Skip to content

Commit

Permalink
handle uppercase addl fees for edit item
Browse files Browse the repository at this point in the history
  • Loading branch information
SatsAllDay committed Oct 29, 2023
1 parent d74d92b commit a308b70
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 37 deletions.
31 changes: 27 additions & 4 deletions api/resolvers/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
)

Expand Down Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions api/typeDefs/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export default gql`
parentOtsHash: String
forwards: [ItemForward]
imgproxyUrls: JSONObject
upperTitleFeePaid: Boolean
}
input ItemForwardInput {
Expand Down
1 change: 1 addition & 0 deletions components/bounty-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export function BountyForm ({
text='save'
ChildButton={SubmitButton}
variant='secondary'
hasPaidUpperTitleFee={item.upperTitleFeePaid}
/>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions components/discussion-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export function DiscussionForm ({
<CancelButton />
<EditFeeButton
paidSats={item.meSats}
hasPaidUpperTitleFee={item.upperTitleFeePaid}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>
Expand Down
33 changes: 6 additions & 27 deletions components/fee-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Table className={styles.receipt} borderless size='sm'>
<tbody>
{addImgLink &&
<>
<tr>
<td>{numWithUnits(paidSats, { abbreviate: false })}</td>
<td align='right' className='font-weight-light'>{parentId ? 'reply' : 'post'} fee</td>
</tr>
<tr>
<td>x 10</td>
<td align='right' className='font-weight-light'>image/link fee</td>
</tr>
<tr>
<td>- {numWithUnits(paidSats, { abbreviate: false })}</td>
<td align='right' className='font-weight-light'>already paid</td>
</tr>
</>}
{addTooManyCapitalsMultiplier > 1 &&
<>
<tr>
Expand Down Expand Up @@ -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)

Expand All @@ -186,7 +165,7 @@ export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton,
</ActionTooltip>
{cost > 0 && show &&
<Info>
<EditReceipt paidSats={paidSats} addImgLink={addImgLink} cost={cost} parentId={parentId} boost={boost} addTooManyCapitalsMultiplier={tooManyCapitals && !hadTooManyCapitals ? UPPER_CHARS_TITLE_FEE_MULT - 1 : 1} />
<EditReceipt paidSats={paidSats} cost={cost} parentId={parentId} boost={boost} addTooManyCapitalsMultiplier={tooManyCapitals && !hasPaidUpperTitleFee ? UPPER_CHARS_TITLE_FEE_MULT - 1 : 1} />
</Info>}
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion components/link-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
<div className='d-flex'>
<CancelButton />
<EditFeeButton
paidSats={item.meSats}
paidSats={item.meSats} hasPaidUpperTitleFee={item.upperTitleFeePaid}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/poll-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
<div className='d-flex'>
<CancelButton />
<EditFeeButton
paidSats={item.meSats}
paidSats={item.meSats} hasPaidUpperTitleFee={item.upperTitleFeePaid}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions fragments/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const ITEM_FIELDS = gql`
uploadId
mine
imgproxyUrls
upperTitleFeePaid
}`

export const ITEM_FULL_FIELDS = gql`
Expand Down
2 changes: 1 addition & 1 deletion pages/[name]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function BioForm ({ handleDone, bio }) {
<CancelButton onClick={handleDone} />
{bio?.text
? <EditFeeButton
paidSats={bio?.meSats}
paidSats={bio?.meSats} hasPaidUpperTitleFee={bio?.upperTitleFeePaid}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
: <FeeButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
-- AlterTable
ALTER TABLE "Item" ADD COLUMN "upperTitleFeePaid" BOOLEAN NOT NULL DEFAULT false;

DROP FUNCTION IF EXISTS create_item(JSONB, JSONB, JSONB, INTERVAL);
-- support uppercase chars in title fee multiplier
CREATE OR REPLACE FUNCTION create_item(
jitem JSONB, forward JSONB, poll_options JSONB, spam_within INTERVAL, upper_title_fee_mult INTEGER)
Expand Down Expand Up @@ -54,10 +58,10 @@ BEGIN
FROM jsonb_object_keys(jsonb_strip_nulls(jitem)) k(key);
-- insert the item
EXECUTE format($fmt$
INSERT INTO "Item" (%s, "weightedDownVotes", freebie)
SELECT %1$s, %L, %L
INSERT INTO "Item" (%s, "weightedDownVotes", freebie, "upperTitleFeePaid")
SELECT %1$s, %L, %L, %L
FROM jsonb_populate_record(NULL::"Item", %L) RETURNING *
$fmt$, select_clause, med_votes, freebie, jitem) INTO item;
$fmt$, select_clause, med_votes, freebie, upper_title_fee_mult > 1, jitem) INTO item;

INSERT INTO "ItemForward" ("itemId", "userId", "pct")
SELECT item.id, "userId", "pct" FROM jsonb_populate_recordset(NULL::"ItemForward", forward);
Expand Down Expand Up @@ -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;
$$;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit a308b70

Please sign in to comment.