attempts at serializable transactions
This commit is contained in:
		
							parent
							
								
									208980f302
								
							
						
					
					
						commit
						0eabe1463d
					
				@ -1,4 +1,5 @@
 | 
				
			|||||||
import { UserInputError, AuthenticationError } from 'apollo-server-micro'
 | 
					import { UserInputError, AuthenticationError } from 'apollo-server-micro'
 | 
				
			||||||
 | 
					import serialize from './serial'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function comments (models, id) {
 | 
					async function comments (models, id) {
 | 
				
			||||||
  const flat = await models.$queryRaw(`
 | 
					  const flat = await models.$queryRaw(`
 | 
				
			||||||
@ -106,15 +107,7 @@ export default {
 | 
				
			|||||||
        throw new UserInputError('sats must be positive', { argumentName: 'sats' })
 | 
					        throw new UserInputError('sats must be positive', { argumentName: 'sats' })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      await serialize(models, models.$queryRaw`SELECT vote(${Number(id)}, ${me.name}, ${Number(sats)})`)
 | 
				
			||||||
        await models.$queryRaw`SELECT vote(${Number(id)}, ${me.name}, ${Number(sats)})`
 | 
					 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        const { meta: { message } } = error
 | 
					 | 
				
			||||||
        if (message.includes('SN_INSUFFICIENT_FUNDS')) {
 | 
					 | 
				
			||||||
          throw new UserInputError('insufficient funds')
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw error
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return sats
 | 
					      return sats
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@ -178,19 +171,11 @@ const createItem = async (parent, { title, url, text, parentId }, { me, models }
 | 
				
			|||||||
    throw new AuthenticationError('you must be logged in')
 | 
					    throw new AuthenticationError('you must be logged in')
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  const [item] = await serialize(models, models.$queryRaw(
 | 
				
			||||||
    const [item] = await models.$queryRaw(
 | 
					 | 
				
			||||||
    `${SELECT} FROM create_item($1, $2, $3, $4, $5) AS "Item"`,
 | 
					    `${SELECT} FROM create_item($1, $2, $3, $4, $5) AS "Item"`,
 | 
				
			||||||
      title, url, text, Number(parentId), me.name)
 | 
					    title, url, text, Number(parentId), me.name))
 | 
				
			||||||
  item.comments = []
 | 
					  item.comments = []
 | 
				
			||||||
  return item
 | 
					  return item
 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    const { meta: { message } } = error
 | 
					 | 
				
			||||||
    if (message.includes('SN_INSUFFICIENT_FUNDS')) {
 | 
					 | 
				
			||||||
      throw new UserInputError('insufficient funds')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    throw error
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function nestComments (flat, parentId) {
 | 
					function nestComments (flat, parentId) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								api/resolvers/serial.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								api/resolvers/serial.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					const { UserInputError } = require('apollo-server-micro')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function serialize (models, call) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const [, result] = await models.$transaction([
 | 
				
			||||||
 | 
					      models.$executeRaw(SERIALIZE),
 | 
				
			||||||
 | 
					      call
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					    return result
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.log(error)
 | 
				
			||||||
 | 
					    if (error.message.includes('SN_INSUFFICIENT_FUNDS')) {
 | 
				
			||||||
 | 
					      throw new UserInputError('insufficient funds')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (error.message.includes('SN_NOT_SERIALIZABLE')) {
 | 
				
			||||||
 | 
					      throw new Error('wallet transaction isolation level is not serializable')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    throw error
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SERIALIZE = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = serialize
 | 
				
			||||||
@ -1,24 +1,59 @@
 | 
				
			|||||||
import { createInvoice, decodePaymentRequest, subscribeToPayViaRequest } from 'ln-service'
 | 
					import { createInvoice, decodePaymentRequest, subscribeToPayViaRequest } from 'ln-service'
 | 
				
			||||||
import { UserInputError, AuthenticationError } from 'apollo-server-micro'
 | 
					import { UserInputError, AuthenticationError } from 'apollo-server-micro'
 | 
				
			||||||
 | 
					import serialize from './serial'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  Query: {
 | 
					  Query: {
 | 
				
			||||||
    invoice: async (parent, { id }, { me, models, lnd }) => {
 | 
					    invoice: async (parent, { id }, { me, models, lnd }) => {
 | 
				
			||||||
      return await models.invoice.findUnique({ where: { id: Number(id) } })
 | 
					      if (!me) {
 | 
				
			||||||
 | 
					        throw new AuthenticationError('you must be logged in')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const inv = await models.invoice.findUnique({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          id: Number(id)
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        include: {
 | 
				
			||||||
 | 
					          user: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (inv.user.name !== me.name) {
 | 
				
			||||||
 | 
					        throw new AuthenticationError('not ur invoice')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return inv
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    withdrawl: async (parent, { id }, { me, models, lnd }) => {
 | 
					    withdrawl: async (parent, { id }, { me, models, lnd }) => {
 | 
				
			||||||
      return await models.withdrawl.findUnique({ where: { id: Number(id) } })
 | 
					      if (!me) {
 | 
				
			||||||
 | 
					        throw new AuthenticationError('you must be logged in')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const wdrwl = await models.withdrawl.findUnique({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          id: Number(id)
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        include: {
 | 
				
			||||||
 | 
					          user: true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (wdrwl.user.name !== me.name) {
 | 
				
			||||||
 | 
					        throw new AuthenticationError('not ur withdrawl')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return wdrwl
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Mutation: {
 | 
					  Mutation: {
 | 
				
			||||||
    createInvoice: async (parent, { amount }, { me, models, lnd }) => {
 | 
					    createInvoice: async (parent, { amount }, { me, models, lnd }) => {
 | 
				
			||||||
      if (!me) {
 | 
					      if (!me) {
 | 
				
			||||||
        throw new AuthenticationError('You must be logged in')
 | 
					        throw new AuthenticationError('you must be logged in')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!amount || amount <= 0) {
 | 
					      if (!amount || amount <= 0) {
 | 
				
			||||||
        throw new UserInputError('Amount must be positive', { argumentName: 'amount' })
 | 
					        throw new UserInputError('amount must be positive', { argumentName: 'amount' })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // set expires at to 3 hours into future
 | 
					      // set expires at to 3 hours into future
 | 
				
			||||||
@ -47,18 +82,18 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    createWithdrawl: async (parent, { invoice, maxFee }, { me, models, lnd }) => {
 | 
					    createWithdrawl: async (parent, { invoice, maxFee }, { me, models, lnd }) => {
 | 
				
			||||||
      if (!me) {
 | 
					      if (!me) {
 | 
				
			||||||
        throw new AuthenticationError('You must be logged in')
 | 
					        throw new AuthenticationError('you must be logged in')
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // decode invoice to get amount
 | 
					      // decode invoice to get amount
 | 
				
			||||||
      const decoded = await decodePaymentRequest({ lnd, request: invoice })
 | 
					      const decoded = await decodePaymentRequest({ lnd, request: invoice })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const msatsFee = Number(maxFee) * 1000
 | 
					      const msatsFee = Number(maxFee) * 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // create withdrawl transactionally (id, bolt11, amount, fee)
 | 
					      // create withdrawl transactionally (id, bolt11, amount, fee)
 | 
				
			||||||
      try {
 | 
					      const [withdrawl] = await serialize(models,
 | 
				
			||||||
        const [withdrawl] =
 | 
					        models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
 | 
				
			||||||
        await models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
 | 
					          ${Number(decoded.mtokens)}, ${msatsFee}, ${me.name})`)
 | 
				
			||||||
          ${Number(decoded.mtokens)}, ${msatsFee}, ${me.name})`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // create the payment, subscribing to its status
 | 
					      // create the payment, subscribing to its status
 | 
				
			||||||
      const sub = subscribeToPayViaRequest({
 | 
					      const sub = subscribeToPayViaRequest({
 | 
				
			||||||
@ -75,8 +110,8 @@ export default {
 | 
				
			|||||||
        // mtokens also contains the fee
 | 
					        // mtokens also contains the fee
 | 
				
			||||||
        const fee = Number(e.fee_mtokens)
 | 
					        const fee = Number(e.fee_mtokens)
 | 
				
			||||||
        const paid = Number(e.mtokens) - fee
 | 
					        const paid = Number(e.mtokens) - fee
 | 
				
			||||||
          await models.$queryRaw`
 | 
					        await serialize(models, models.$queryRaw`
 | 
				
			||||||
            SELECT confirm_withdrawl(${withdrawl.id}, ${paid}, ${fee})`
 | 
					            SELECT confirm_withdrawl(${withdrawl.id}, ${paid}, ${fee})`)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // if the payment fails, we need to
 | 
					      // if the payment fails, we need to
 | 
				
			||||||
@ -94,18 +129,11 @@ export default {
 | 
				
			|||||||
        } else if (e.is_route_not_found) {
 | 
					        } else if (e.is_route_not_found) {
 | 
				
			||||||
          status = 'ROUTE_NOT_FOUND'
 | 
					          status = 'ROUTE_NOT_FOUND'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
          await models.$queryRaw`
 | 
					        await serialize(models, models.$queryRaw`
 | 
				
			||||||
            SELECT reverse_withdrawl(${withdrawl.id}, ${status})`
 | 
					            SELECT reverse_withdrawl(${withdrawl.id}, ${status})`)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return withdrawl
 | 
					      return withdrawl
 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        const { meta: { message } } = error
 | 
					 | 
				
			||||||
        if (message.includes('SN_INSUFFICIENT_FUNDS')) {
 | 
					 | 
				
			||||||
          throw new UserInputError('insufficient funds')
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw error
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -90,7 +90,10 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac
 | 
				
			|||||||
          </div>}
 | 
					          </div>}
 | 
				
			||||||
        {reply &&
 | 
					        {reply &&
 | 
				
			||||||
          <div className={styles.replyWrapper}>
 | 
					          <div className={styles.replyWrapper}>
 | 
				
			||||||
            <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />
 | 
					            <Reply
 | 
				
			||||||
 | 
					              parentId={item.id} autoFocus={!replyOpen}
 | 
				
			||||||
 | 
					              onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
          </div>}
 | 
					          </div>}
 | 
				
			||||||
        {children}
 | 
					        {children}
 | 
				
			||||||
        <div className={`${styles.comments} ml-sm-1 ml-md-3`}>
 | 
					        <div className={`${styles.comments} ml-sm-1 ml-md-3`}>
 | 
				
			||||||
 | 
				
			|||||||
@ -93,7 +93,7 @@ export function Form ({
 | 
				
			|||||||
      validationSchema={schema}
 | 
					      validationSchema={schema}
 | 
				
			||||||
      validateOnBlur={false}
 | 
					      validateOnBlur={false}
 | 
				
			||||||
      onSubmit={async (...args) =>
 | 
					      onSubmit={async (...args) =>
 | 
				
			||||||
        onSubmit && onSubmit(...args).catch(e => setError(e.message))}
 | 
					        onSubmit && onSubmit(...args).catch(e => setError(e.message || e))}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <FormikForm {...props} noValidate>
 | 
					      <FormikForm {...props} noValidate>
 | 
				
			||||||
        {error && <Alert variant='danger' onClose={() => setError(undefined)} dismissible>{error}</Alert>}
 | 
					        {error && <Alert variant='danger' onClose={() => setError(undefined)} dismissible>{error}</Alert>}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ export const CommentSchema = Yup.object({
 | 
				
			|||||||
  text: Yup.string().required('required').trim()
 | 
					  text: Yup.string().required('required').trim()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Reply ({ parentId, onSuccess }) {
 | 
					export default function Reply ({ parentId, onSuccess, autoFocus }) {
 | 
				
			||||||
  const [createComment] = useMutation(
 | 
					  const [createComment] = useMutation(
 | 
				
			||||||
    gql`
 | 
					    gql`
 | 
				
			||||||
      ${COMMENTS}
 | 
					      ${COMMENTS}
 | 
				
			||||||
@ -63,6 +63,7 @@ export default function Reply ({ parentId, onSuccess }) {
 | 
				
			|||||||
          name='text'
 | 
					          name='text'
 | 
				
			||||||
          as='textarea'
 | 
					          as='textarea'
 | 
				
			||||||
          rows={4}
 | 
					          rows={4}
 | 
				
			||||||
 | 
					          autoFocus={autoFocus}
 | 
				
			||||||
          required
 | 
					          required
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <SubmitButton variant='secondary' className='mt-1'>reply</SubmitButton>
 | 
					        <SubmitButton variant='secondary' className='mt-1'>reply</SubmitButton>
 | 
				
			||||||
 | 
				
			|||||||
@ -68,11 +68,11 @@ function LoadWithdrawl ({ query }) {
 | 
				
			|||||||
      variant = 'failed'
 | 
					      variant = 'failed'
 | 
				
			||||||
      break
 | 
					      break
 | 
				
			||||||
    case 'PATHFINDING_TIMEOUT':
 | 
					    case 'PATHFINDING_TIMEOUT':
 | 
				
			||||||
      status = <>timed out trying to find route <small className='ml-3'>try increasing max fee</small></>
 | 
					      status = <>timed out finding route <small className='ml-3'>try increasing max fee</small></>
 | 
				
			||||||
      variant = 'failed'
 | 
					      variant = 'failed'
 | 
				
			||||||
      break
 | 
					      break
 | 
				
			||||||
    case 'ROUTE_NOT_FOUND':
 | 
					    case 'ROUTE_NOT_FOUND':
 | 
				
			||||||
      status = <>could not find route <small className='ml-3'>try increasing max fee</small></>
 | 
					      status = <>no route <small className='ml-3'>try increasing max fee</small></>
 | 
				
			||||||
      variant = 'failed'
 | 
					      variant = 'failed'
 | 
				
			||||||
      break
 | 
					      break
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,17 @@
 | 
				
			|||||||
-- This is an empty migration.
 | 
					-- This is an empty migration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create function now_utc() returns timestamp as $$
 | 
				
			||||||
 | 
					  select now() at time zone 'utc';
 | 
				
			||||||
 | 
					$$ language sql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					create function ASSERT_SERIALIZED() returns void as $$
 | 
				
			||||||
 | 
					BEGIN
 | 
				
			||||||
 | 
					    IF (select current_setting('transaction_isolation') <> 'serializable') THEN
 | 
				
			||||||
 | 
					        RAISE EXCEPTION 'SN_NOT_SERIALIZABLE';
 | 
				
			||||||
 | 
					    END IF;
 | 
				
			||||||
 | 
					END;
 | 
				
			||||||
 | 
					$$ language plpgsql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE OR REPLACE FUNCTION vote(item_id INTEGER, username TEXT, vote_sats INTEGER)
 | 
					CREATE OR REPLACE FUNCTION vote(item_id INTEGER, username TEXT, vote_sats INTEGER)
 | 
				
			||||||
RETURNS INTEGER
 | 
					RETURNS INTEGER
 | 
				
			||||||
LANGUAGE plpgsql
 | 
					LANGUAGE plpgsql
 | 
				
			||||||
@ -8,6 +20,8 @@ DECLARE
 | 
				
			|||||||
    user_id INTEGER;
 | 
					    user_id INTEGER;
 | 
				
			||||||
    user_sats INTEGER;
 | 
					    user_sats INTEGER;
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SELECT (msats / 1000), id INTO user_sats, user_id FROM users WHERE name = username;
 | 
					    SELECT (msats / 1000), id INTO user_sats, user_id FROM users WHERE name = username;
 | 
				
			||||||
    IF vote_sats > user_sats THEN
 | 
					    IF vote_sats > user_sats THEN
 | 
				
			||||||
        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
					        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
				
			||||||
@ -16,12 +30,15 @@ BEGIN
 | 
				
			|||||||
    UPDATE users SET msats = msats - (vote_sats * 1000) WHERE id = user_id;
 | 
					    UPDATE users SET msats = msats - (vote_sats * 1000) WHERE id = user_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    IF EXISTS (SELECT 1 FROM "Vote" WHERE "itemId" = item_id AND "userId" = user_id) THEN
 | 
					    IF EXISTS (SELECT 1 FROM "Vote" WHERE "itemId" = item_id AND "userId" = user_id) THEN
 | 
				
			||||||
        INSERT INTO "Vote" (sats, "itemId", "userId", boost, updated_at) VALUES (vote_sats, item_id, user_id, true, 'now');
 | 
					        INSERT INTO "Vote" (sats, "itemId", "userId", boost, created_at, updated_at)
 | 
				
			||||||
 | 
					        VALUES (vote_sats, item_id, user_id, true, now_utc(), now_utc());
 | 
				
			||||||
    ELSE
 | 
					    ELSE
 | 
				
			||||||
        INSERT INTO "Vote" (sats, "itemId", "userId", updated_at) VALUES (1, item_id, user_id, 'now');
 | 
					        INSERT INTO "Vote" (sats, "itemId", "userId", created_at, updated_at)
 | 
				
			||||||
 | 
					        VALUES (1, item_id, user_id, now_utc(), now_utc());
 | 
				
			||||||
        UPDATE users SET msats = msats + 1000 WHERE id = (SELECT "userId" FROM "Item" WHERE id = item_id);
 | 
					        UPDATE users SET msats = msats + 1000 WHERE id = (SELECT "userId" FROM "Item" WHERE id = item_id);
 | 
				
			||||||
        IF vote_sats > 1 THEN
 | 
					        IF vote_sats > 1 THEN
 | 
				
			||||||
            INSERT INTO "Vote" (sats, "itemId", "userId", boost, updated_at) VALUES (vote_sats - 1, item_id, user_id, true, 'now');
 | 
					            INSERT INTO "Vote" (sats, "itemId", "userId", boost, created_at, updated_at)
 | 
				
			||||||
 | 
					            VALUES (vote_sats - 1, item_id, user_id, true, now_utc(), now_utc());
 | 
				
			||||||
        END IF;
 | 
					        END IF;
 | 
				
			||||||
    END IF;
 | 
					    END IF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,6 +55,8 @@ DECLARE
 | 
				
			|||||||
    user_sats INTEGER;
 | 
					    user_sats INTEGER;
 | 
				
			||||||
    item "Item";
 | 
					    item "Item";
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SELECT (msats / 1000), id INTO user_sats, user_id FROM users WHERE name = username;
 | 
					    SELECT (msats / 1000), id INTO user_sats, user_id FROM users WHERE name = username;
 | 
				
			||||||
    IF 1 > user_sats THEN
 | 
					    IF 1 > user_sats THEN
 | 
				
			||||||
        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
					        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
				
			||||||
@ -45,9 +64,11 @@ BEGIN
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    UPDATE users SET msats = msats - 1000 WHERE id = user_id;
 | 
					    UPDATE users SET msats = msats - 1000 WHERE id = user_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    INSERT INTO "Item" (title, url, text, "userId", "parentId", updated_at)
 | 
					    INSERT INTO "Item" (title, url, text, "userId", "parentId", created_at, updated_at)
 | 
				
			||||||
    VALUES (title, url, text, user_id, parent_id, 'now') RETURNING * INTO item;
 | 
					    VALUES (title, url, text, user_id, parent_id, now_utc(), now_utc()) RETURNING * INTO item;
 | 
				
			||||||
    INSERT INTO "Vote" (sats, "itemId", "userId", updated_at) VALUES (1, item.id, user_id, 'now');
 | 
					
 | 
				
			||||||
 | 
					    INSERT INTO "Vote" (sats, "itemId", "userId", created_at, updated_at)
 | 
				
			||||||
 | 
					    VALUES (1, item.id, user_id, now_utc(), now_utc());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RETURN item;
 | 
					    RETURN item;
 | 
				
			||||||
END;
 | 
					END;
 | 
				
			||||||
@ -61,9 +82,12 @@ DECLARE
 | 
				
			|||||||
    user_id INTEGER;
 | 
					    user_id INTEGER;
 | 
				
			||||||
    confirmed_at TIMESTAMP;
 | 
					    confirmed_at TIMESTAMP;
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SELECT "userId", "confirmedAt" INTO user_id, confirmed_at FROM "Invoice" WHERE hash = lnd_id;
 | 
					    SELECT "userId", "confirmedAt" INTO user_id, confirmed_at FROM "Invoice" WHERE hash = lnd_id;
 | 
				
			||||||
    IF confirmed_at IS NULL THEN
 | 
					    IF confirmed_at IS NULL THEN
 | 
				
			||||||
        UPDATE "Invoice" SET "msatsReceived" = lnd_received, "confirmedAt" = 'now'  WHERE hash = lnd_id;
 | 
					        UPDATE "Invoice" SET "msatsReceived" = lnd_received, "confirmedAt" = now_utc(), updated_at = now_utc()
 | 
				
			||||||
 | 
					        WHERE hash = lnd_id;
 | 
				
			||||||
        UPDATE users SET msats = msats + lnd_received WHERE id = user_id;
 | 
					        UPDATE users SET msats = msats + lnd_received WHERE id = user_id;
 | 
				
			||||||
    END IF;
 | 
					    END IF;
 | 
				
			||||||
    RETURN 0;
 | 
					    RETURN 0;
 | 
				
			||||||
 | 
				
			|||||||
@ -7,13 +7,15 @@ DECLARE
 | 
				
			|||||||
    user_msats INTEGER;
 | 
					    user_msats INTEGER;
 | 
				
			||||||
    withdrawl "Withdrawl";
 | 
					    withdrawl "Withdrawl";
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SELECT msats, id INTO user_msats, user_id FROM users WHERE name = username;
 | 
					    SELECT msats, id INTO user_msats, user_id FROM users WHERE name = username;
 | 
				
			||||||
    IF (msats_amount + msats_max_fee) > user_msats THEN
 | 
					    IF (msats_amount + msats_max_fee) > user_msats THEN
 | 
				
			||||||
        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
					        RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
 | 
				
			||||||
    END IF;
 | 
					    END IF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    INSERT INTO "Withdrawl" (hash, bolt11, "msatsPaying", "msatsFeePaying", "userId", updated_at)
 | 
					    INSERT INTO "Withdrawl" (hash, bolt11, "msatsPaying", "msatsFeePaying", "userId", created_at, updated_at)
 | 
				
			||||||
    VALUES (lnd_id, invoice, msats_amount, msats_max_fee, user_id, 'now') RETURNING * INTO withdrawl;
 | 
					    VALUES (lnd_id, invoice, msats_amount, msats_max_fee, user_id, now_utc(), now_utc()) RETURNING * INTO withdrawl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    UPDATE users SET msats = msats - msats_amount - msats_max_fee WHERE id = user_id;
 | 
					    UPDATE users SET msats = msats - msats_amount - msats_max_fee WHERE id = user_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,8 +31,13 @@ DECLARE
 | 
				
			|||||||
    msats_fee_paying INTEGER;
 | 
					    msats_fee_paying INTEGER;
 | 
				
			||||||
    user_id INTEGER;
 | 
					    user_id INTEGER;
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN
 | 
					    IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN
 | 
				
			||||||
        UPDATE "Withdrawl" SET status = 'CONFIRMED', "msatsPaid" = msats_paid, "msatsFeePaid" = msats_fee_paid WHERE id = wid;
 | 
					        UPDATE "Withdrawl"
 | 
				
			||||||
 | 
					        SET status = 'CONFIRMED', "msatsPaid" = msats_paid,
 | 
				
			||||||
 | 
					        "msatsFeePaid" = msats_fee_paid, updated_at = now_utc()
 | 
				
			||||||
 | 
					        WHERE id = wid;
 | 
				
			||||||
        SELECT "msatsFeePaying", "userId" INTO msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid;
 | 
					        SELECT "msatsFeePaying", "userId" INTO msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid;
 | 
				
			||||||
        UPDATE users SET msats = msats + (msats_fee_paying - msats_fee_paid) WHERE id = user_id;
 | 
					        UPDATE users SET msats = msats + (msats_fee_paying - msats_fee_paid) WHERE id = user_id;
 | 
				
			||||||
    END IF;
 | 
					    END IF;
 | 
				
			||||||
@ -48,8 +55,10 @@ DECLARE
 | 
				
			|||||||
    msats_paying INTEGER;
 | 
					    msats_paying INTEGER;
 | 
				
			||||||
    user_id INTEGER;
 | 
					    user_id INTEGER;
 | 
				
			||||||
BEGIN
 | 
					BEGIN
 | 
				
			||||||
 | 
					    PERFORM ASSERT_SERIALIZED();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN
 | 
					    IF EXISTS (SELECT 1 FROM "Withdrawl" WHERE id = wid AND status IS NULL) THEN
 | 
				
			||||||
        UPDATE "Withdrawl" SET status = wstatus WHERE id = wid;
 | 
					        UPDATE "Withdrawl" SET status = wstatus, updated_at = now_utc() WHERE id = wid;
 | 
				
			||||||
        SELECT "msatsPaying", "msatsFeePaying", "userId" INTO msats_paying, msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid;
 | 
					        SELECT "msatsPaying", "msatsFeePaying", "userId" INTO msats_paying, msats_fee_paying, user_id FROM "Withdrawl" WHERE id = wid;
 | 
				
			||||||
        UPDATE users SET msats = msats + msats_paying + msats_fee_paying WHERE id = user_id;
 | 
					        UPDATE users SET msats = msats + msats_paying + msats_fee_paying WHERE id = user_id;
 | 
				
			||||||
    END IF;
 | 
					    END IF;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
const { PrismaClient } = require('@prisma/client')
 | 
					const { PrismaClient } = require('@prisma/client')
 | 
				
			||||||
const { authenticatedLndGrpc, subscribeToInvoices, getInvoice, getPayment } = require('ln-service')
 | 
					const { authenticatedLndGrpc, subscribeToInvoices, getInvoice, getPayment } = require('ln-service')
 | 
				
			||||||
const dotenv = require('dotenv')
 | 
					const dotenv = require('dotenv')
 | 
				
			||||||
 | 
					const serialize = require('../api/resolvers/serial')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dotenv.config({ path: '..' })
 | 
					dotenv.config({ path: '..' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -15,9 +16,11 @@ const models = new PrismaClient()
 | 
				
			|||||||
async function recordInvoiceStatus (inv) {
 | 
					async function recordInvoiceStatus (inv) {
 | 
				
			||||||
  console.log(inv)
 | 
					  console.log(inv)
 | 
				
			||||||
  if (inv.is_confirmed) {
 | 
					  if (inv.is_confirmed) {
 | 
				
			||||||
    await models.$queryRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`
 | 
					    await serialize(models,
 | 
				
			||||||
 | 
					      models.$executeRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`)
 | 
				
			||||||
  } else if (inv.is_canceled) {
 | 
					  } else if (inv.is_canceled) {
 | 
				
			||||||
    // mark as cancelled
 | 
					    // mark as cancelled
 | 
				
			||||||
 | 
					    await serialize(models,
 | 
				
			||||||
      models.invoice.update({
 | 
					      models.invoice.update({
 | 
				
			||||||
        where: {
 | 
					        where: {
 | 
				
			||||||
          hash: inv.id
 | 
					          hash: inv.id
 | 
				
			||||||
@ -25,7 +28,7 @@ async function recordInvoiceStatus (inv) {
 | 
				
			|||||||
        data: {
 | 
					        data: {
 | 
				
			||||||
          cancelled: true
 | 
					          cancelled: true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					      }))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,8 +70,8 @@ async function recordWithdrawlStatus (id, wdrwl) {
 | 
				
			|||||||
    // is this true for getPayment?
 | 
					    // is this true for getPayment?
 | 
				
			||||||
    const fee = Number(wdrwl.payment.fee_mtokens)
 | 
					    const fee = Number(wdrwl.payment.fee_mtokens)
 | 
				
			||||||
    const paid = Number(wdrwl.mtokens) - fee
 | 
					    const paid = Number(wdrwl.mtokens) - fee
 | 
				
			||||||
    await models.$queryRaw`
 | 
					    await serialize(models, models.$executeRaw`
 | 
				
			||||||
      SELECT confirm_withdrawl(${id}, ${paid}, ${fee})`
 | 
					      SELECT confirm_withdrawl(${id}, ${paid}, ${fee})`)
 | 
				
			||||||
  } else if (wdrwl.is_failed) {
 | 
					  } else if (wdrwl.is_failed) {
 | 
				
			||||||
    let status = 'UNKNOWN_FAILURE'
 | 
					    let status = 'UNKNOWN_FAILURE'
 | 
				
			||||||
    if (wdrwl.failed.is_insufficient_balance) {
 | 
					    if (wdrwl.failed.is_insufficient_balance) {
 | 
				
			||||||
@ -80,8 +83,8 @@ async function recordWithdrawlStatus (id, wdrwl) {
 | 
				
			|||||||
    } else if (wdrwl.failed.is_route_not_found) {
 | 
					    } else if (wdrwl.failed.is_route_not_found) {
 | 
				
			||||||
      status = 'ROUTE_NOT_FOUND'
 | 
					      status = 'ROUTE_NOT_FOUND'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await models.$queryRaw`
 | 
					    await serialize(models, models.$executeRaw`
 | 
				
			||||||
      SELECT reverse_withdrawl(${id}, ${status})`
 | 
					      SELECT reverse_withdrawl(${id}, ${status})`)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,7 +93,7 @@ async function checkPendingWithdrawls () {
 | 
				
			|||||||
  const leftovers = await models.withdrawl.findMany({
 | 
					  const leftovers = await models.withdrawl.findMany({
 | 
				
			||||||
    where: {
 | 
					    where: {
 | 
				
			||||||
      createdAt: {
 | 
					      createdAt: {
 | 
				
			||||||
        lt: new Date(new Date().setSeconds(new Date().getSeconds() + 30))
 | 
					        lt: new Date(new Date().setSeconds(new Date().getSeconds() - 30))
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      status: {
 | 
					      status: {
 | 
				
			||||||
        equals: null
 | 
					        equals: null
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user