make jobs great again
This commit is contained in:
parent
52fab60cda
commit
46ea2f661c
|
@ -201,7 +201,7 @@ export default {
|
|||
WHERE "parentId" IS NULL AND created_at <= $1
|
||||
AND "pinId" IS NULL
|
||||
${subClause(3)}
|
||||
AND status = 'ACTIVE'
|
||||
AND status = 'ACTIVE' AND "maxBid" > 0
|
||||
ORDER BY "maxBid" DESC, created_at ASC)
|
||||
UNION ALL
|
||||
(${SELECT}
|
||||
|
@ -209,7 +209,7 @@ export default {
|
|||
WHERE "parentId" IS NULL AND created_at <= $1
|
||||
AND "pinId" IS NULL
|
||||
${subClause(3)}
|
||||
AND status = 'NOSATS'
|
||||
AND ((status = 'ACTIVE' AND "maxBid" = 0) OR status = 'NOSATS')
|
||||
ORDER BY created_at DESC)
|
||||
) a
|
||||
OFFSET $2
|
||||
|
@ -456,11 +456,19 @@ export default {
|
|||
bool: {
|
||||
should: [
|
||||
{ match: { status: 'ACTIVE' } },
|
||||
{ match: { status: 'NOSATS' } },
|
||||
{ match: { userId: me.id } }
|
||||
]
|
||||
}
|
||||
}
|
||||
: { match: { status: 'ACTIVE' } },
|
||||
: {
|
||||
bool: {
|
||||
should: [
|
||||
{ match: { status: 'ACTIVE' } },
|
||||
{ match: { status: 'NOSATS' } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
|
@ -544,17 +552,24 @@ export default {
|
|||
items
|
||||
}
|
||||
},
|
||||
auctionPosition: async (parent, { id, sub, bid }, { models }) => {
|
||||
auctionPosition: async (parent, { id, sub, bid }, { models, me }) => {
|
||||
// count items that have a bid gte to the current bid or
|
||||
// gte current bid and older
|
||||
const where = {
|
||||
where: {
|
||||
subName: sub,
|
||||
status: 'ACTIVE',
|
||||
maxBid: {
|
||||
gte: bid
|
||||
status: { not: 'STOPPED' }
|
||||
}
|
||||
}
|
||||
|
||||
if (bid > 0) {
|
||||
where.where.maxBid = { gte: bid }
|
||||
} else {
|
||||
const createdAt = id ? (await getItem(parent, { id }, { models, me })).createdAt : new Date()
|
||||
where.where.OR = [
|
||||
{ maxBid: { gt: 0 } },
|
||||
{ createdAt: { gt: createdAt } }
|
||||
]
|
||||
}
|
||||
|
||||
if (id) {
|
||||
|
@ -646,62 +661,36 @@ export default {
|
|||
throw new UserInputError('not a valid sub', { argumentName: 'sub' })
|
||||
}
|
||||
|
||||
if (fullSub.baseCost > maxBid) {
|
||||
throw new UserInputError(`bid must be at least ${fullSub.baseCost}`, { argumentName: 'maxBid' })
|
||||
if (maxBid < 0) {
|
||||
throw new UserInputError('bid must be at least 0', { argumentName: 'maxBid' })
|
||||
}
|
||||
|
||||
if (!location && !remote) {
|
||||
throw new UserInputError('must specify location or remote', { argumentName: 'location' })
|
||||
}
|
||||
|
||||
const checkSats = async () => {
|
||||
// check if the user has the funds to run for the first minute
|
||||
const minuteMsats = maxBid * 1000
|
||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||
if (user.msats < minuteMsats) {
|
||||
throw new UserInputError('insufficient funds')
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
title,
|
||||
company,
|
||||
location: location.toLowerCase() === 'remote' ? undefined : location,
|
||||
remote,
|
||||
text,
|
||||
url,
|
||||
maxBid,
|
||||
subName: sub,
|
||||
userId: me.id,
|
||||
uploadId: logo
|
||||
}
|
||||
location = location.toLowerCase() === 'remote' ? undefined : location
|
||||
|
||||
let item
|
||||
if (id) {
|
||||
if (status) {
|
||||
data.status = status
|
||||
|
||||
// if the job is changing to active, we need to check they have funds
|
||||
if (status === 'ACTIVE') {
|
||||
await checkSats()
|
||||
}
|
||||
}
|
||||
|
||||
const old = await models.item.findUnique({ where: { id: Number(id) } })
|
||||
if (Number(old.userId) !== Number(me?.id)) {
|
||||
throw new AuthenticationError('item does not belong to you')
|
||||
}
|
||||
|
||||
return await models.item.update({
|
||||
where: { id: Number(id) },
|
||||
data
|
||||
})
|
||||
([item] = await serialize(models,
|
||||
models.$queryRaw(
|
||||
`${SELECT} FROM update_job($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) AS "Item"`,
|
||||
Number(id), title, url, text, Number(maxBid), company, location, remote, Number(logo), status)))
|
||||
} else {
|
||||
([item] = await serialize(models,
|
||||
models.$queryRaw(
|
||||
`${SELECT} FROM create_job($1, $2, $3, $4, $5, $6, $7, $8, $9) AS "Item"`,
|
||||
title, url, text, Number(me.id), Number(maxBid), company, location, remote, Number(logo))))
|
||||
}
|
||||
|
||||
// before creating job, check the sats
|
||||
await checkSats()
|
||||
return await models.item.create({
|
||||
data
|
||||
})
|
||||
await createMentions(item, models)
|
||||
|
||||
return item
|
||||
},
|
||||
createComment: async (parent, { text, parentId }, { me, models }) => {
|
||||
return await createItem(parent, { text, parentId }, { me, models })
|
||||
|
@ -767,6 +756,9 @@ export default {
|
|||
}
|
||||
},
|
||||
Item: {
|
||||
isJob: async (item, args, { models }) => {
|
||||
return item.subName === 'jobs'
|
||||
},
|
||||
sub: async (item, args, { models }) => {
|
||||
if (!item.subName) {
|
||||
return null
|
||||
|
|
|
@ -98,7 +98,6 @@ export default {
|
|||
FROM "Item"
|
||||
WHERE "Item"."userId" = $1
|
||||
AND "maxBid" IS NOT NULL
|
||||
AND status <> 'STOPPED'
|
||||
AND "statusUpdatedAt" <= $2
|
||||
ORDER BY "sortTime" DESC
|
||||
LIMIT ${LIMIT}+$3)`
|
||||
|
|
|
@ -337,9 +337,6 @@ export default {
|
|||
|
||||
const job = await models.item.findFirst({
|
||||
where: {
|
||||
status: {
|
||||
not: 'STOPPED'
|
||||
},
|
||||
maxBid: {
|
||||
not: null
|
||||
},
|
||||
|
|
|
@ -92,6 +92,7 @@ export default gql`
|
|||
position: Int
|
||||
prior: Int
|
||||
maxBid: Int
|
||||
isJob: Boolean!
|
||||
pollCost: Int
|
||||
poll: Poll
|
||||
company: String
|
||||
|
|
|
@ -83,7 +83,7 @@ function ItemEmbed ({ item }) {
|
|||
}
|
||||
|
||||
function TopLevelItem ({ item, noReply, ...props }) {
|
||||
const ItemComponent = item.maxBid ? ItemJob : Item
|
||||
const ItemComponent = item.isJob ? ItemJob : Item
|
||||
|
||||
return (
|
||||
<ItemComponent item={item} toc showFwdUser {...props}>
|
||||
|
|
|
@ -18,7 +18,7 @@ export default function ItemJob ({ item, toc, rank, children }) {
|
|||
{rank}
|
||||
</div>)
|
||||
: <div />}
|
||||
<div className={`${styles.item} ${item.status === 'NOSATS' && !item.mine ? styles.itemDead : ''}`}>
|
||||
<div className={`${styles.item}`}>
|
||||
<Link href={`/items/${item.id}`} passHref>
|
||||
<a>
|
||||
<Image
|
||||
|
@ -38,11 +38,6 @@ export default function ItemJob ({ item, toc, rank, children }) {
|
|||
</Link>
|
||||
</div>
|
||||
<div className={`${styles.other}`}>
|
||||
{item.status === 'NOSATS' &&
|
||||
<>
|
||||
<span>expired</span>
|
||||
{item.company && <span> \ </span>}
|
||||
</>}
|
||||
{item.company &&
|
||||
<>
|
||||
{item.company}
|
||||
|
@ -72,7 +67,7 @@ export default function ItemJob ({ item, toc, rank, children }) {
|
|||
edit
|
||||
</a>
|
||||
</Link>
|
||||
{item.status !== 'ACTIVE' && <span className='font-weight-bold text-danger'> {item.status}</span>}
|
||||
{item.status !== 'ACTIVE' && <span className='ml-1 font-weight-bold text-boost'> {item.status}</span>}
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,7 @@ export default function MixedItems ({ rank, items, cursor, fetchMore }) {
|
|||
<Comment item={item} noReply includeParent clickToContext />
|
||||
</div>
|
||||
</>)
|
||||
: (item.maxBid
|
||||
: (item.isJob
|
||||
? <ItemJob item={item} rank={rank && i + 1} />
|
||||
: <Item item={item} rank={rank && i + 1} />)}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -28,7 +28,7 @@ export default function Items ({ variables = {}, rank, items, pins, cursor }) {
|
|||
{pinMap && pinMap[i + 1] && <Item item={pinMap[i + 1]} />}
|
||||
{item.parentId
|
||||
? <><div /><div className='pb-3'><Comment item={item} noReply includeParent /></div></>
|
||||
: (item.maxBid
|
||||
: (item.isJob
|
||||
? <ItemJob item={item} rank={rank && i + 1} />
|
||||
: (item.title
|
||||
? <Item item={item} rank={rank && i + 1} />
|
||||
|
|
|
@ -11,6 +11,8 @@ import { useRouter } from 'next/router'
|
|||
import Link from 'next/link'
|
||||
import { usePrice } from './price'
|
||||
import Avatar from './avatar'
|
||||
import BootstrapForm from 'react-bootstrap/Form'
|
||||
import Alert from 'react-bootstrap/Alert'
|
||||
|
||||
Yup.addMethod(Yup.string, 'or', function (schemas, msg) {
|
||||
return this.test({
|
||||
|
@ -34,7 +36,7 @@ function satsMin2Mo (minute) {
|
|||
|
||||
function PriceHint ({ monthly }) {
|
||||
const price = usePrice()
|
||||
if (!price) {
|
||||
if (!price || !monthly) {
|
||||
return null
|
||||
}
|
||||
const fixed = (n, f) => Number.parseFloat(n).toFixed(f)
|
||||
|
@ -47,13 +49,7 @@ function PriceHint ({ monthly }) {
|
|||
export default function JobForm ({ item, sub }) {
|
||||
const storageKeyPrefix = item ? undefined : `${sub.name}-job`
|
||||
const router = useRouter()
|
||||
const [monthly, setMonthly] = useState(satsMin2Mo(item?.maxBid || sub.baseCost))
|
||||
const [logoId, setLogoId] = useState(item?.uploadId)
|
||||
const [getAuctionPosition, { data }] = useLazyQuery(gql`
|
||||
query AuctionPosition($id: ID, $bid: Int!) {
|
||||
auctionPosition(sub: "${sub.name}", id: $id, bid: $bid)
|
||||
}`,
|
||||
{ fetchPolicy: 'network-only' })
|
||||
const [upsertJob] = useMutation(gql`
|
||||
mutation upsertJob($id: ID, $title: String!, $company: String!, $location: String,
|
||||
$remote: Boolean, $text: String!, $url: String!, $maxBid: Int!, $status: String, $logo: Int) {
|
||||
|
@ -72,8 +68,8 @@ export default function JobForm ({ item, sub }) {
|
|||
url: Yup.string()
|
||||
.or([Yup.string().email(), Yup.string().url()], 'invalid url or email')
|
||||
.required('required'),
|
||||
maxBid: Yup.number('must be number')
|
||||
.integer('must be whole').min(sub.baseCost, `must be at least ${sub.baseCost}`)
|
||||
maxBid: Yup.number().typeError('must be a number')
|
||||
.integer('must be whole').min(0, 'must be positive')
|
||||
.required('required'),
|
||||
location: Yup.string().test(
|
||||
'no-remote',
|
||||
|
@ -85,14 +81,6 @@ export default function JobForm ({ item, sub }) {
|
|||
})
|
||||
})
|
||||
|
||||
const position = data?.auctionPosition
|
||||
|
||||
useEffect(() => {
|
||||
const initialMaxBid = Number(item?.maxBid || localStorage.getItem(storageKeyPrefix + '-maxBid')) || sub.baseCost
|
||||
getAuctionPosition({ variables: { id: item?.id, bid: initialMaxBid } })
|
||||
setMonthly(satsMin2Mo(initialMaxBid))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
|
@ -104,7 +92,7 @@ export default function JobForm ({ item, sub }) {
|
|||
remote: item?.remote || false,
|
||||
text: item?.text || '',
|
||||
url: item?.url || '',
|
||||
maxBid: item?.maxBid || sub.baseCost,
|
||||
maxBid: item?.maxBid || 0,
|
||||
stop: false,
|
||||
start: false
|
||||
}}
|
||||
|
@ -188,36 +176,66 @@ export default function JobForm ({ item, sub }) {
|
|||
required
|
||||
clear
|
||||
/>
|
||||
<PromoteJob item={item} sub={sub} storageKeyPrefix={storageKeyPrefix} />
|
||||
{item && <StatusControl item={item} />}
|
||||
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PromoteJob ({ item, sub, storageKeyPrefix }) {
|
||||
const [monthly, setMonthly] = useState(satsMin2Mo(item?.maxBid || 0))
|
||||
const [getAuctionPosition, { data }] = useLazyQuery(gql`
|
||||
query AuctionPosition($id: ID, $bid: Int!) {
|
||||
auctionPosition(sub: "${sub.name}", id: $id, bid: $bid)
|
||||
}`,
|
||||
{ fetchPolicy: 'network-only' })
|
||||
const position = data?.auctionPosition
|
||||
|
||||
useEffect(() => {
|
||||
const initialMaxBid = Number(item?.maxBid || localStorage.getItem(storageKeyPrefix + '-maxBid')) || 0
|
||||
getAuctionPosition({ variables: { id: item?.id, bid: initialMaxBid } })
|
||||
setMonthly(satsMin2Mo(initialMaxBid))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<AccordianItem
|
||||
show={item?.maxBid > 0}
|
||||
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>promote</div>}
|
||||
body={
|
||||
<>
|
||||
<Input
|
||||
label={
|
||||
<div className='d-flex align-items-center'>bid
|
||||
<Info>
|
||||
<ol className='font-weight-bold'>
|
||||
<li>The higher your bid the higher your job will rank</li>
|
||||
<li>The minimum bid is {sub.baseCost} sats/min</li>
|
||||
<li>You can increase or decrease your bid, and edit or stop your job at anytime</li>
|
||||
<li>Your job will be hidden if your wallet runs out of sats and can be unhidden by filling your wallet again</li>
|
||||
<li>You can increase, decrease, or remove your bid at anytime</li>
|
||||
<li>You can edit or stop your job at anytime</li>
|
||||
<li>If you run out of sats, your job will stop being promoted until you fill your wallet again</li>
|
||||
</ol>
|
||||
</Info>
|
||||
<small className='text-muted ml-2'>optional</small>
|
||||
</div>
|
||||
}
|
||||
name='maxBid'
|
||||
onChange={async (formik, e) => {
|
||||
if (e.target.value >= sub.baseCost && e.target.value <= 100000000) {
|
||||
if (e.target.value >= 0 && e.target.value <= 100000000) {
|
||||
setMonthly(satsMin2Mo(e.target.value))
|
||||
getAuctionPosition({ variables: { id: item?.id, bid: Number(e.target.value) } })
|
||||
} else {
|
||||
setMonthly(satsMin2Mo(sub.baseCost))
|
||||
setMonthly(satsMin2Mo(0))
|
||||
}
|
||||
}}
|
||||
append={<InputGroup.Text className='text-monospace'>sats/min</InputGroup.Text>}
|
||||
hint={<PriceHint monthly={monthly} />}
|
||||
storageKeyPrefix={storageKeyPrefix}
|
||||
/>
|
||||
<><div className='font-weight-bold text-muted'>This bid puts your job in position: {position}</div></>
|
||||
{item && <StatusControl item={item} />}
|
||||
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
||||
</Form>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -241,7 +259,7 @@ function StatusControl ({ item }) {
|
|||
</>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
} else if (item.status === 'STOPPED') {
|
||||
StatusComp = () => {
|
||||
return (
|
||||
<AccordianItem
|
||||
|
@ -258,12 +276,13 @@ function StatusControl ({ item }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='my-2'>
|
||||
<div className='my-3 border border-3 rounded'>
|
||||
<div className='p-3'>
|
||||
<BootstrapForm.Label>job control</BootstrapForm.Label>
|
||||
{item.status === 'NOSATS' &&
|
||||
<div className='text-danger font-weight-bold my-1'>
|
||||
you have no sats! <Link href='/wallet?type=fund' passHref><a className='text-reset text-underline'>fund your wallet</a></Link> to resume your job
|
||||
</div>}
|
||||
<Alert variant='warning'>your promotion ran out of sats. <Link href='/wallet?type=fund' passHref><a className='text-reset text-underline'>fund your wallet</a></Link> or reduce bid to continue promoting your job</Alert>}
|
||||
<StatusComp />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -105,13 +105,15 @@ function Notification ({ n }) {
|
|||
you were mentioned in
|
||||
</small>}
|
||||
{n.__typename === 'JobChanged' &&
|
||||
<small className={`font-weight-bold text-${n.item.status === 'NOSATS' ? 'danger' : 'success'} ml-1`}>
|
||||
{n.item.status === 'NOSATS'
|
||||
? 'your job ran out of sats'
|
||||
: 'your job is active again'}
|
||||
<small className={`font-weight-bold text-${n.item.status === 'ACTIVE' ? 'success' : 'boost'} ml-1`}>
|
||||
{n.item.status === 'ACTIVE'
|
||||
? 'your job is active again'
|
||||
: (n.item.status === 'NOSATS'
|
||||
? 'your job promotion ran out of sats'
|
||||
: 'your job has been stopped')}
|
||||
</small>}
|
||||
<div className={n.__typename === 'Votification' || n.__typename === 'Mention' || n.__typename === 'JobChanged' ? '' : 'py-2'}>
|
||||
{n.item.maxBid
|
||||
{n.item.isJob
|
||||
? <ItemJob item={n.item} />
|
||||
: n.item.title
|
||||
? <Item item={n.item} />
|
||||
|
|
|
@ -28,6 +28,7 @@ export const ITEM_FIELDS = gql`
|
|||
commentSats
|
||||
lastCommentAt
|
||||
maxBid
|
||||
isJob
|
||||
company
|
||||
location
|
||||
remote
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function PostEdit ({ data: { item } }) {
|
|||
|
||||
return (
|
||||
<LayoutCenter sub={item.sub?.name}>
|
||||
{item.maxBid
|
||||
{item.isJob
|
||||
? <JobForm item={item} sub={item.sub} />
|
||||
: (item.url
|
||||
? <LinkForm item={item} editThreshold={editThreshold} adv />
|
||||
|
|
|
@ -6,7 +6,7 @@ import { getGetServerSideProps } from '../../../api/ssrApollo'
|
|||
import { useQuery } from '@apollo/client'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps(ITEM_FULL, null,
|
||||
data => !data.item || (data.item.status !== 'ACTIVE' && !data.item.mine))
|
||||
data => !data.item || (data.item.status === 'STOPPED' && !data.item.mine))
|
||||
|
||||
export default function AnItem ({ data: { item } }) {
|
||||
const { data } = useQuery(ITEM_FULL, {
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
-- 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";
|
||||
status_updated_at timestamp(3);
|
||||
BEGIN
|
||||
PERFORM ASSERT_SERIALIZED();
|
||||
|
||||
-- extract data we need
|
||||
SELECT "maxBid" * 1000, "userId", status, "statusUpdatedAt" INTO bid, user_id, item_status, status_updated_at FROM "Item" WHERE id = item_id;
|
||||
SELECT msats INTO user_msats FROM users WHERE id = user_id;
|
||||
|
||||
-- 0 bid items expire after 30 days unless updated
|
||||
IF bid = 0 THEN
|
||||
IF item_status <> 'STOPPED' AND status_updated_at < now_utc() - INTERVAL '30 days' THEN
|
||||
UPDATE "Item" SET status = 'STOPPED', "statusUpdatedAt" = now_utc() WHERE id = item_id;
|
||||
END IF;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- 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;
|
||||
|
||||
-- create an item act
|
||||
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
|
||||
VALUES (bid / 1000, item_id, user_id, 'STREAM', now_utc(), now_utc());
|
||||
|
||||
-- update item status = ACTIVE and statusUpdatedAt = now_utc 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;
|
||||
|
||||
-- when creating free item, set freebie flag so can be optionally viewed
|
||||
CREATE OR REPLACE FUNCTION create_job(
|
||||
title TEXT, url TEXT, text TEXT, user_id INTEGER, job_bid INTEGER, job_company TEXT,
|
||||
job_location TEXT, job_remote BOOLEAN, job_upload_id INTEGER)
|
||||
RETURNS "Item"
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
item "Item";
|
||||
BEGIN
|
||||
PERFORM ASSERT_SERIALIZED();
|
||||
-- create item
|
||||
SELECT * INTO item FROM create_item(title, url, text, 0, NULL, user_id, NULL, '0');
|
||||
|
||||
-- update by adding additional fields
|
||||
UPDATE "Item"
|
||||
SET "maxBid" = job_bid, company = job_company, location = job_location, remote = job_remote, "uploadId" = job_upload_id, "subName" = 'jobs'
|
||||
WHERE id = item.id RETURNING * INTO item;
|
||||
|
||||
-- run_auction
|
||||
EXECUTE run_auction(item.id);
|
||||
|
||||
RETURN item;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_job(item_id INTEGER,
|
||||
item_title TEXT, item_url TEXT, item_text TEXT, job_bid INTEGER, job_company TEXT,
|
||||
job_location TEXT, job_remote BOOLEAN, job_upload_id INTEGER, job_status "Status")
|
||||
RETURNS "Item"
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
user_msats INTEGER;
|
||||
item "Item";
|
||||
BEGIN
|
||||
PERFORM ASSERT_SERIALIZED();
|
||||
-- update item
|
||||
SELECT * INTO item FROM update_item(item_id, item_title, item_url, item_text, 0, NULL);
|
||||
|
||||
IF item.status <> job_status THEN
|
||||
UPDATE "Item"
|
||||
SET "maxBid" = job_bid, company = job_company, location = job_location, remote = job_remote, "uploadId" = job_upload_id, status = job_status, "statusUpdatedAt" = now_utc()
|
||||
WHERE id = item.id RETURNING * INTO item;
|
||||
ELSE
|
||||
UPDATE "Item"
|
||||
SET "maxBid" = job_bid, company = job_company, location = job_location, remote = job_remote, "uploadId" = job_upload_id
|
||||
WHERE id = item.id RETURNING * INTO item;
|
||||
END IF;
|
||||
|
||||
-- run_auction
|
||||
EXECUTE run_auction(item.id);
|
||||
|
||||
RETURN item;
|
||||
END;
|
||||
$$;
|
Loading…
Reference in New Issue