Added basic cron job, endpoint, and vercel setup just wrong intervals right now, also added admin and subscriptionExpiredAt into Role schema so I can show user a special message after their subscription expired

This commit is contained in:
austinkelsay 2024-09-01 12:40:25 -05:00
parent fb02ea79af
commit e69d974ad7
9 changed files with 139 additions and 21 deletions

View File

@ -1,4 +1,3 @@
// next.config.js
const removeImports = require("next-remove-imports")(); const removeImports = require("next-remove-imports")();
module.exports = removeImports({ module.exports = removeImports({
@ -9,4 +8,12 @@ module.exports = removeImports({
webpack(config, options) { webpack(config, options) {
return config; return config;
}, },
async rewrites() {
return [
{
source: '/api/cron',
destination: '/api/cron',
},
];
},
}); });

View File

@ -15,8 +15,10 @@ CREATE TABLE "Role" (
"id" TEXT NOT NULL, "id" TEXT NOT NULL,
"userId" TEXT NOT NULL, "userId" TEXT NOT NULL,
"subscribed" BOOLEAN NOT NULL DEFAULT false, "subscribed" BOOLEAN NOT NULL DEFAULT false,
"admin" BOOLEAN NOT NULL DEFAULT false,
"subscriptionStartDate" TIMESTAMP(3), "subscriptionStartDate" TIMESTAMP(3),
"lastPaymentAt" TIMESTAMP(3), "lastPaymentAt" TIMESTAMP(3),
"subscriptionExpiredAt" TIMESTAMP(3),
"nwc" TEXT, "nwc" TEXT,
CONSTRAINT "Role_pkey" PRIMARY KEY ("id") CONSTRAINT "Role_pkey" PRIMARY KEY ("id")

View File

@ -27,8 +27,10 @@ model Role {
user User @relation(fields: [userId], references: [id]) user User @relation(fields: [userId], references: [id])
userId String @unique userId String @unique
subscribed Boolean @default(false) subscribed Boolean @default(false)
admin Boolean @default(false)
subscriptionStartDate DateTime? subscriptionStartDate DateTime?
lastPaymentAt DateTime? lastPaymentAt DateTime?
subscriptionExpiredAt DateTime?
nwc String? nwc String?
} }

View File

@ -104,6 +104,26 @@ const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptio
const newNWCUrl = newNwc.getNostrWalletConnectUrl(); const newNWCUrl = newNwc.getNostrWalletConnectUrl();
if (newNWCUrl) { if (newNWCUrl) {
const nwc = new webln.NostrWebLNProvider({
nostrWalletConnectUrl: newNWCUrl,
});
await nwc.enable();
const invoice = await fetchInvoice();
if (!invoice || !invoice.paymentRequest) {
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
return;
}
const paymentResponse = await nwc.sendPayment(invoice.paymentRequest);
if (!paymentResponse || !paymentResponse?.preimage) {
showToast('error', 'NWC', 'Payment failed');
return;
}
const subscriptionResponse = await axios.put('/api/users/subscription', { const subscriptionResponse = await axios.put('/api/users/subscription', {
userId: session.user.id, userId: session.user.id,
isSubscribed: true, isSubscribed: true,

View File

@ -111,12 +111,14 @@ export const updateUserSubscription = async (userId, isSubscribed, nwc) => {
subscriptionStartDate: isSubscribed ? now : null, subscriptionStartDate: isSubscribed ? now : null,
lastPaymentAt: isSubscribed ? now : null, lastPaymentAt: isSubscribed ? now : null,
nwc: nwc ? nwc : null, nwc: nwc ? nwc : null,
subscriptionExpiredAt: null,
}, },
update: { update: {
subscribed: isSubscribed, subscribed: isSubscribed,
subscriptionStartDate: isSubscribed ? { set: now } : { set: null }, subscriptionStartDate: isSubscribed ? { set: now } : { set: null },
lastPaymentAt: isSubscribed ? now : { set: null }, lastPaymentAt: isSubscribed ? now : { set: null },
nwc: nwc ? nwc : null, nwc: nwc ? nwc : null,
subscriptionExpiredAt: null,
}, },
}, },
}, },
@ -126,3 +128,38 @@ export const updateUserSubscription = async (userId, isSubscribed, nwc) => {
}, },
}); });
}; };
export const checkAndUpdateExpiredSubscriptions = 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 expiredSubscriptions = await prisma.role.findMany({
where: {
subscribed: true,
lastPaymentAt: {
lt: fiveMinutesAgo
}
},
select: {
userId: true
}
});
const updatePromises = expiredSubscriptions.map(({ userId }) =>
prisma.role.update({
where: { userId },
data: {
subscribed: false,
subscriptionStartDate: null,
lastPaymentAt: null,
nwc: null,
subscriptionExpiredAt: now,
}
})
);
await prisma.$transaction(updatePromises);
return expiredSubscriptions.length;
};

