feat: build dontaions table & api

This commit is contained in:
MTG2000
2022-05-24 17:41:26 +03:00
parent b743b90485
commit 8b94788b13
12 changed files with 419 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
const BOLT_FUN_LIGHTNING_ADDRESS = 'johns@getalby.com'; // #TODO, replace it by bolt-fun lightning address if there exist one
module.exports = {
BOLT_FUN_LIGHTNING_ADDRESS,
}

View File

@@ -76,6 +76,20 @@ export interface NexusGenObjects {
id: number; // Int!
title: string; // String!
}
Donation: { // root type
amount: number; // Int!
createdAt: NexusGenScalars['Date']; // Date!
id: number; // Int!
paid: boolean; // Boolean!
payment_hash: string; // String!
payment_request: string; // String!
}
DonationsStats: { // root type
applications: number; // Int!
donations: number; // Int!
prizes: number; // Int!
touranments: number; // Int!
}
Hackathon: { // root type
cover_image: string; // String!
description: string; // String!
@@ -208,6 +222,21 @@ export interface NexusGenFieldTypes {
title: string; // String!
votes_sum: number; // Int!
}
Donation: { // field return type
amount: number; // Int!
by: NexusGenRootTypes['User'] | null; // User
createdAt: NexusGenScalars['Date']; // Date!
id: number; // Int!
paid: boolean; // Boolean!
payment_hash: string; // String!
payment_request: string; // String!
}
DonationsStats: { // field return type
applications: number; // Int!
donations: number; // Int!
prizes: number; // Int!
touranments: number; // Int!
}
Hackathon: { // field return type
cover_image: string; // String!
description: string; // String!
@@ -226,7 +255,9 @@ export interface NexusGenFieldTypes {
minSendable: number | null; // Int
}
Mutation: { // field return type
confirmDonation: NexusGenRootTypes['Donation']; // Donation!
confirmVote: NexusGenRootTypes['Vote']; // Vote!
donate: NexusGenRootTypes['Donation']; // Donation!
vote: NexusGenRootTypes['Vote']; // Vote!
}
PostComment: { // field return type
@@ -258,6 +289,7 @@ export interface NexusGenFieldTypes {
allTopics: NexusGenRootTypes['Topic'][]; // [Topic!]!
getAllHackathons: NexusGenRootTypes['Hackathon'][]; // [Hackathon!]!
getCategory: NexusGenRootTypes['Category']; // Category!
getDonationsStats: NexusGenRootTypes['DonationsStats'][]; // [DonationsStats!]!
getFeed: NexusGenRootTypes['Post'][]; // [Post!]!
getLnurlDetailsForProject: NexusGenRootTypes['LnurlDetails']; // LnurlDetails!
getPostById: NexusGenRootTypes['Post']; // Post!
@@ -369,6 +401,21 @@ export interface NexusGenFieldTypeNames {
title: 'String'
votes_sum: 'Int'
}
Donation: { // field return type name
amount: 'Int'
by: 'User'
createdAt: 'Date'
id: 'Int'
paid: 'Boolean'
payment_hash: 'String'
payment_request: 'String'
}
DonationsStats: { // field return type name
applications: 'Int'
donations: 'Int'
prizes: 'Int'
touranments: 'Int'
}
Hackathon: { // field return type name
cover_image: 'String'
description: 'String'
@@ -387,7 +434,9 @@ export interface NexusGenFieldTypeNames {
minSendable: 'Int'
}
Mutation: { // field return type name
confirmDonation: 'Donation'
confirmVote: 'Vote'
donate: 'Donation'
vote: 'Vote'
}
PostComment: { // field return type name
@@ -419,6 +468,7 @@ export interface NexusGenFieldTypeNames {
allTopics: 'Topic'
getAllHackathons: 'Hackathon'
getCategory: 'Category'
getDonationsStats: 'DonationsStats'
getFeed: 'Post'
getLnurlDetailsForProject: 'LnurlDetails'
getPostById: 'Post'
@@ -493,10 +543,17 @@ export interface NexusGenFieldTypeNames {
export interface NexusGenArgTypes {
Mutation: {
confirmDonation: { // args
payment_request: string; // String!
preimage: string; // String!
}
confirmVote: { // args
payment_request: string; // String!
preimage: string; // String!
}
donate: { // args
amount_in_sat: number; // Int!
}
vote: { // args
amount_in_sat: number; // Int!
item_id: number; // Int!

View File

@@ -47,6 +47,23 @@ type Category {
"""Date custom scalar type"""
scalar Date
type Donation {
amount: Int!
by: User
createdAt: Date!
id: Int!
paid: Boolean!
payment_hash: String!
payment_request: String!
}
type DonationsStats {
applications: Int!
donations: Int!
prizes: Int!
touranments: Int!
}
type Hackathon {
cover_image: String!
description: String!
@@ -67,7 +84,9 @@ type LnurlDetails {
}
type Mutation {
confirmDonation(payment_request: String!, preimage: String!): Donation!
confirmVote(payment_request: String!, preimage: String!): Vote!
donate(amount_in_sat: Int!): Donation!
vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote!
}
@@ -119,6 +138,7 @@ type Query {
allTopics: [Topic!]!
getAllHackathons(sortBy: String, topic: Int): [Hackathon!]!
getCategory(id: Int!): Category!
getDonationsStats: [DonationsStats!]!
getFeed(skip: Int = 0, sortBy: String = "all", take: Int = 10, topic: Int = 0): [Post!]!
getLnurlDetailsForProject(project_id: Int!): LnurlDetails!
getPostById(id: Int!, type: POST_TYPE!): Post!

View File

@@ -0,0 +1,150 @@
const { createHash } = require('crypto');
const { parsePaymentRequest } = require('invoices');
const {
intArg,
objectType,
stringArg,
extendType,
nonNull,
} = require('nexus');
const { BOLT_FUN_LIGHTNING_ADDRESS } = require('../helpers/consts');
const { prisma } = require('../prisma');
const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers');
const Donation = objectType({
name: 'Donation',
definition(t) {
t.nonNull.int('id');
t.nonNull.int('amount');
t.nonNull.date('createdAt');
t.nonNull.string('payment_request');
t.nonNull.string('payment_hash');
t.nonNull.boolean('paid');
t.field('by', {
type: 'User',
resolve: (parent) => {
return prisma.donation.findUnique({ where: { id: parent.id } }).donor();
}
});
}
})
const donateMutation = extendType({
type: "Mutation",
definition(t) {
t.nonNull.field('donate', {
type: "Donation",
args: {
amount_in_sat: nonNull(intArg())
},
resolve: async (_, args) => {
const { amount_in_sat } = args;
const lightning_address = BOLT_FUN_LIGHTNING_ADDRESS;
const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat);
const invoice = parsePaymentRequest({ request: pr });
return prisma.donation.create({
data: {
amount: amount_in_sat,
payment_request: pr,
payment_hash: invoice.id,
}
});
}
})
}
})
const confirmDonateMutation = extendType({
type: "Mutation",
definition(t) {
t.nonNull.field('confirmDonation', {
type: "Donation",
args: {
payment_request: nonNull(stringArg()),
preimage: nonNull(stringArg())
},
resolve: async (_, args) => {
const paymentHash = createHash("sha256")
.update(hexToUint8Array(args.preimage))
.digest("hex");
// look for a vote for the payment request and the calculated payment hash
const donation = await prisma.donation.findFirst({
where: {
payment_request: args.payment_request,
payment_hash: paymentHash,
},
});
// if we find a donation it means the preimage is correct and we update the donation and mark it as paid
// can we write this nicer?
if (donation) {
// return the current donation
return prisma.donation.update({
where: { id: donation.id },
data: {
paid: true,
preimage: args.preimage,
}
});
} else {
throw new Error("Invalid preimage");
}
}
})
}
})
const DonationsStats = objectType({
name: 'DonationsStats',
definition(t) {
t.nonNull.int("prizes");
t.nonNull.int("touranments");
t.nonNull.int("donations");
t.nonNull.int("applications");
},
})
const getDonationsStats = extendType({
type: "Query",
definition(t) {
t.nonNull.list.nonNull.field('getDonationsStats', {
type: "DonationsStats",
resolve() {
return {
prizes: 2600,
touranments: 2,
donations: prisma.donation.aggregate({
_sum: {
amount: true
},
where: {
paid: true
}
}),
applications: prisma.project.count()
}
}
})
}
})
module.exports = {
// Types
Donation,
DonationsStats,
// Queries
donateMutation,
confirmDonateMutation,
getDonationsStats,
}

View File

@@ -5,6 +5,7 @@ const vote = require('./vote')
const post = require('./post')
const users = require('./users')
const hackathon = require('./hackathon')
const donation = require('./donation')
module.exports = {
...scalars,
@@ -13,5 +14,6 @@ module.exports = {
...vote,
...post,
...users,
...hackathon
...hackathon,
...donation,
}

View File

@@ -10,7 +10,8 @@ const {
const { parsePaymentRequest } = require('invoices');
const { getPaymetRequestForItem, hexToUint8Array } = require('./helpers');
const { createHash } = require('crypto');
const { prisma } = require('../prisma')
const { prisma } = require('../prisma');
const { BOLT_FUN_LIGHTNING_ADDRESS } = require('../helpers/consts');
// the types of items we can vote to
@@ -18,7 +19,6 @@ const VOTE_ITEM_TYPE = enumType({
name: 'VOTE_ITEM_TYPE',
members: ['Story', 'Bounty', 'Question', 'Project', 'User', 'PostComment'],
})
const BOLT_FUN_LIGHTNING_ADDRESS = 'johns@getalby.com'; // #TODO, replace it by bolt-fun lightning address if there exist one
const Vote = objectType({
@@ -133,7 +133,6 @@ const voteMutation = extendType({
const { item_id, item_type, amount_in_sat } = args;
const lightning_address = (await getLightningAddress(item_id, item_type)) ?? BOLT_FUN_LIGHTNING_ADDRESS;
const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat);
console.log(pr);
const invoice = parsePaymentRequest({ request: pr });
// #TODO remove votes rows that get added but not confirmed after some time

View File

@@ -0,0 +1,16 @@
-- CreateTable
CREATE TABLE "Donation" (
"id" SERIAL NOT NULL,
"amount" INTEGER NOT NULL,
"createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
"payment_request" TEXT,
"payment_hash" TEXT,
"preimage" TEXT,
"paid" BOOLEAN NOT NULL DEFAULT false,
"donor_id" INTEGER,
CONSTRAINT "Donation_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Donation" ADD CONSTRAINT "Donation_donor_id_fkey" FOREIGN KEY ("donor_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -44,6 +44,7 @@ model User {
stories Story[]
questions Question[]
posts_comments PostComment[]
donations Donation[]
}
// -----------------
@@ -182,3 +183,19 @@ model Hackathon {
topics Topic[]
}
// -----------------
// Donations
// -----------------
model Donation {
id Int @id @default(autoincrement())
amount Int
createdAt DateTime @default(now()) @db.Date
payment_request String?
payment_hash String?
preimage String?
paid Boolean @default(false)
donor User? @relation(fields: [donor_id], references: [id])
donor_id Int?
}

View File

@@ -0,0 +1 @@
export * from './pages/DonationsPage'

View File

@@ -0,0 +1,12 @@
export default function HackathonsPage() {
return (
<div
className={`page-container pt-16 w-full`}
>
</div>
)
}

View File

@@ -0,0 +1,16 @@
mutation Donate($amountInSat: Int!) {
donate(amount_in_sat: $amountInSat) {
id
amount
payment_request
payment_hash
}
}
mutation ConfirmDonation($paymentRequest: String!, $preimage: String!) {
confirmDonation(payment_request: $paymentRequest, preimage: $preimage) {
id
amount
paid
}
}

View File

@@ -63,6 +63,25 @@ export type Category = {
votes_sum: Scalars['Int'];
};
export type Donation = {
__typename?: 'Donation';
amount: Scalars['Int'];
by: Maybe<User>;
createdAt: Scalars['Date'];
id: Scalars['Int'];
paid: Scalars['Boolean'];
payment_hash: Scalars['String'];
payment_request: Scalars['String'];
};
export type DonationsStats = {
__typename?: 'DonationsStats';
applications: Scalars['Int'];
donations: Scalars['Int'];
prizes: Scalars['Int'];
touranments: Scalars['Int'];
};
export type Hackathon = {
__typename?: 'Hackathon';
cover_image: Scalars['String'];
@@ -86,17 +105,30 @@ export type LnurlDetails = {
export type Mutation = {
__typename?: 'Mutation';
confirmDonation: Donation;
confirmVote: Vote;
donate: Donation;
vote: Vote;
};
export type MutationConfirmDonationArgs = {
payment_request: Scalars['String'];
preimage: Scalars['String'];
};
export type MutationConfirmVoteArgs = {
payment_request: Scalars['String'];
preimage: Scalars['String'];
};
export type MutationDonateArgs = {
amount_in_sat: Scalars['Int'];
};
export type MutationVoteArgs = {
amount_in_sat: Scalars['Int'];
item_id: Scalars['Int'];
@@ -154,6 +186,7 @@ export type Query = {
allTopics: Array<Topic>;
getAllHackathons: Array<Hackathon>;
getCategory: Category;
getDonationsStats: Array<DonationsStats>;
getFeed: Array<Post>;
getLnurlDetailsForProject: LnurlDetails;
getPostById: Post;
@@ -317,6 +350,21 @@ export type SearchProjectsQueryVariables = Exact<{
export type SearchProjectsQuery = { __typename?: 'Query', searchProjects: Array<{ __typename?: 'Project', id: number, thumbnail_image: string, title: string, category: { __typename?: 'Category', title: string, id: number } }> };
export type DonateMutationVariables = Exact<{
amountInSat: Scalars['Int'];
}>;
export type DonateMutation = { __typename?: 'Mutation', donate: { __typename?: 'Donation', id: number, amount: number, payment_request: string, payment_hash: string } };
export type ConfirmDonationMutationVariables = Exact<{
paymentRequest: Scalars['String'];
preimage: Scalars['String'];
}>;
export type ConfirmDonationMutation = { __typename?: 'Mutation', confirmDonation: { __typename?: 'Donation', id: number, amount: number, paid: boolean } };
export type AllTopicsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -483,6 +531,78 @@ export function useSearchProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt
export type SearchProjectsQueryHookResult = ReturnType<typeof useSearchProjectsQuery>;
export type SearchProjectsLazyQueryHookResult = ReturnType<typeof useSearchProjectsLazyQuery>;
export type SearchProjectsQueryResult = Apollo.QueryResult<SearchProjectsQuery, SearchProjectsQueryVariables>;
export const DonateDocument = gql`
mutation Donate($amountInSat: Int!) {
donate(amount_in_sat: $amountInSat) {
id
amount
payment_request
payment_hash
}
}
`;
export type DonateMutationFn = Apollo.MutationFunction<DonateMutation, DonateMutationVariables>;
/**
* __useDonateMutation__
*
* To run a mutation, you first call `useDonateMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDonateMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [donateMutation, { data, loading, error }] = useDonateMutation({
* variables: {
* amountInSat: // value for 'amountInSat'
* },
* });
*/
export function useDonateMutation(baseOptions?: Apollo.MutationHookOptions<DonateMutation, DonateMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DonateMutation, DonateMutationVariables>(DonateDocument, options);
}
export type DonateMutationHookResult = ReturnType<typeof useDonateMutation>;
export type DonateMutationResult = Apollo.MutationResult<DonateMutation>;
export type DonateMutationOptions = Apollo.BaseMutationOptions<DonateMutation, DonateMutationVariables>;
export const ConfirmDonationDocument = gql`
mutation ConfirmDonation($paymentRequest: String!, $preimage: String!) {
confirmDonation(payment_request: $paymentRequest, preimage: $preimage) {
id
amount
paid
}
}
`;
export type ConfirmDonationMutationFn = Apollo.MutationFunction<ConfirmDonationMutation, ConfirmDonationMutationVariables>;
/**
* __useConfirmDonationMutation__
*
* To run a mutation, you first call `useConfirmDonationMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useConfirmDonationMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [confirmDonationMutation, { data, loading, error }] = useConfirmDonationMutation({
* variables: {
* paymentRequest: // value for 'paymentRequest'
* preimage: // value for 'preimage'
* },
* });
*/
export function useConfirmDonationMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ConfirmDonationMutation, ConfirmDonationMutationVariables>(ConfirmDonationDocument, options);
}
export type ConfirmDonationMutationHookResult = ReturnType<typeof useConfirmDonationMutation>;
export type ConfirmDonationMutationResult = Apollo.MutationResult<ConfirmDonationMutation>;
export type ConfirmDonationMutationOptions = Apollo.BaseMutationOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>;
export const AllTopicsDocument = gql`
query allTopics {
allTopics {