From cdbd10da4ee45a74080fbb33def46d5b5948e400 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Sat, 21 May 2022 13:36:34 +0300 Subject: [PATCH] feat: build generic voting api, build useVote hook --- functions/graphql/nexus-typegen.ts | 31 +--- functions/graphql/schema.graphql | 14 +- functions/graphql/types/helpers.js | 12 +- functions/graphql/types/vote.js | 169 ++++++++++-------- .../migration.sql | 15 ++ prisma/schema.prisma | 5 +- .../pages/ProjectPage/VoteCard/VoteCard.tsx | 66 ++----- src/graphql/index.tsx | 41 ++--- src/utils/hooks/index.ts | 1 + src/utils/hooks/useVote/index.ts | 1 + src/utils/hooks/useVote/useVote.tsx | 96 ++++++++++ .../hooks/useVote}/vote.graphql | 12 +- 12 files changed, 243 insertions(+), 220 deletions(-) create mode 100644 prisma/migrations/20220521073620_make_vote_generic/migration.sql create mode 100644 src/utils/hooks/useVote/index.ts create mode 100644 src/utils/hooks/useVote/useVote.tsx rename src/{features/Projects/pages/ProjectPage/VoteCard => utils/hooks/useVote}/vote.graphql (58%) diff --git a/functions/graphql/nexus-typegen.ts b/functions/graphql/nexus-typegen.ts index af20d9f..fe39635 100644 --- a/functions/graphql/nexus-typegen.ts +++ b/functions/graphql/nexus-typegen.ts @@ -32,7 +32,7 @@ export interface NexusGenInputs { export interface NexusGenEnums { POST_TYPE: "Bounty" | "Question" | "Story" - VOTE_ITEM_TYPE: "Bounty" | "Comment" | "Project" | "Question" | "Story" | "User" + VOTE_ITEM_TYPE: "Bounty" | "PostComment" | "Project" | "Question" | "Story" | "User" } export interface NexusGenScalars { @@ -137,13 +137,6 @@ export interface NexusGenObjects { name: string; // String! } Vote: { // root type - amount_in_sat: number; // Int! - id: number; // Int! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - } - Vote2: { // root type amount_in_sat: number; // Int! id: number; // Int! item_id: number; // Int! @@ -214,7 +207,6 @@ export interface NexusGenFieldTypes { Mutation: { // field return type confirmVote: NexusGenRootTypes['Vote']; // Vote! vote: NexusGenRootTypes['Vote']; // Vote! - vote2: NexusGenRootTypes['Vote2']; // Vote2! } PostComment: { // field return type author: NexusGenRootTypes['User']; // User! @@ -298,14 +290,6 @@ export interface NexusGenFieldTypes { name: string; // String! } Vote: { // field return type - amount_in_sat: number; // Int! - id: number; // Int! - paid: boolean; // Boolean! - payment_hash: string; // String! - payment_request: string; // String! - project: NexusGenRootTypes['Project']; // Project! - } - Vote2: { // field return type amount_in_sat: number; // Int! id: number; // Int! item_id: number; // Int! @@ -372,7 +356,6 @@ export interface NexusGenFieldTypeNames { Mutation: { // field return type name confirmVote: 'Vote' vote: 'Vote' - vote2: 'Vote2' } PostComment: { // field return type name author: 'User' @@ -456,14 +439,6 @@ export interface NexusGenFieldTypeNames { name: 'String' } Vote: { // field return type name - amount_in_sat: 'Int' - id: 'Int' - paid: 'Boolean' - payment_hash: 'String' - payment_request: 'String' - project: 'Project' - } - Vote2: { // field return type name amount_in_sat: 'Int' id: 'Int' item_id: 'Int' @@ -489,10 +464,6 @@ export interface NexusGenArgTypes { preimage: string; // String! } vote: { // args - amount_in_sat: number; // Int! - project_id: number; // Int! - } - vote2: { // args amount_in_sat: number; // Int! item_id: number; // Int! item_type: NexusGenEnums['VOTE_ITEM_TYPE']; // VOTE_ITEM_TYPE! diff --git a/functions/graphql/schema.graphql b/functions/graphql/schema.graphql index 0a27383..9a4a7d3 100644 --- a/functions/graphql/schema.graphql +++ b/functions/graphql/schema.graphql @@ -56,8 +56,7 @@ type LnurlDetails { type Mutation { confirmVote(payment_request: String!, preimage: String!): Vote! - vote(amount_in_sat: Int!, project_id: Int!): Vote! - vote2(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote2! + vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! } enum POST_TYPE { @@ -168,7 +167,7 @@ type User { enum VOTE_ITEM_TYPE { Bounty - Comment + PostComment Project Question Story @@ -176,15 +175,6 @@ enum VOTE_ITEM_TYPE { } type Vote { - amount_in_sat: Int! - id: Int! - paid: Boolean! - payment_hash: String! - payment_request: String! - project: Project! -} - -type Vote2 { amount_in_sat: Int! id: Int! item_id: Int! diff --git a/functions/graphql/types/helpers.js b/functions/graphql/types/helpers.js index 4708be5..ece6a9f 100644 --- a/functions/graphql/types/helpers.js +++ b/functions/graphql/types/helpers.js @@ -34,7 +34,8 @@ async function getLnurlCallbackUrl(lightning_address) { ); } -async function getPaymetRequestForProject(project, amount_in_sat) { + +async function getPaymetRequestForItem(lightning_address, amount_in_sat) { // # NOTE: CACHING LNURL CALLBACK URLS + PARAMETERS // LNURL flows have a lot of back and forth and can impact // the load time for your application users. @@ -45,11 +46,9 @@ async function getPaymetRequestForProject(project, amount_in_sat) { // careful when trying to optimise the amount of // requests so be mindful of this when you are storing // these items. - let lnurlCallbackUrl = project.lnurl_callback_url; + const amount = amount_in_sat * 1000; // msats - if (!lnurlCallbackUrl) { - lnurlCallbackUrl = await getLnurlCallbackUrl(project.lightning_address); - } + let lnurlCallbackUrl = await getLnurlCallbackUrl(lightning_address); return axios .get(lnurlCallbackUrl, { params: { amount } }) .then((prResponse) => { @@ -69,10 +68,9 @@ const paginationArgs = (args) => { } module.exports = { - getPaymetRequestForProject, + getPaymetRequestForItem, hexToUint8Array, lightningAddressToLnurl, getLnurlDetails, - paginationArgs } diff --git a/functions/graphql/types/vote.js b/functions/graphql/types/vote.js index d46b58b..9fd7d7b 100644 --- a/functions/graphql/types/vote.js +++ b/functions/graphql/types/vote.js @@ -8,7 +8,7 @@ const { enumType, } = require('nexus') const { parsePaymentRequest } = require('invoices'); -const { getPaymetRequestForProject, hexToUint8Array } = require('./helpers'); +const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers'); const { createHash } = require('crypto'); const { prisma } = require('../prisma') @@ -16,9 +16,10 @@ const { prisma } = require('../prisma') // the types of items we can vote to const VOTE_ITEM_TYPE = enumType({ name: 'VOTE_ITEM_TYPE', - members: ['Story', 'Bounty', 'Question', 'Project', 'User', 'Comment'], + members: ['Story', 'Bounty', 'Question', 'Project', 'User', 'PostComment'], }) + const Vote = objectType({ name: 'Vote', definition(t) { @@ -28,26 +29,6 @@ const Vote = objectType({ t.nonNull.string('payment_hash'); t.nonNull.boolean('paid'); - t.nonNull.field('project', { - type: "Project", - resolve: (parent, args,) => { - return parent.project ?? prisma.vote.findUnique({ - where: { id: parent.id } - }).project() - } - }) - } -}) - -const Vote2 = objectType({ - name: 'Vote2', - definition(t) { - t.nonNull.int('id'); - t.nonNull.int('amount_in_sat'); - t.nonNull.string('payment_request'); - t.nonNull.string('payment_hash'); - t.nonNull.boolean('paid'); - t.nonNull.field('item_type', { type: "VOTE_ITEM_TYPE" }) @@ -67,46 +48,78 @@ const LnurlDetails = objectType({ } }) -// This is the old voting mutation, it can only vote for projects (SHOULD BE REPLACED BY THE NEW VOTE MUTATION WHEN THAT ONE IS WORKING) + + + +const getModalOfType = (type) => { + switch (type) { + case "Story": + return prisma.story; + case "Question": + return prisma.question; + case "Project": + return prisma.project; + case "Comment": + return prisma.postComment; + default: + return null; + } +} + +const getLightningAddress = async (item_id, item_type) => { + switch (item_type) { + case "Story": + return prisma.story.findUnique({ + where: { id: item_id }, include: { + user: { + select: { + lightning_address: true + } + } + } + }).then(data => data.user.lightning_address); + case "Question": + return prisma.question.findUnique({ + where: { id: item_id }, include: { + user: { + select: { + lightning_address: true + } + } + } + }).then(data => data.user.lightning_address); + case "Project": + return prisma.project.findUnique({ + where: { id: item_id }, + select: { + lightning_address: true + } + }).then(data => data.lightning_address); + case "Comment": + return prisma.postComment.findUnique({ + where: { id: item_id }, include: { + user: { + select: { + lightning_address: true + } + } + } + }).then(data => data.user.lightning_address); + default: + return null; + } +} + +// type => modal +// type => lightning address (pr) + + +// This is the new voting mutation, it can vote for any type of item that we define in the VOTE_ITEM_TYPE enum const voteMutation = extendType({ type: "Mutation", definition(t) { t.nonNull.field('vote', { type: "Vote", - args: { - project_id: nonNull(intArg()), - amount_in_sat: nonNull(intArg()) - }, - resolve: async (_, args) => { - const project = await prisma.project.findUnique({ - where: { id: args.project_id }, - }); - const pr = await getPaymetRequestForProject(project, args.amount_in_sat); - const invoice = parsePaymentRequest({ request: pr }); - return prisma.vote.create({ - data: { - project_id: project.id, - amount_in_sat: args.amount_in_sat, - payment_request: pr, - payment_hash: invoice.id, - }, - include: { - project: true - } - }); - } - }) - } -}) - - - -// This is the new voting mutation, it can vote for any type of item that we define in the VOTE_ITEM_TYPE enum -const vote2Mutation = extendType({ - type: "Mutation", - definition(t) { - t.nonNull.field('vote2', { - type: "Vote2", args: { item_type: arg({ type: nonNull("VOTE_ITEM_TYPE") @@ -117,20 +130,23 @@ const vote2Mutation = extendType({ resolve: async (_, args) => { const { item_id, item_type, amount_in_sat } = args; + const lightning_address = getLightningAddress(item_id, item_type) + const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat); + const invoice = parsePaymentRequest({ request: pr }); - // Create the invoice here according to it's type & get a payment request and a payment hash + // #TODO remove votes rows that get added but not confirmed after some time + // maybe using a scheduler, timeout, or whatever mean available - return { - id: 111, - amount_in_sat: amount_in_sat, - payment_request: '{{payment_request}}', - payment_hash: '{{payment_hash}}', - paid: true, - item_type: item_type, - item_id: item_id, - } + return prisma.vote.create({ + data: { + item_type: item_type, + item_id: item_id, + amount_in_sat: amount_in_sat, + payment_request: pr, + payment_hash: invoice.id, + } + }); } - }) } }) @@ -156,17 +172,19 @@ const confirmVoteMutation = extendType({ payment_hash: paymentHash, }, }); + // if we find a vote it means the preimage is correct and we update the vote and mark it as paid // can we write this nicer? if (vote) { - const project = await prisma.project.findUnique({ - where: { id: vote.project_id }, + const modal = getModalOfType(vote.item_type); + const item = await modal.findUnique({ + where: { id: vote.item_id }, }); // count up votes cache - await prisma.project.update({ - where: { id: project.id }, + await modal.update({ + where: { id: item.id }, data: { - votes_count: project.votes_count + vote.amount_in_sat, + votes_count: item.votes_count + vote.amount_in_sat, }, }); // return the current vote @@ -175,9 +193,6 @@ const confirmVoteMutation = extendType({ data: { paid: true, preimage: args.preimage, - }, - include: { - project: true } }); } else { @@ -194,11 +209,9 @@ module.exports = { // Types Vote, - Vote2, LnurlDetails, // Mutations voteMutation, - vote2Mutation, confirmVoteMutation } \ No newline at end of file diff --git a/prisma/migrations/20220521073620_make_vote_generic/migration.sql b/prisma/migrations/20220521073620_make_vote_generic/migration.sql new file mode 100644 index 0000000..e31c22b --- /dev/null +++ b/prisma/migrations/20220521073620_make_vote_generic/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `project_id` on the `Vote` table. All the data in the column will be lost. + - Added the required column `item_id` to the `Vote` table without a default value. This is not possible if the table is not empty. + - Added the required column `item_type` to the `Vote` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Vote" DROP CONSTRAINT "Vote_project_id_fkey"; + +-- AlterTable +ALTER TABLE "Vote" DROP COLUMN "project_id", +ADD COLUMN "item_id" INTEGER NOT NULL, +ADD COLUMN "item_type" TEXT NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 96b27cb..835f2f8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,8 +22,8 @@ model Tag { model Vote { id Int @id @default(autoincrement()) - project Project @relation(fields: [project_id], references: [id]) - project_id Int + item_id Int + item_type String amount_in_sat Int payment_request String? payment_hash String? @@ -73,7 +73,6 @@ model Project { category Category @relation(fields: [category_id], references: [id]) category_id Int votes_count Int @default(0) - vote Vote[] createdAt DateTime @default(now()) awards Award[] diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx index 70c91d4..c625ffa 100644 --- a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx +++ b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx @@ -3,11 +3,10 @@ import React, { FormEvent, useState } from 'react'; import { AiFillThunderbolt } from 'react-icons/ai' import { IoClose } from 'react-icons/io5' import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'; -import { useAppSelector } from 'src/utils/hooks'; +import { PaymentStatus, useVote } from 'src/utils/hooks'; import Confetti from "react-confetti"; -import { Wallet_Service } from 'src/services'; import { useWindowSize } from '@react-hookz/web'; -import { useConfirmVoteMutation, useVoteMutation } from 'src/graphql'; +import { Vote_Item_Type } from 'src/graphql'; const defaultOptions = [ { text: '100 sat', value: 100 }, @@ -16,17 +15,6 @@ const defaultOptions = [ ] -enum PaymentStatus { - DEFAULT, - FETCHING_PAYMENT_DETAILS, - PAID, - AWAITING_PAYMENT, - PAYMENT_CONFIRMED, - NOT_PAID, - CANCELED -} - - interface Props extends ModalCard { projectId: number; initVotes?: number; @@ -35,57 +23,24 @@ interface Props extends ModalCard { export default function VoteCard({ onClose, direction, projectId, initVotes, ...props }: Props) { const { width, height } = useWindowSize() - const { isWalletConnected } = useAppSelector(state => ({ - isWalletConnected: state.wallet.isConnected, - initVotes: state.vote.voteAmount, - projectId: state.project.openId - })); const [selectedOption, setSelectedOption] = useState(10); const [voteAmount, setVoteAmount] = useState(initVotes ?? 10); - const [paymentStatus, setPaymentStatus] = useState(PaymentStatus.DEFAULT); - - const [vote, { data }] = useVoteMutation({ - onCompleted: async (votingData) => { - try { - setPaymentStatus(PaymentStatus.AWAITING_PAYMENT); - const webln = await Wallet_Service.getWebln() - const paymentResponse = await webln.sendPayment(votingData.vote.payment_request); - setPaymentStatus(PaymentStatus.PAID); - confirmVote({ - variables: { - paymentRequest: votingData.vote.payment_request, - preimage: paymentResponse.preimage - } - }) - } catch (error) { - console.log(error); - setPaymentStatus(PaymentStatus.NOT_PAID); - } - + const { vote, paymentStatus } = useVote({ + onSuccess: () => { + setTimeout(() => { + onClose?.(); + }, 4000); }, - onError: (error) => { - console.log(error); - alert("Something wrong happened...") - setPaymentStatus(PaymentStatus.NOT_PAID); + onError: () => { setTimeout(() => { onClose?.(); }, 4000); } - }); + }) - const [confirmVote, { data: confirmedVoteData }] = useConfirmVoteMutation({ - onCompleted: (votingData) => { - setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED); - setTimeout(() => { - onClose?.(); - }, 4000); - }, - onError: () => { } - - }); const onChangeInput = (event: React.ChangeEvent) => { setSelectedOption(-1); @@ -99,8 +54,7 @@ export default function VoteCard({ onClose, direction, projectId, initVotes, ... const requestPayment = (e: FormEvent) => { e.preventDefault(); - setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS); - vote({ variables: { "amountInSat": voteAmount, "projectId": projectId! } }); + vote({ variables: { "amountInSat": voteAmount, "itemId": projectId!, itemType: Vote_Item_Type.Project } }); } return ( diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index a292181..180c50a 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -75,7 +75,6 @@ export type Mutation = { __typename?: 'Mutation'; confirmVote: Vote; vote: Vote; - vote2: Vote2; }; @@ -86,12 +85,6 @@ export type MutationConfirmVoteArgs = { export type MutationVoteArgs = { - amount_in_sat: Scalars['Int']; - project_id: Scalars['Int']; -}; - - -export type MutationVote2Args = { amount_in_sat: Scalars['Int']; item_id: Scalars['Int']; item_type: Vote_Item_Type; @@ -274,7 +267,7 @@ export type User = { export enum Vote_Item_Type { Bounty = 'Bounty', - Comment = 'Comment', + PostComment = 'PostComment', Project = 'Project', Question = 'Question', Story = 'Story', @@ -285,16 +278,6 @@ export type Vote = { __typename?: 'Vote'; amount_in_sat: Scalars['Int']; id: Scalars['Int']; - paid: Scalars['Boolean']; - payment_hash: Scalars['String']; - payment_request: Scalars['String']; - project: Project; -}; - -export type Vote2 = { - __typename?: 'Vote2'; - amount_in_sat: Scalars['Int']; - id: Scalars['Int']; item_id: Scalars['Int']; item_type: Vote_Item_Type; paid: Scalars['Boolean']; @@ -372,12 +355,13 @@ export type ProjectDetailsQueryVariables = Exact<{ export type ProjectDetailsQuery = { __typename?: 'Query', getProject: { __typename?: 'Project', id: number, title: string, description: string, cover_image: string, thumbnail_image: string, screenshots: Array, website: string, lightning_address: string | null, lnurl_callback_url: string | null, votes_count: number, category: { __typename?: 'Category', id: number, title: string }, awards: Array<{ __typename?: 'Award', title: string, image: string, url: string, id: number }>, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } }; export type VoteMutationVariables = Exact<{ - projectId: Scalars['Int']; + itemType: Vote_Item_Type; + itemId: Scalars['Int']; amountInSat: Scalars['Int']; }>; -export type VoteMutation = { __typename?: 'Mutation', vote: { __typename?: 'Vote', id: number, amount_in_sat: number, payment_request: string, payment_hash: string, paid: boolean } }; +export type VoteMutation = { __typename?: 'Mutation', vote: { __typename?: 'Vote', id: number, amount_in_sat: number, payment_request: string, payment_hash: string, paid: boolean, item_type: Vote_Item_Type, item_id: number } }; export type ConfirmVoteMutationVariables = Exact<{ paymentRequest: Scalars['String']; @@ -385,7 +369,7 @@ export type ConfirmVoteMutationVariables = Exact<{ }>; -export type ConfirmVoteMutation = { __typename?: 'Mutation', confirmVote: { __typename?: 'Vote', id: number, amount_in_sat: number, payment_request: string, payment_hash: string, paid: boolean, project: { __typename?: 'Project', id: number, votes_count: number } } }; +export type ConfirmVoteMutation = { __typename?: 'Mutation', confirmVote: { __typename?: 'Vote', id: number, amount_in_sat: number, payment_request: string, payment_hash: string, paid: boolean, item_type: Vote_Item_Type, item_id: number } }; export const NavCategoriesDocument = gql` @@ -1029,13 +1013,15 @@ export type ProjectDetailsQueryHookResult = ReturnType; export type ProjectDetailsQueryResult = Apollo.QueryResult; export const VoteDocument = gql` - mutation Vote($projectId: Int!, $amountInSat: Int!) { - vote(project_id: $projectId, amount_in_sat: $amountInSat) { + mutation Vote($itemType: VOTE_ITEM_TYPE!, $itemId: Int!, $amountInSat: Int!) { + vote(item_type: $itemType, item_id: $itemId, amount_in_sat: $amountInSat) { id amount_in_sat payment_request payment_hash paid + item_type + item_id } } `; @@ -1054,7 +1040,8 @@ export type VoteMutationFn = Apollo.MutationFunction void + onError?: (error: any) => void +}) => { + + const [paymentStatus, setPaymentStatus] = useState(PaymentStatus.DEFAULT); + + const [voteMutaion] = useVoteMutation({ + onCompleted: async (votingData) => { + try { + setPaymentStatus(PaymentStatus.AWAITING_PAYMENT); + const webln = await Wallet_Service.getWebln() + const paymentResponse = await webln.sendPayment(votingData.vote.payment_request); + setPaymentStatus(PaymentStatus.PAID); + confirmVote({ + variables: { + paymentRequest: votingData.vote.payment_request, + preimage: paymentResponse.preimage + } + }) + } catch (error) { + console.log(error); + setPaymentStatus(PaymentStatus.NOT_PAID); + } + + }, + onError: (error) => { + console.log(error); + alert("Something wrong happened...") + setPaymentStatus(PaymentStatus.NOT_PAID); + onError?.(error) + } + }); + + const [confirmVote] = useConfirmVoteMutation({ + onCompleted: () => { + setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED); + onSuccess?.(); + }, + update(cache, { data }) { + try { + const { item_id, item_type, amount_in_sat } = data!.confirmVote; + const { votes_count } = cache.readFragment({ + id: `${item_type}:${item_id}`, + fragment: gql` + fragment My${item_type} on ${item_type} { + votes_count + }` + }) ?? {}; + cache.writeFragment({ + id: `${item_type}:${item_id}`, + fragment: gql` + fragment My${item_type} on ${item_type} { + votes_count + } + `, + data: { + votes_count: votes_count + amount_in_sat + }, + }) + } catch (error) { + + } + }, + + onError: () => { } + + }); + + + const vote = (...params: Parameters) => { + setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS) + voteMutaion(...params) + } + return { + paymentStatus, + vote + } +} \ No newline at end of file diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/vote.graphql b/src/utils/hooks/useVote/vote.graphql similarity index 58% rename from src/features/Projects/pages/ProjectPage/VoteCard/vote.graphql rename to src/utils/hooks/useVote/vote.graphql index 2fa0254..0134583 100644 --- a/src/features/Projects/pages/ProjectPage/VoteCard/vote.graphql +++ b/src/utils/hooks/useVote/vote.graphql @@ -1,10 +1,12 @@ -mutation Vote($projectId: Int!, $amountInSat: Int!) { - vote(project_id: $projectId, amount_in_sat: $amountInSat) { +mutation Vote($itemType: VOTE_ITEM_TYPE!, $itemId: Int!, $amountInSat: Int!) { + vote(item_type: $itemType, item_id: $itemId, amount_in_sat: $amountInSat) { id amount_in_sat payment_request payment_hash paid + item_type + item_id } } @@ -15,9 +17,7 @@ mutation ConfirmVote($paymentRequest: String!, $preimage: String!) { payment_request payment_hash paid - project { - id - votes_count - } + item_type + item_id } }