diff --git a/api/resolvers/item.js b/api/resolvers/item.js index d477c819..5c21de90 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -78,7 +78,11 @@ export default { let items; let user; let pins; let subFull const subClause = (num) => { - return sub ? ` AND "subName" = $${num} ` : `AND ("subName" IS NULL OR "subName" = $${3}) ` + return sub ? ` AND "subName" = $${num} ` : ` AND ("subName" IS NULL OR "subName" = $${3}) ` + } + + const activeOrMine = () => { + return me ? ` AND (status = 'ACTIVE' OR "userId" = ${me.id}) ` : ' AND status = \'ACTIVE\' ' } switch (sort) { @@ -97,6 +101,7 @@ export default { FROM "Item" WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2 AND "pinId" IS NULL + ${activeOrMine()} ORDER BY created_at DESC OFFSET $3 LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset) @@ -107,6 +112,7 @@ export default { FROM "Item" WHERE "parentId" IS NULL AND created_at <= $1 AND "pinId" IS NULL ${subClause(3)} + ${activeOrMine()} ORDER BY created_at DESC OFFSET $2 LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub || 'NULL') @@ -140,6 +146,7 @@ export default { WHERE "parentId" IS NULL AND created_at <= $1 AND "pinId" IS NULL ${subClause(3)} + AND status = 'ACTIVE' ORDER BY "maxBid" / 1000 DESC, created_at ASC OFFSET $2 LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub) @@ -291,7 +298,7 @@ export default { comments: async (parent, { id, sort }, { models }) => { return comments(models, id, sort) }, - search: async (parent, { q: query, sub, cursor }, { models, search }) => { + search: async (parent, { q: query, sub, cursor }, { me, models, search }) => { const decodedCursor = decodeCursor(cursor) let sitems @@ -305,8 +312,18 @@ export default { bool: { must: [ sub - ? { term: { 'sub.name': sub } } + ? { match: { 'sub.name': sub } } : { bool: { must_not: { exists: { field: 'sub.name' } } } }, + me + ? { + bool: { + should: [ + { match: { status: 'ACTIVE' } }, + { match: { userId: me.id } } + ] + } + } + : { match: { status: 'ACTIVE' } }, { bool: { should: [ @@ -395,6 +412,7 @@ export default { const where = { where: { subName: sub, + status: 'ACTIVE', OR: [{ maxBid: { gte: bid + 1000 @@ -487,7 +505,7 @@ export default { return await updateItem(parent, { id, data: { title, text } }, { me, models }) }, - upsertJob: async (parent, { id, sub, title, text, url, maxBid }, { me, models }) => { + upsertJob: async (parent, { id, sub, title, text, url, maxBid, status }, { me, models }) => { if (!me) { throw new AuthenticationError('you must be logged in to create job') } @@ -512,6 +530,15 @@ export default { throw new UserInputError(`bid must be at least ${fullSub.baseCost}`, { argumentName: 'maxBid' }) } + const checkSats = async () => { + // check if the user has the funds to run for the first minute + const minuteMsats = maxBid * 1000 / 30 / 24 / 60 + const user = models.user.findUnique({ where: { id: me.id } }) + if (user.msats < minuteMsats) { + throw new UserInputError('insufficient funds') + } + } + const data = { title, text, @@ -522,12 +549,23 @@ export default { } 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() + } + } + return await models.item.update({ where: { id: Number(id) }, data }) } + // before creating job, check the sats + await checkSats() return await models.item.create({ data }) @@ -850,7 +888,7 @@ function nestComments (flat, parentId) { export const SELECT = `SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title, "Item".text, "Item".url, "Item"."userId", "Item"."parentId", "Item"."pinId", "Item"."maxBid", - "Item"."subName", ltree2text("Item"."path") AS "path"` + "Item"."subName", "Item".status, ltree2text("Item"."path") AS "path"` const LEFT_JOIN_SATS_SELECT = 'SELECT i.id, SUM(CASE WHEN "ItemAct".act = \'VOTE\' THEN "ItemAct".sats ELSE 0 END) as sats, SUM(CASE WHEN "ItemAct".act = \'BOOST\' THEN "ItemAct".sats ELSE 0 END) as boost' diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index c9946b1b..da5b15bc 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -25,7 +25,7 @@ export default gql` updateDiscussion(id: ID!, title: String!, text: String): Item! createComment(text: String!, parentId: ID!): Item! updateComment(id: ID!, text: String!): Item! - upsertJob(id: ID, sub: ID!, title: String!, text: String!, url: String!, maxBid: Int!): Item! + upsertJob(id: ID, sub: ID!, title: String!, text: String!, url: String!, maxBid: Int!, status: String): Item! act(id: ID!, sats: Int): ItemActResult! } @@ -67,5 +67,6 @@ export default gql` prior: Int maxBid: Int sub: Sub + status: String } ` diff --git a/components/item.js b/components/item.js index ce41b971..b39bd1c3 100644 --- a/components/item.js +++ b/components/item.js @@ -69,6 +69,7 @@ export function ItemJob ({ item, rank, children }) { edit + {item.status !== 'ACTIVE' && {item.status}} } diff --git a/components/item.module.css b/components/item.module.css index 34e843ed..c6bf6f24 100644 --- a/components/item.module.css +++ b/components/item.module.css @@ -24,7 +24,6 @@ .case { fill: #a5a5a5; margin-right: .2rem; - margin-left: .2rem; margin-top: .2rem; padding: 0 2px; } diff --git a/components/job-form.js b/components/job-form.js index 60eaced8..15fb6d9c 100644 --- a/components/job-form.js +++ b/components/job-form.js @@ -1,4 +1,4 @@ -import { Form, Input, MarkdownInput, SubmitButton } from './form' +import { Checkbox, Form, Input, MarkdownInput, SubmitButton } from './form' import TextareaAutosize from 'react-textarea-autosize' import { InputGroup, Modal } from 'react-bootstrap' import * as Yup from 'yup' @@ -8,6 +8,7 @@ import AccordianItem from './accordian-item' import styles from '../styles/post.module.css' import { useLazyQuery, gql, useMutation } from '@apollo/client' import { useRouter } from 'next/router' +import Link from 'next/link' Yup.addMethod(Yup.string, 'or', function (schemas, msg) { return this.test({ @@ -41,8 +42,10 @@ export default function JobForm ({ item, sub }) { }`, { fetchPolicy: 'network-only' }) const [upsertJob] = useMutation(gql` - mutation upsertJob($id: ID, $title: String!, $text: String!, $url: String!, $maxBid: Int!) { - upsertJob(sub: "${sub.name}", id: $id title: $title, text: $text, url: $url, maxBid: $maxBid) { + mutation upsertJob($id: ID, $title: String!, $text: String!, + $url: String!, $maxBid: Int!, $status: String) { + upsertJob(sub: "${sub.name}", id: $id title: $title, text: $text, + url: $url, maxBid: $maxBid, status: $status) { id } }` @@ -118,12 +121,21 @@ export default function JobForm ({ item, sub }) { title: item?.title || '', text: item?.text || '', url: item?.url || '', - maxBid: item?.maxBid || sub.baseCost + maxBid: item?.maxBid || sub.baseCost, + stop: false, + start: false }} schema={JobSchema} storageKeyPrefix={storageKeyPrefix} - onSubmit={(async ({ maxBid, ...values }) => { - const variables = { sub: sub.name, maxBid: Number(maxBid), ...values } + onSubmit={(async ({ maxBid, stop, start, ...values }) => { + let status + if (start) { + status = 'ACTIVE' + } else if (stop) { + status = 'STOPPED' + } + + const variables = { sub: sub.name, maxBid: Number(maxBid), status, ...values } if (item) { variables.id = item.id } @@ -176,8 +188,57 @@ export default function JobForm ({ item, sub }) { hint={up to {pull} sats/min will be pulled from your wallet} />
This bid puts your job in position: {position}
+ {item && } {item ? 'save' : 'post'} ) } + +function StatusControl ({ item }) { + let StatusComp + + if (item.status === 'ACTIVE') { + StatusComp = () => { + return ( + I want to stop my job} + headerColor='var(--danger)' + body={ + stop my job} name='stop' inline + /> + } + /> + ) + } + } else if (item.status === 'STOPPED') { + StatusComp = () => { + return ( + I want to resume my job} + headerColor='var(--success)' + body={ + resume my job} name='start' inline + /> + } + /> + ) + } + } else { + StatusComp = () => { + return ( +
+ you have no sats! fund your wallet to resume your job +
+ ) + } + } + + return ( +
+ +
+ ) +} diff --git a/fragments/items.js b/fragments/items.js index b4ddd8e7..74afd416 100644 --- a/fragments/items.js +++ b/fragments/items.js @@ -22,6 +22,7 @@ export const ITEM_FIELDS = gql` name baseCost } + status mine root { id diff --git a/worker/search.js b/worker/search.js index acde70b0..7621adcd 100644 --- a/worker/search.js +++ b/worker/search.js @@ -17,6 +17,7 @@ const ITEM_SEARCH_FIELDS = gql` sub { name } + status maxBid upvotes sats