diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 54ce8d69..779330af 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -8,8 +8,8 @@ import domino from 'domino' import { ITEM_SPAM_INTERVAL, ITEM_FILTER_THRESHOLD, DONT_LIKE_THIS_COST, COMMENT_DEPTH_LIMIT, COMMENT_TYPE_QUERY, - ANON_COMMENT_FEE, ANON_USER_ID, ANON_POST_FEE, ANON_ITEM_SPAM_INTERVAL, POLL_COST, - ITEM_ALLOW_EDITS, GLOBAL_SEED + ANON_USER_ID, ANON_ITEM_SPAM_INTERVAL, POLL_COST, + ITEM_ALLOW_EDITS, GLOBAL_SEED, ANON_FEE_MULTIPLIER } from '../../lib/constants' import { msatsToSats } from '../../lib/format' import { parse } from 'tldts' @@ -476,7 +476,8 @@ export default { FROM "Item" ${whereClause( subClause(sub, 3, 'Item', true), - muteClause(me))} + muteClause(me), + await filterClause(me, models, type))} ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC OFFSET $1 LIMIT $2`, @@ -1126,7 +1127,17 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo const uploadIds = uploadIdsFromText(item.text, { models }) const { fees: imgFees } = await imageFeesInfo(uploadIds, { models, me }) - const enforceFee = (me ? undefined : (item.parentId ? ANON_COMMENT_FEE : (ANON_POST_FEE + (item.boost || 0)))) + imgFees + let enforceFee + if (!me) { + if (item.parentId) { + enforceFee = ANON_FEE_MULTIPLIER + } else { + const sub = await models.sub.findUnique({ where: { name: item.subName } }) + enforceFee = sub.baseCost * ANON_FEE_MULTIPLIER + (item.boost || 0) + } + enforceFee += imgFees + } + item = await serializeInvoicable( models.$queryRawUnsafe( `${SELECT} FROM create_item($1::JSONB, $2::JSONB, $3::JSONB, '${spamInterval}'::INTERVAL, $4::INTEGER[]) AS "Item"`, diff --git a/components/fee-button.js b/components/fee-button.js index b6f6599e..502724da 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -4,7 +4,7 @@ import ActionTooltip from './action-tooltip' import Info from './info' import styles from './fee-button.module.css' import { gql, useQuery } from '@apollo/client' -import { SSR } from '../lib/constants' +import { ANON_FEE_MULTIPLIER, SSR } from '../lib/constants' import { numWithUnits } from '../lib/format' import { useMe } from './me' import AnonIcon from '../svgs/spy-fill.svg' @@ -15,15 +15,13 @@ import { SubmitButton } from './form' const FeeButtonContext = createContext() export function postCommentBaseLineItems ({ baseCost = 1, comment = false, allowFreebies = true, me }) { - // XXX this doesn't match the logic on the server but it has the same - // result on fees ... will need to change the server logic to match const anonCharge = me ? {} : { anonCharge: { - term: 'x 100', + term: `x ${ANON_FEE_MULTIPLIER}`, label: 'anon mult', - modifier: (cost) => cost * 100 + modifier: (cost) => cost * ANON_FEE_MULTIPLIER } } return { diff --git a/lib/constants.js b/lib/constants.js index 1d95ea1e..cfa9d35f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -50,8 +50,7 @@ export const OLD_ITEM_DAYS = 3 export const ANON_USER_ID = 27 export const DELETE_USER_ID = 106 export const AD_USER_ID = 9 -export const ANON_POST_FEE = 1000 -export const ANON_COMMENT_FEE = 100 +export const ANON_FEE_MULTIPLIER = 100 export const SSR = typeof window === 'undefined' export const MAX_FORWARDS = 5 export const LNURLP_COMMENT_MAX_LENGTH = 1000 diff --git a/pages/territory.js b/pages/territory.js index e2c322b7..ffcb45fa 100644 --- a/pages/territory.js +++ b/pages/territory.js @@ -2,7 +2,7 @@ import { getGetServerSideProps } from '../api/ssrApollo' import { CenterLayout } from '../components/layout' import TerritoryForm from '../components/territory-form' -export const getServerSideProps = getGetServerSideProps({}) +export const getServerSideProps = getGetServerSideProps({ authRequired: true }) export default function TerritoryPage () { return ( diff --git a/prisma/migrations/20231210222855_anon_create_item/migration.sql b/prisma/migrations/20231210222855_anon_create_item/migration.sql new file mode 100644 index 00000000..b5a2fc8b --- /dev/null +++ b/prisma/migrations/20231210222855_anon_create_item/migration.sql @@ -0,0 +1,136 @@ +-- no freebies in jobs +update "Sub" set "allowFreebies" = false where "name" = 'jobs'; + +CREATE OR REPLACE FUNCTION create_item( + jitem JSONB, forward JSONB, poll_options JSONB, spam_within INTERVAL, upload_ids INTEGER[]) +RETURNS "Item" +LANGUAGE plpgsql +AS $$ +DECLARE + user_msats BIGINT; + cost_msats BIGINT := 1000; + base_cost_msats BIGINT := 1000; + freebie BOOLEAN; + allow_freebies BOOLEAN := true; + item "Item"; + med_votes FLOAT; + select_clause TEXT; +BEGIN + PERFORM ASSERT_SERIALIZED(); + + -- access fields with appropriate types + item := jsonb_populate_record(NULL::"Item", jitem); + + SELECT msats INTO user_msats FROM users WHERE id = item."userId"; + + -- if this is a post, get the base cost of the sub + IF item."parentId" IS NULL AND item."subName" IS NOT NULL THEN + SELECT "baseCost" * 1000, "baseCost" * 1000, "allowFreebies" + INTO base_cost_msats, cost_msats, allow_freebies + FROM "Sub" + WHERE name = item."subName"; + END IF; + + IF item."maxBid" IS NULL THEN + -- spam multiplier + cost_msats := cost_msats * POWER(10, item_spam(item."parentId", item."userId", spam_within)); + IF item."userId" = 27 THEN + -- anon multiplier + cost_msats := cost_msats * 100; + END IF; + END IF; + + -- add image fees + IF upload_ids IS NOT NULL THEN + cost_msats := cost_msats + (SELECT "nUnpaid" * "imageFeeMsats" FROM image_fees_info(item."userId", upload_ids)); + UPDATE "Upload" SET paid = 't' WHERE id = ANY(upload_ids); + END IF; + + -- it's only a freebie if it's no greater than the base cost, they have less than the cost, and boost = 0 + freebie := allow_freebies + AND (cost_msats <= base_cost_msats) + AND (user_msats < cost_msats) + AND (item.boost IS NULL OR item.boost = 0) + AND item."userId" <> 27; + + IF NOT freebie AND cost_msats > user_msats THEN + RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS'; + END IF; + + -- get this user's median item score + SELECT COALESCE( + percentile_cont(0.5) WITHIN GROUP( + ORDER BY "weightedVotes" - "weightedDownVotes"), 0) + INTO med_votes FROM "Item" WHERE "userId" = item."userId"; + + -- if their median votes are positive, start at 0 + -- if the median votes are negative, start their post with that many down votes + -- basically: if their median post is bad, presume this post is too + -- addendum: if they're an anon poster, always start at 0 + IF med_votes >= 0 OR item."userId" = 27 THEN + med_votes := 0; + ELSE + med_votes := ABS(med_votes); + END IF; + + -- there's no great way to set default column values when using json_populate_record + -- so we need to only select fields with non-null values that way when func input + -- does not include a value, the default value is used instead of null + SELECT string_agg(quote_ident(key), ',') INTO select_clause + 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 + FROM jsonb_populate_record(NULL::"Item", %L) RETURNING * + $fmt$, select_clause, med_votes, freebie, jitem) INTO item; + + INSERT INTO "ItemForward" ("itemId", "userId", "pct") + SELECT item.id, "userId", "pct" FROM jsonb_populate_recordset(NULL::"ItemForward", forward); + + -- Automatically subscribe to one's own posts + INSERT INTO "ThreadSubscription" ("itemId", "userId") + VALUES (item.id, item."userId"); + + -- Automatically subscribe forward recipients to the new post + INSERT INTO "ThreadSubscription" ("itemId", "userId") + SELECT item.id, "userId" 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 NOT freebie THEN + UPDATE users SET msats = msats - cost_msats WHERE id = item."userId"; + + INSERT INTO "ItemAct" (msats, "itemId", "userId", act) + VALUES (cost_msats, item.id, item."userId", 'FEE'); + END IF; + + -- if this item has boost + IF item.boost > 0 THEN + PERFORM item_act(item.id, item."userId", 'BOOST', item.boost); + END IF; + + -- if this is a job + IF item."maxBid" IS NOT NULL THEN + PERFORM run_auction(item.id); + END IF; + + -- if this is a bio + IF item.bio THEN + UPDATE users SET "bioId" = item.id WHERE id = item."userId"; + END IF; + + -- record attachments + IF upload_ids IS NOT NULL THEN + INSERT INTO "ItemUpload" ("itemId", "uploadId") + SELECT item.id, * FROM UNNEST(upload_ids); + 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