diff --git a/.github/workflows/maintainance.yml b/.github/workflows/maintainance.yml new file mode 100644 index 0000000..b0498e8 --- /dev/null +++ b/.github/workflows/maintainance.yml @@ -0,0 +1,16 @@ +name: Daily Maintenance + +on: + schedule: + - cron: "0 8 * * *" + +jobs: + maintenance: + runs-on: ubuntu-latest + + steps: + - name: Call maintenance endpoint + run: | + curl -X POST \ + https://subly-backend-iuej.onrender.com/api/jobs/maintenance \ + -H "x-cron-secret: ${{ secrets.CRON_SECRET }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbe9c82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/ \ No newline at end of file diff --git a/server/server.js b/server/server.js index 07c624c..0caad15 100644 --- a/server/server.js +++ b/server/server.js @@ -16,6 +16,7 @@ import { currencyRouters } from "./src/routes/rate.route.js"; import { historyRouter } from "./src/routes/history.route.js"; import cors from "cors"; import { notificationRouter } from "./src/routes/notification.route.js"; +import { jobsMaintainanceRouter } from "./src/routes/maintainance.route.js"; @@ -67,6 +68,7 @@ app.use(cookieParser()); app.use("/api/auth", authLimiter, authRouter); app.use("/api/me", requireAuth, meRouter); +app.use("/api/jobs", jobsMaintainanceRouter); app.use("/api/rate", currencyRouters); app.use("/api/refresh", apiLimiter, refreshRouter); app.use("/api/subscription", requireAuth, subscriptionRouter); diff --git a/server/src/config/env.js b/server/src/config/env.js index e137391..ed53f97 100644 --- a/server/src/config/env.js +++ b/server/src/config/env.js @@ -11,4 +11,5 @@ export const env = { CLIENT_URL: process.env.CLIENT_URL, CORS_ORIGINS: process.env.CORS_ORIGINS, RESEND_API_KEY: process.env.RESEND_API_KEY, + CRON_SECRET: process.env.CRON_SECRET, }; diff --git a/server/src/controllers/maintainance.controller.js b/server/src/controllers/maintainance.controller.js new file mode 100644 index 0000000..662723d --- /dev/null +++ b/server/src/controllers/maintainance.controller.js @@ -0,0 +1,21 @@ +import { sendExpired } from "../jobs/sendExpired.js"; +import { sendReminder } from "../jobs/sendReminder.js"; +import { syncRate } from "../jobs/syncRate.js"; + + +export async function jobsMaintainance(req, res, next) { + try { + const results = await Promise.allSettled([ + syncRate(), + sendReminder(), + sendExpired() + ]); + + return res.json({ + message: "Maintainance completed", + results + }) + } catch (err) { + next(err); + } +} diff --git a/server/src/jobs/sendExpired.js b/server/src/jobs/sendExpired.js index 7c4f42b..9f64b7c 100644 --- a/server/src/jobs/sendExpired.js +++ b/server/src/jobs/sendExpired.js @@ -1,21 +1,19 @@ import { prisma } from "../libs/prisma.js"; -async function sendExpired() { - const now = new Date() +export async function sendExpired() { + const now = new Date(); - const result = await prisma.subscription.updateMany({ - where: { - status: 'ACTIVE', - nextBillingDate: { - lt: now - } - }, - data: { - status:'EXPIRED' - } - }) - - + const result = await prisma.subscription.updateMany({ + where: { + status: "ACTIVE", + nextBillingDate: { + lt: now, + }, + }, + data: { + status: "EXPIRED", + }, + }); } sendExpired().catch(console.error).finally(async () => { diff --git a/server/src/jobs/sendReminder.js b/server/src/jobs/sendReminder.js index 081dd21..012ad8c 100644 --- a/server/src/jobs/sendReminder.js +++ b/server/src/jobs/sendReminder.js @@ -1,7 +1,7 @@ import { prisma } from "../libs/prisma.js"; import { sendReminderEmail } from "../services/email.service.js"; -async function sendReminder() { +export async function sendReminder() { const users = await prisma.user.findMany({ where: { emailNofiticationEnabled: true, @@ -23,8 +23,6 @@ async function sendReminder() { const dueSubscription = user.subscriptions.filter( (sub) => !sub.reminderDaysBefore && sub.nextBillingDate <= reminderDate, ); - - if (!dueSubscription.length) continue; @@ -34,16 +32,11 @@ async function sendReminder() { await prisma.notification.create({ data: { - userId: user.id, - title: "Subscriptions about to be renewed", - message: `${dueSubscription.length} of your subscription is about to expire`, + userId: user.id, + title: "Subscriptions about to be renewed", + message: `${dueSubscription.length} of your subscription is about to expire`, }, }); } } -sendReminder() - .catch(console.error) - .finally(async () => { - await prisma.$disconnect() - }) diff --git a/server/src/jobs/syncRate.js b/server/src/jobs/syncRate.js index 071dfa4..68965ee 100644 --- a/server/src/jobs/syncRate.js +++ b/server/src/jobs/syncRate.js @@ -2,25 +2,22 @@ import { prisma } from "../libs/prisma.js"; import axios from 'axios'; -async function syncRate() { - const response = await axios.get("https://api.exchangerate-api.com/v4/latest/EUR"); +export async function syncRate() { + const response = await axios.get( + "https://api.exchangerate-api.com/v4/latest/EUR", + ); - await prisma.rates.upsert({ - - where: { - baseCurrency: "EUR", - }, - update: { - rates: response.data.rates, - }, - create: { - baseCurrency: "EUR", - rates: response.data.rates, - }, - - }) + await prisma.rates.upsert({ + where: { + baseCurrency: "EUR", + }, + update: { + rates: response.data.rates, + }, + create: { + baseCurrency: "EUR", + rates: response.data.rates, + }, + }); } -syncRate().catch(console.error).finally(async () => { - await prisma.$disconnect() -}) \ No newline at end of file diff --git a/server/src/middlewares/verifyCronSecret.middleware.js b/server/src/middlewares/verifyCronSecret.middleware.js new file mode 100644 index 0000000..5677171 --- /dev/null +++ b/server/src/middlewares/verifyCronSecret.middleware.js @@ -0,0 +1,18 @@ +import { env } from "../config/env"; +export function verifyCronSecret(req, res, next) { + const secret = req.headers["x-cron-secret"]; + + if (!secret) { + return res.status(401).json({ + message: "Missing cron secret", + }); + } + + if (secret !== env.CRON_SECRET) { + return res.status(401).json({ + message: "Invalid cron secret", + }); + } + + next(); +} diff --git a/server/src/routes/maintainance.route.js b/server/src/routes/maintainance.route.js new file mode 100644 index 0000000..d9f6952 --- /dev/null +++ b/server/src/routes/maintainance.route.js @@ -0,0 +1,8 @@ + +import express from 'express' +import { jobsMaintainance } from '../controllers/maintainance.controller.js'; + + +export const jobsMaintainanceRouter = express.Router(); + +jobsMaintainanceRouter.post('/maintainance', jobsMaintainance) \ No newline at end of file