mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-28 18:54:35 +01:00
196 lines
5.7 KiB
JavaScript
196 lines
5.7 KiB
JavaScript
const { parsePaymentRequest } = require("invoices");
|
|
const axios = require("axios");
|
|
const { createHash } = require("crypto");
|
|
|
|
function hexToUint8Array(hexString) {
|
|
const match = hexString.match(/.{1,2}/g);
|
|
if (match) {
|
|
return new Uint8Array(match.map((byte) => parseInt(byte, 16)));
|
|
}
|
|
}
|
|
|
|
// TODO: generaly validate LNURL responses
|
|
|
|
function getLnurlDetails(lnurl) {
|
|
return axios.get(lnurl);
|
|
}
|
|
|
|
function lightningAddressToLnurl(lightning_address) {
|
|
const [name, domain] = lightning_address.split("@");
|
|
return `https://${domain}/.well-known/lnurlp/${name}`;
|
|
}
|
|
|
|
async function getLnurlCallbackUrl(lightning_address) {
|
|
return getLnurlDetails(lightningAddressToLnurl(lightning_address)).then(
|
|
(response) => {
|
|
return response.data.callback;
|
|
}
|
|
);
|
|
}
|
|
|
|
async function getPaymetRequestForProject(project, amount_in_sat) {
|
|
let lnurlCallbackUrl = project.lnurl_callback_url;
|
|
const amount = amount_in_sat * 1000; // msats
|
|
console.log(lnurlCallbackUrl);
|
|
if (!lnurlCallbackUrl) {
|
|
lnurlCallbackUrl = await getLnurlCallbackUrl(project.lightning_address);
|
|
}
|
|
return axios
|
|
.get(lnurlCallbackUrl, { params: { amount } })
|
|
.then((prResponse) => {
|
|
console.log(prResponse.data);
|
|
return prResponse.data.pr;
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
Query: {
|
|
allCategories: async (_source, args, context) => {
|
|
return context.prisma.category.findMany({
|
|
orderBy: { title: "desc" },
|
|
include: {
|
|
project: {
|
|
take: 5,
|
|
orderBy: { votes_count: "desc" },
|
|
},
|
|
},
|
|
});
|
|
},
|
|
getCategory: async (_source, args, context) => {
|
|
return context.prisma.category.findUnique({
|
|
where: { id: args.id },
|
|
include: {
|
|
project: {
|
|
take: 5,
|
|
orderBy: { votes_count: "desc" },
|
|
},
|
|
},
|
|
});
|
|
},
|
|
newProjects: async (_source, args, context) => {
|
|
const take = args.take || 50;
|
|
const skip = args.skip || 0;
|
|
return context.prisma.project.findMany({
|
|
orderBy: { created_at: "desc" },
|
|
include: { category: true },
|
|
skip,
|
|
take,
|
|
});
|
|
},
|
|
allProjects: async (_source, args, context) => {
|
|
const take = args.take || 50;
|
|
const skip = args.skip || 0;
|
|
return context.prisma.project.findMany({
|
|
orderBy: { votes_count: "desc" },
|
|
include: { category: true },
|
|
skip,
|
|
take,
|
|
});
|
|
},
|
|
projectsByCategory: async (_source, args, context) => {
|
|
const take = args.take || 50;
|
|
const skip = args.skip || 0;
|
|
const categoryId = args.category_id;
|
|
return context.prisma.project.findMany({
|
|
where: { category_id: categoryId },
|
|
orderBy: { votes_count: "desc" },
|
|
include: { category: true },
|
|
skip,
|
|
take,
|
|
});
|
|
},
|
|
getProject: async (_source, args, context) => {
|
|
return context.prisma.project.findUnique({
|
|
where: {
|
|
id: args.id,
|
|
},
|
|
include: { category: true },
|
|
});
|
|
},
|
|
getLnurlDetailsForProject: async (_source, args, context) => {
|
|
const project = await context.prisma.project.findUnique({
|
|
where: {
|
|
id: args.project_id,
|
|
},
|
|
});
|
|
const lnurlDetails = await getLnurlDetails(
|
|
lightningAddressToLnurl(project.lightning_address)
|
|
);
|
|
if (
|
|
!lnurlDetails.data ||
|
|
lnurlDetails.data.status.toLowerCase() !== "ok"
|
|
) {
|
|
console.error(lnurlDetails.data);
|
|
throw new Error("Recipient not available");
|
|
}
|
|
|
|
// cache the callback URL
|
|
await context.prisma.project.update({
|
|
where: { id: project.id },
|
|
data: {
|
|
lnurl_callback_url: lnurlDetails.data.callback,
|
|
},
|
|
});
|
|
return {
|
|
minSendable: parseInt(lnurlDetails.data.minSendable) / 1000,
|
|
maxSendable: parseInt(lnurlDetails.data.maxSendable) / 1000,
|
|
metadata: lnurlDetails.data.metadata,
|
|
commentAllowed: lnurlDetails.data.commentAllowed,
|
|
};
|
|
},
|
|
},
|
|
Mutation: {
|
|
vote: async (_source, args, context) => {
|
|
const project = await context.prisma.project.findUnique({
|
|
where: { id: args.project_id },
|
|
});
|
|
const pr = await getPaymetRequestForProject(project, args.amount_in_sat);
|
|
const invoice = parsePaymentRequest({ request: pr });
|
|
return context.prisma.vote.create({
|
|
data: {
|
|
project_id: project.id,
|
|
amount_in_sat: args.amount_in_sat,
|
|
payment_request: pr,
|
|
payment_hash: invoice.id,
|
|
},
|
|
});
|
|
},
|
|
confirmVote: async (_source, args, context) => {
|
|
const paymentHash = createHash("sha256")
|
|
.update(hexToUint8Array(args.preimage))
|
|
.digest("hex");
|
|
// look for a vote for the payment request and the calculated payment hash
|
|
const vote = await context.prisma.vote.findFirst({
|
|
where: {
|
|
payment_request: args.payment_request,
|
|
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 context.prisma.project.findUnique({
|
|
where: { id: vote.project_id },
|
|
});
|
|
// count up votes cache
|
|
await context.prisma.project.update({
|
|
where: { id: project.id },
|
|
data: {
|
|
votes_count: (project.votes_count = vote.amount_in_sat),
|
|
},
|
|
});
|
|
// return the current vote
|
|
return context.prisma.vote.update({
|
|
where: { id: vote.id },
|
|
data: {
|
|
paid: true,
|
|
preimage: args.preimage,
|
|
},
|
|
});
|
|
} else {
|
|
throw new Error("Invalid preimage");
|
|
}
|
|
},
|
|
},
|
|
};
|