Skip to content

Commit

Permalink
feat: Don't allow >100k emails per month
Browse files Browse the repository at this point in the history
  • Loading branch information
amaury1093 committed Dec 29, 2023
1 parent d8cbfef commit dcb5202
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 47 deletions.
38 changes: 2 additions & 36 deletions src/app/[lang]/dashboard/ApiUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import { Capacity, Text } from "@geist-ui/react";
import { Loader } from "@geist-ui/react-icons";
import React, { useEffect, useState } from "react";
import { sentryException } from "@/util/sentry";
import { subApiMaxCalls } from "@/util/subs";
import { getApiUsage, subApiMaxCalls } from "@/util/subs";
import styles from "./ApiUsage.module.css";
import { formatDate } from "@/util/helpers";
import { Dictionary } from "@/dictionaries";
import { Tables } from "@/supabase/database.types";
import { SupabaseClient } from "@supabase/supabase-js";
import { parseISO, subMonths } from "date-fns";
import { SubscriptionWithPrice } from "@/supabase/supabaseServer";
import { createClient } from "@/supabase/client";

Expand All @@ -28,7 +25,7 @@ export function ApiUsage({

useEffect(() => {
const t = setInterval(() => {
getApiUsageClient(supabase, subscription)
getApiUsage(supabase, subscription)
.then(setApiCalls)
.catch(sentryException);
}, 3000);
Expand Down Expand Up @@ -82,34 +79,3 @@ export function ApiUsage({
</section>
);
}

// Get the api calls of a user in the past month.
async function getApiUsageClient(
supabase: SupabaseClient,
subscription: Tables<"subscriptions"> | null
): Promise<number> {
const { error, count } = await supabase
.from("calls")
.select("*", { count: "exact" })
.gt("created_at", getUsageStartDate(subscription).toISOString());

if (error) {
throw error;
}

return count || 0;
}

// Returns the start date of the usage metering.
// - If the user has an active subscription, it's the current period's start
// date.
// - If not, then it's 1 month rolling.
function getUsageStartDate(subscription: Tables<"subscriptions"> | null): Date {
if (!subscription) {
return subMonths(new Date(), 1);
}

return typeof subscription.current_period_start === "string"
? parseISO(subscription.current_period_start)
: subscription.current_period_start;
}
4 changes: 2 additions & 2 deletions src/app/[lang]/dashboard/bulk/BulkHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function BulkHistory(props: {
prop="safe"
render={renderSafe}
>
<Text className="full-width text-right" span type="success">
<Text className="full-width text-right green" span>
{d.table.safe}
</Text>
</Table.Column>
Expand Down Expand Up @@ -152,7 +152,7 @@ export function BulkHistory(props: {
}

const renderSafe = (value: string) => (
<Text className="full-width text-right" span type="success">
<Text className="full-width text-right green" span>
{value}
</Text>
);
Expand Down
36 changes: 27 additions & 9 deletions src/app/api/v1/bulk/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import amqplib from "amqplib";
import { supabaseAdmin } from "@/supabase/supabaseAdmin";
import { sentryException } from "@/util/sentry";
import { ENABLE_BULK, getWebappURL } from "@/util/helpers";
import {
checkUserInDB,
isEarlyResponse,
} from "@/app/api/v0/check_email/checkUserInDb";
import { SAAS_100K_PRODUCT_ID } from "@/util/subs";
import { isEarlyResponse } from "@/app/api/v0/check_email/checkUserInDb";
import { SAAS_100K_PRODUCT_ID, getApiUsage } from "@/util/subs";
import { Json } from "@/supabase/database.types";
import { cookies } from "next/headers";
import { createClient } from "@/supabase/server";
import { getSubscription } from "@/supabase/supabaseServer";

interface BulkPayload {
input_type: "array";
Expand All @@ -25,9 +25,24 @@ export const POST = async (req: NextRequest): Promise<Response> => {
}

try {
const { user, subAndCalls } = await checkUserInDB(req);
const cookieStore = cookies();
const supabase = createClient(cookieStore);
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return Response.json(
{
error: "Not logged in",
},
{
status: 401,
}
);
}
const subscripton = await getSubscription();

if (subAndCalls.product_id !== SAAS_100K_PRODUCT_ID) {
if (subscripton?.prices?.product_id !== SAAS_100K_PRODUCT_ID) {
return Response.json(
{
error: "Bulk verification is not available on your plan",
Expand All @@ -39,10 +54,13 @@ export const POST = async (req: NextRequest): Promise<Response> => {
}

const payload: BulkPayload = await req.json();
if (payload.input.length > 100000) {

const callsUsed = await getApiUsage(supabase, subscripton);

if (payload.input.length + callsUsed > 100_000) {
return Response.json(
{
error: "Bulk verification is limited to 100_000 emails",
error: "Verifying more than 100,000 emails per month is not available on your plan. Please contact [email protected] to upgrade to the Commercial License.",
},
{
status: 403,
Expand Down
4 changes: 4 additions & 0 deletions src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ button {
.mt-0 {
margin-top: 0;
}

.green {
color: green !important;
}
33 changes: 33 additions & 0 deletions src/util/subs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { Tables } from "@/supabase/database.types";
import { SupabaseClient } from "@supabase/supabase-js";
import { parseISO, subMonths } from "date-fns";

// We're hardcoding these as env variables.
export const SAAS_10K_PRODUCT_ID = process.env.NEXT_PUBLIC_SAAS_10K_PRODUCT_ID;
export const SAAS_100K_PRODUCT_ID =
Expand All @@ -21,3 +25,32 @@ export function subApiMaxCalls(productId: string | null): number {
? 10_000
: 50;
}

// Get the api calls of a user in the past month/billing period.
export async function getApiUsage(
supabase: SupabaseClient,
subscription: Tables<"subscriptions"> | null
): Promise<number> {
const { error, count } = await supabase
.from("calls")
.select("*", { count: "exact" })
.gt("created_at", getUsageStartDate(subscription).toISOString());

if (error) {
throw error;
}

return count || 0;
}

// Returns the start date of the usage metering.
// - If the user has an active subscription, it's the current period's start
// date.
// - If not, then it's 1 month rolling.
function getUsageStartDate(subscription: Tables<"subscriptions"> | null): Date {
if (!subscription) {
return subMonths(new Date(), 1);
}

return parseISO(subscription.current_period_start);
}

1 comment on commit dcb5202

@vercel
Copy link

@vercel vercel bot commented on dcb5202 Dec 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.