From 1b84f76a275cb8cf64191b6b6edfcd8a1dbad562 Mon Sep 17 00:00:00 2001 From: keyan Date: Thu, 3 Mar 2022 12:56:02 -0600 Subject: [PATCH] make jobs expensive, priced based ranking rather than auction --- api/resolvers/item.js | 26 +++++-------- api/typeDefs/sub.js | 1 + components/job-form.js | 39 +++---------------- fragments/items.js | 1 + fragments/subs.js | 1 + .../20220303170859_exp_auction/migration.sql | 35 +++++++++++++++++ prisma/schema.prisma | 1 + worker/auction.js | 33 +++------------- 8 files changed, 59 insertions(+), 78 deletions(-) create mode 100644 prisma/migrations/20220303170859_exp_auction/migration.sql diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 75ecfc02..7a2cc7b1 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -147,7 +147,7 @@ export default { AND "pinId" IS NULL ${subClause(3)} AND status = 'ACTIVE' - ORDER BY "maxBid" / 1000 DESC, created_at ASC + ORDER BY "maxBid" DESC, created_at ASC OFFSET $2 LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub) break @@ -409,27 +409,15 @@ export default { } }, auctionPosition: async (parent, { id, sub, bid }, { models }) => { - // count items that have a bid gte to the current bid + 1000 or + // count items that have a bid gte to the current bid or // gte current bid and older const where = { where: { subName: sub, status: 'ACTIVE', - OR: [{ - maxBid: { - gte: bid + 1000 - } - }, { - AND: [{ - maxBid: { - gte: bid - } - }, { - createdAt: { - lt: new Date() - } - }] - }] + maxBid: { + gte: bid + } } } @@ -532,6 +520,10 @@ export default { throw new UserInputError(`bid must be at least ${fullSub.baseCost}`, { argumentName: 'maxBid' }) } + if (maxBid % fullSub.deltaCost !== 0) { + throw new UserInputError(`bid must be a multiple of ${fullSub.deltaCost}`, { argumentName: 'maxBid' }) + } + const checkSats = async () => { // check if the user has the funds to run for the first minute const minuteMsats = maxBid * 5 / 216 diff --git a/api/typeDefs/sub.js b/api/typeDefs/sub.js index 3cc115ef..320f9dc3 100644 --- a/api/typeDefs/sub.js +++ b/api/typeDefs/sub.js @@ -12,5 +12,6 @@ export default gql` postTypes: [String!]! rankingType: String! baseCost: Int! + deltaCost: Int! } ` diff --git a/components/job-form.js b/components/job-form.js index ac8b9b4e..0a697a4a 100644 --- a/components/job-form.js +++ b/components/job-form.js @@ -58,9 +58,9 @@ export default function JobForm ({ item, sub }) { .or([Yup.string().email(), Yup.string().url()], 'invalid url or email') .required('Required'), maxBid: Yup.number('must be number') - .integer('must be integer').min(sub.baseCost, 'must be at least 10000') + .integer('must be integer').min(sub.baseCost, `must be at least ${sub.baseCost}`) .max(100000000, 'must be less than 100000000') - .test('multiple', 'must be a multiple of 1000 sats', (val) => val % 1000 === 0) + .test('multiple', `must be a multiple of ${sub.deltaCost} sats`, (val) => val % sub.deltaCost === 0) }) const position = data?.auctionPosition @@ -82,37 +82,10 @@ export default function JobForm ({ item, sub }) {
  1. The higher your bid the higher your job will rank
  2. The minimum bid is {sub.baseCost} sats/mo
  3. -
  4. You can increase or decrease your bid at anytime
  5. -
  6. You can edit or remove your job at anytime
  7. +
  8. Your sats/mo must be a multiple of {sub.deltaCost} sats
  9. +
  10. You can increase or decrease your bid, and edit or stop your job at anytime
  11. Your job will be hidden if your wallet runs out of sats and can be unhidden by filling your wallet again
- How does ranking work in detail?} - body={ -
-
    -
  1. You only pay as many sats/mo as required to maintain your position relative to other - posts (or {sub.baseCost} sats/mo) and only up to your max bid. -
  2. -
  3. Your sats/mo must be a multiple of 1000 sats
  4. -
- -
By example
-

If your post's (A's) max bid is higher than another post (B) by at least - 1000 sats/mo your post will rank higher and your wallet will pay 1000 - sats/mo more than B. -

-

If another post (C) comes along whose max bid is higher than B's but less - than your's (A's), C will pay 1000 sats/mo more than B, and you will pay 1000 sats/mo - more than C. -

-

If a post (D) comes along whose max bid is higher than your's (A's), D - will pay 1000 stat/mo more than you (A), and the amount you (A) pays won't - change. -

