diff --git a/src/db/models/userModels.js b/src/db/models/userModels.js index e43e5ce..0e23b54 100644 --- a/src/db/models/userModels.js +++ b/src/db/models/userModels.js @@ -1,102 +1,106 @@ import prisma from "../prisma"; +import { webln } from "@getalby/sdk"; +import { LightningAddress } from "@getalby/sdk"; + +const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS; export const getAllUsers = async () => { - return await prisma.user.findMany({ + return await prisma.user.findMany({ + include: { + role: true, // Include related role + purchased: { include: { - role: true, // Include related role - purchased: { - include: { - course: true, // Include course details in purchases - resource: true, // Include resource details in purchases - }, - }, + course: true, // Include course details in purchases + resource: true, // Include resource details in purchases }, - }); + }, + }, + }); }; export const getUserById = async (id) => { - return await prisma.user.findUnique({ - where: { id }, + return await prisma.user.findUnique({ + where: { id }, + include: { + role: true, // Include related role + purchased: { include: { - role: true, // Include related role - purchased: { - include: { - course: true, // Include course details in purchases - resource: true, // Include resource details in purchases - }, - }, + course: true, // Include course details in purchases + resource: true, // Include resource details in purchases }, - }); + }, + }, + }); }; export const getUserByPubkey = async (pubkey) => { - return await prisma.user.findUnique({ - where: { pubkey }, + return await prisma.user.findUnique({ + where: { pubkey }, + include: { + role: true, // Include related role + purchased: { include: { - role: true, // Include related role - purchased: { - include: { - course: true, // Include course details in purchases - resource: true, // Include resource details in purchases - }, - }, + course: true, // Include course details in purchases + resource: true, // Include resource details in purchases }, - }); + }, + }, + }); } export const addResourcePurchaseToUser = async (userId, purchaseData) => { - return await prisma.user.update({ - where: { id: userId }, - data: { - purchased: { - create: { - resourceId: purchaseData.resourceId, - amountPaid: purchaseData.amountPaid, - }, + return await prisma.user.update({ + where: { id: userId }, + data: { + purchased: { + create: { + resourceId: purchaseData.resourceId, + amountPaid: purchaseData.amountPaid, }, }, - include: { - purchased: { - include: { - resource: true, - }, + }, + include: { + purchased: { + include: { + resource: true, }, }, - }); - }; + }, + }); +}; export const addCoursePurchaseToUser = async (userId, purchaseData) => { - return await prisma.user.update({ - where: { id: userId }, - data: { - purchased: { - create: { - courseId: purchaseData.courseId, - amountPaid: purchaseData.amountPaid, - }, + return await prisma.user.update({ + where: { id: userId }, + data: { + purchased: { + create: { + courseId: purchaseData.courseId, + amountPaid: purchaseData.amountPaid, }, }, - }); - }; + }, + }); +}; export const createUser = async (data) => { - return await prisma.user.create({ - data, - }); + return await prisma.user.create({ + data, + }); }; export const updateUser = async (id, data) => { - console.log("user modelllll", id, data) - return await prisma.user.update({ - where: { id }, - data, - }); + console.log("user modelllll", id, data) + return await prisma.user.update({ + where: { id }, + data, + }); }; export const deleteUser = async (id) => { - return await prisma.user.delete({ - where: { id }, - }); + return await prisma.user.delete({ + where: { id }, + }); }; export const updateUserSubscription = async (userId, isSubscribed, nwc) => { @@ -129,24 +133,27 @@ export const updateUserSubscription = async (userId, isSubscribed, nwc) => { }); }; -export const checkAndUpdateExpiredSubscriptions = async () => { +export const findExpiredSubscriptions = async () => { const now = new Date(); - const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000); - const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000); + const thirtyOneDaysAgo = new Date(now.getTime() - 31 * 24 * 60 * 60 * 1000); - const expiredSubscriptions = await prisma.role.findMany({ - where: { + return await prisma.role.findMany({ + where: { subscribed: true, lastPaymentAt: { - lt: fiveMinutesAgo + lt: thirtyOneDaysAgo } }, select: { - userId: true + userId: true, + nwc: true } }); +}; - const updatePromises = expiredSubscriptions.map(({ userId }) => +export const expireUserSubscriptions = async (userIds) => { + const now = new Date(); + const updatePromises = userIds.map((userId) => prisma.role.update({ where: { userId }, data: { @@ -160,6 +167,5 @@ export const checkAndUpdateExpiredSubscriptions = async () => { ); await prisma.$transaction(updatePromises); - - return expiredSubscriptions.length; + return userIds.length; }; diff --git a/src/pages/api/users/subscription/cron.js b/src/pages/api/users/subscription/cron.js index 6017d08..0f4a0b0 100644 --- a/src/pages/api/users/subscription/cron.js +++ b/src/pages/api/users/subscription/cron.js @@ -1,4 +1,9 @@ -import { checkAndUpdateExpiredSubscriptions } from "@/db/models/userModels"; +import { findExpiredSubscriptions, updateUserSubscription, expireUserSubscriptions } from "@/db/models/userModels"; +import { webln } from "@getalby/sdk"; +import { LightningAddress } from '@getalby/lightning-tools'; + +const lnAddress = process.env.LIGHTNING_ADDRESS; +const amount = 25; // Set the subscription amount in satoshis export default async function handler(req, res) { if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) { @@ -7,8 +12,41 @@ export default async function handler(req, res) { if (req.method === 'POST') { try { - const updatedCount = await checkAndUpdateExpiredSubscriptions(); - res.status(200).json({ message: `Cron job completed successfully. Updated ${updatedCount} subscriptions.` }); + const expiredSubscriptions = await findExpiredSubscriptions(); + const stillExpired = []; + + for (const { userId, nwc } of expiredSubscriptions) { + if (nwc) { + try { + const nwcProvider = new webln.NostrWebLNProvider({ + nostrWalletConnectUrl: nwc + }); + await nwcProvider.enable(); + + const ln = new LightningAddress(lnAddress); + await ln.fetch(); + const newInvoice = await ln.requestInvoice({ satoshi: amount }); + + const response = await nwcProvider.sendPayment(newInvoice?.paymentRequest); + + if (response && response?.preimage) { + await updateUserSubscription(userId, true, nwc); + continue; // Skip adding to stillExpired list + } + } catch (error) { + console.error(`Payment failed for user ${userId}:`, error); + } + } + stillExpired.push(userId); + } + + const expiredCount = await expireUserSubscriptions(stillExpired); + + res.status(200).json({ + message: `Cron job completed successfully. + Processed ${expiredSubscriptions.length} subscriptions. + Expired ${expiredCount} subscriptions.` + }); } catch (error) { console.error('Cron job error:', error); res.status(500).json({ error: error.message }); diff --git a/src/pages/profile.js b/src/pages/profile.js index 8d6e945..7469d29 100644 --- a/src/pages/profile.js +++ b/src/pages/profile.js @@ -22,24 +22,19 @@ const Profile = () => { const [subscribedUntil, setSubscribedUntil] = useState(null); const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null); - const { data: session, status, update } = useSession(); + const { data: session } = useSession(); const { returnImageProxy } = useImageProxy(); const { ndk } = useNDKContext(); const menu = useRef(null); - useEffect(() => { - // Refetch the session when the component mounts - update(); - }, []); - useEffect(() => { if (session && session.user) { setUser(session.user); if (session.user.role) { setSubscribed(session.user.role.subscribed); const subscribedAt = new Date(session.user.role.lastPaymentAt); - // The user is subscribed until the date in subscribedAt + 30 days - const subscribedUntil = new Date(subscribedAt.getTime() + 30 * 24 * 60 * 60 * 1000); + // The user is subscribed until the date in subscribedAt + 31 days + const subscribedUntil = new Date(subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000); setSubscribedUntil(subscribedUntil); if (session.user.role.subscriptionExpiredAt) { const expiredAt = new Date(session.user.role.subscriptionExpiredAt) diff --git a/src/utils/nostr.js b/src/utils/nostr.js index bd21b1b..9d647ce 100644 --- a/src/utils/nostr.js +++ b/src/utils/nostr.js @@ -7,7 +7,7 @@ export const findKind0Fields = async (kind0) => { const findTruthyPropertyValue = (object, properties) => { for (const property of properties) { - if (object[property]) { + if (object?.[property]) { return object[property]; } } diff --git a/vercel.json b/vercel.json index 2bcda26..61341d7 100644 --- a/vercel.json +++ b/vercel.json @@ -3,7 +3,7 @@ "crons": [ { "path": "/api/users/subscription/cron", - "schedule": "0 * * * *" + "schedule": "0 0 * * *" } ] }