diff --git a/package.json b/package.json index 9e7a61e..ac11d22 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@fastify/cors": "^8.2.0", "@supabase/supabase-js": "^2.0.5", "aws-sdk": "^2.1272.0", + "cron-parser": "^4.8.1", "date-fns": "^2.30.0", "dotenv": "^16.0.3", "fastify": "^4.10.2", diff --git a/src/services/requests/scanService.ts b/src/services/requests/scanService.ts index 340240c..ad5e62c 100644 --- a/src/services/requests/scanService.ts +++ b/src/services/requests/scanService.ts @@ -10,7 +10,7 @@ import { createReport } from "../../storage/report.storage"; import { ScanDoesNotExist, UserHasNotEnoughCredits } from "../../exceptions/exceptions"; import { deleteProbes, updateProbesByScanId } from "../../storage/probe.storage"; import { getUserCredits, updateUserCredits } from "../../storage/credits.storage"; -import { probesPriceMapping } from "../../config"; +import { getAvailableProbes, probesPriceMapping } from "../../config"; export const requestScan = async (scanRequest: CreateScanRequest): Promise => { @@ -19,7 +19,9 @@ export const requestScan = async (scanRequest: CreateScanRequest): Promise sum + availableProbes[probe.name]?.price, 0) + if (userCredits < sumOfCreditsToUse) { throw new UserHasNotEnoughCredits(scanRequest.user_id) } @@ -69,7 +71,7 @@ export const requestScan = async (scanRequest: CreateScanRequest): Promise => { console.log(`[REQUEST][SCAN][RESTART][${scan.id}] Restarting scan...`) const userCredits = await getUserCredits(scan.userId) - if (userCredits < scan.probes?.length) { + const availableProbes = getAvailableProbes() + const sumOfCreditsToUse = scan.probes?.reduce((sum, probe) => sum + availableProbes[probe.name]?.price, 0) + if (userCredits < sumOfCreditsToUse) { throw new UserHasNotEnoughCredits(scan.userId) } @@ -156,5 +160,5 @@ export const restartScan = async (scan: ScanWithProbes): Promise => { console.log(`[REQUEST][SCAN][RESTART][${scan.id}] Published request to Queue !`) // Update user credits - await updateUserCredits(scan.userId, userCredits - scan.probes?.length) + await updateUserCredits(scan.userId, { remainingCredits: userCredits - sumOfCreditsToUse }) } \ No newline at end of file diff --git a/src/storage/credits.storage.ts b/src/storage/credits.storage.ts index c328148..ca88575 100644 --- a/src/storage/credits.storage.ts +++ b/src/storage/credits.storage.ts @@ -2,7 +2,7 @@ import { UserCredits } from "../models/credit" import supabaseClient from "./supabase" export const getUserCredits = async (userId: string): Promise => { - return (await supabaseClient.from('user_credits').select('remaningCredits').eq('userId', userId).maybeSingle()).data?.remaningCredits || 0 + return (await supabaseClient.from('user_credits').select('remainingCredits').eq('userId', userId).maybeSingle()).data?.remainingCredits || 0 } export const updateUserCredits = async (userId: string, payload: Partial): Promise => { diff --git a/src/utils/probe.utils.ts b/src/utils/probe.utils.ts new file mode 100644 index 0000000..af78fd9 --- /dev/null +++ b/src/utils/probe.utils.ts @@ -0,0 +1,32 @@ +import cronParser from 'cron-parser' +import { isAfter, addMonths } from 'date-fns' +import { Probe } from '../models/probe' + +export const calculateCreditUsageForPeriod = (periodicity: string, renewalDate: Date, nbCreditsForProbe): number => { + if (!periodicity) { + return 0 + } + + if (periodicity === 'ONCE') { + return nbCreditsForProbe + } + + // Protection against infinite loop + let iteration = 0 + + const occurrences = [] + const interval = cronParser.parseExpression(periodicity) + while (interval.hasNext() && iteration < 100) { + const occurrence = interval.next() + if (isAfter(occurrence.toDate(), renewalDate)) { + break; + } + occurrences.push(occurrence) + iteration++ + } + return occurrences.length * nbCreditsForProbe +} + +export const getAllCreditsUsedByProbesForMonth = (probes: Probe[], periodicity: string): number => { + return probes?.reduce((sum, probe) => sum + calculateCreditUsageForPeriod(periodicity, addMonths(new Date(), 1), probe.price), 0) || 0 +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0473d6c..5d266cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1361,6 +1361,13 @@ cookie@^0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cron-parser@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.8.1.tgz#47062ea63d21d78c10ddedb08ea4c5b6fc2750fb" + integrity sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ== + dependencies: + luxon "^3.2.1" + cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" @@ -2524,6 +2531,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +luxon@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48" + integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg== + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"