-
-} - />
max bid +
bid setInfo(true)} />
} @@ -185,7 +158,7 @@ export default function JobForm ({ item, sub }) { } }} append={sats/month} - hint={up to {pull} sats/min will be pulled from your wallet} + hint={{pull} sats/min will be pulled from your wallet} />
This bid puts your job in position: {position}
{item && } diff --git a/fragments/items.js b/fragments/items.js index 74afd416..bea7dbd8 100644 --- a/fragments/items.js +++ b/fragments/items.js @@ -21,6 +21,7 @@ export const ITEM_FIELDS = gql` sub { name baseCost + deltaCost } status mine diff --git a/fragments/subs.js b/fragments/subs.js index cea8ec61..f21e0225 100644 --- a/fragments/subs.js +++ b/fragments/subs.js @@ -7,6 +7,7 @@ export const SUB_FIELDS = gql` postTypes rankingType baseCost + deltaCost }` export const SUB = gql` diff --git a/prisma/migrations/20220303170859_exp_auction/migration.sql b/prisma/migrations/20220303170859_exp_auction/migration.sql new file mode 100644 index 00000000..08d6efbb --- /dev/null +++ b/prisma/migrations/20220303170859_exp_auction/migration.sql @@ -0,0 +1,35 @@ +-- AlterTable +ALTER TABLE "Sub" ADD COLUMN "deltaCost" INTEGER NOT NULL DEFAULT 0; + +-- charge the user for the auction item +CREATE OR REPLACE FUNCTION run_auction(item_id INTEGER) RETURNS void AS $$ + DECLARE + bid INTEGER; + user_id INTEGER; + user_msats INTEGER; + item_status "Status"; + BEGIN + PERFORM ASSERT_SERIALIZED(); + + -- extract data we need + SELECT ("maxBid" * 5 / 216), "userId", status INTO bid, user_id, item_status FROM "Item" WHERE id = item_id; + SELECT msats INTO user_msats FROM users WHERE id = user_id; + + -- check if user wallet has enough sats + IF bid > user_msats THEN + -- if not, set status = NOSATS and statusUpdatedAt to now_utc if not already set + IF item_status <> 'NOSATS' THEN + UPDATE "Item" SET status = 'NOSATS', "statusUpdatedAt" = now_utc() WHERE id = item_id; + END IF; + ELSE + -- if so, deduct from user + UPDATE users SET msats = msats - bid WHERE id = user_id; + -- update item status = ACTIVE and statusUpdatedAt = null if NOSATS + IF item_status = 'NOSATS' THEN + UPDATE "Item" SET status = 'ACTIVE', "statusUpdatedAt" = now_utc() WHERE id = item_id; + END IF; + END IF; + END; +$$ LANGUAGE plpgsql; + +UPDATE "Sub" SET "baseCost" = 500000, "deltaCost" = 50000 WHERE name ='jobs'; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 122ff980..7eb2b1a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -145,6 +145,7 @@ model Sub { postTypes PostType[] rankingType RankingType baseCost Int @default(1) + deltaCost Int @default(0) desc String? Item Item[] diff --git a/worker/auction.js b/worker/auction.js index d60e9de8..3b57c083 100644 --- a/worker/auction.js +++ b/worker/auction.js @@ -3,12 +3,7 @@ const serialize = require('../api/resolvers/serial') function auction ({ models }) { return async function ({ name }) { console.log('running', name) - // TODO: do this for each sub with auction ranking - // because we only have one auction sub, we don't need to do this - const SUB_BASE_COST = 10000 - const BID_DELTA = 1000 - - // get all items we need to check in order of low to high bid + // get all items we need to check const items = await models.item.findMany( { where: { @@ -18,33 +13,15 @@ function auction ({ models }) { status: { not: 'STOPPED' } - }, - orderBy: { - maxBid: 'asc' } } ) - // we subtract bid delta so that the lowest bidder, pays - // the sub base cost - let lastBid = SUB_BASE_COST - BID_DELTA // for each item, run serialized auction function - for (const item of items) { - let bid = lastBid - // if this item's maxBid is great enough, have them pay more - // else have them match the last bid - if (item.maxBid >= lastBid + BID_DELTA) { - bid = lastBid + BID_DELTA - } - - const [{ run_auction: succeeded }] = await serialize(models, - models.$queryRaw`SELECT run_auction(${item.id}, ${bid})`) - - // if we succeeded update the lastBid - if (succeeded) { - lastBid = bid - } - } + items.forEach(async item => { + await serialize(models, + models.$executeRaw`SELECT run_auction(${item.id})`) + }) console.log('done', name) }