View File

@ -0,0 +1,20 @@
import { checkAndUpdateExpiredSubscriptions } from "@/db/models/userModels";
export default async function handler(req, res) {
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (req.method === 'POST') {
try {
const updatedCount = await checkAndUpdateExpiredSubscriptions();
res.status(200).json({ message: `Cron job completed successfully. Updated ${updatedCount} subscriptions.` });
} catch (error) {
console.error('Cron job error:', error);
res.status(500).json({ error: error.message });
}
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}

View File

@ -20,12 +20,18 @@ const Profile = () => {
const [subscribeModalVisible, setSubscribeModalVisible] = useState(false); // Add this state const [subscribeModalVisible, setSubscribeModalVisible] = useState(false); // Add this state
const [subscribed, setSubscribed] = useState(false); const [subscribed, setSubscribed] = useState(false);
const [subscribedUntil, setSubscribedUntil] = useState(null); const [subscribedUntil, setSubscribedUntil] = useState(null);
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
const { data: session, status } = useSession(); const { data: session, status, update } = useSession();
const { returnImageProxy } = useImageProxy(); const { returnImageProxy } = useImageProxy();
const { ndk } = useNDKContext(); const { ndk } = useNDKContext();
const menu = useRef(null); const menu = useRef(null);
useEffect(() => {
// Refetch the session when the component mounts
update();
}, []);
useEffect(() => { useEffect(() => {
if (session && session.user) { if (session && session.user) {
setUser(session.user); setUser(session.user);
@ -35,6 +41,10 @@ const Profile = () => {
// The user is subscribed until the date in subscribedAt + 30 days // The user is subscribed until the date in subscribedAt + 30 days
const subscribedUntil = new Date(subscribedAt.getTime() + 30 * 24 * 60 * 60 * 1000); const subscribedUntil = new Date(subscribedAt.getTime() + 30 * 24 * 60 * 60 * 1000);
setSubscribedUntil(subscribedUntil); setSubscribedUntil(subscribedUntil);
if (session.user.role.subscriptionExpiredAt) {
const expiredAt = new Date(session.user.role.subscriptionExpiredAt)
setSubscriptionExpiredAt(expiredAt);
}
} }
} }
}, [session]); }, [session]);
@ -96,13 +106,14 @@ const Profile = () => {
<BitcoinConnectButton /> <BitcoinConnectButton />
</div> </div>
<div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center border-2 border-gray-700 bg-[#121212] p-8 rounded-md"> <div className="flex flex-col w-1/2 mx-auto my-4 justify-between items-center border-2 border-gray-700 bg-[#121212] p-8 rounded-md">
{subscribed ? ( {subscribed && (
<> <>
<Message severity="success" text="Subscribed!" /> <Message severity="success" text="Subscribed!" />
<p className="mt-8">Thank you for your support 🎉</p> <p className="mt-8">Thank you for your support 🎉</p>
<p className="text-sm text-gray-400">Pay-as-you-go subscription will renew on {subscribedUntil.toLocaleDateString()}</p> <p className="text-sm text-gray-400">Pay-as-you-go subscription will renew on {subscribedUntil.toLocaleDateString()}</p>
</> </>
) : ( )}
{(!subscribed && !subscriptionExpiredAt) && (
<> <>
<Message severity="info" text="You currently have no active subscription" /> <Message severity="info" text="You currently have no active subscription" />
<Button <Button
@ -112,6 +123,16 @@ const Profile = () => {
/> />
</> </>
)} )}
{subscriptionExpiredAt && (
<>
<Message severity="warn" text={`Your subscription expired on ${subscriptionExpiredAt.toLocaleDateString()}`} />
<Button
label="Subscribe"
className="w-auto mt-8 text-[#f8f8ff]"
onClick={openSubscribeModal} // Add this onClick handler
/>
</>
)}
</div> </div>
</div> </div>
{!session || !session?.user || !ndk ? ( {!session || !session?.user || !ndk ? (

9
vercel.json Normal file
View File

@ -0,0 +1,9 @@
{
"version": 2,
"crons": [
{
"path": "/api/users/subscription/cron",
"schedule": "0 * * * *"
}
]
}