Skip to content

Commit

Permalink
Update repository with latest /web content from upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Dec 7, 2024
1 parent fa508dd commit 60f59e5
Show file tree
Hide file tree
Showing 33 changed files with 4,675 additions and 1,433 deletions.
119 changes: 119 additions & 0 deletions app/api/cron/reset-tokens/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { db, UserUsageTable } from "@/drizzle/schema";
import { PRODUCTS } from "@/srm.config";
import { eq } from "drizzle-orm";
import type { Server } from "http";
import { createServer } from "http";
import { NextApiHandler } from "next";
import { GET } from "./route";
import type { SuperTest, Test } from "supertest";
import supertest from "supertest";
/**
* @jest-environment node
*/

describe("Token Reset Cron Job", () => {
const mockUserId = "test-user-123";
const monthlyTokenLimit = 5000 * 1000; // 5M tokens
let server: Server;
let request: SuperTest<Test>;

beforeAll(() => {
const handler: NextApiHandler = (req, res) => {
if (req.method === "GET") {
return GET(req as any);
}
};
server = createServer(handler as any);
request = supertest(server);
});

afterAll((done) => {
server.close(done);
});

beforeEach(async () => {
// Setup test data
await db.insert(UserUsageTable).values({
userId: mockUserId,
subscriptionStatus: "active",
paymentStatus: "paid",
tokenUsage: 1000000, // 1M tokens used
maxTokenUsage: monthlyTokenLimit,
billingCycle: "subscription",
currentPlan: PRODUCTS.SubscriptionMonthly.metadata.plan,
});
});

afterEach(async () => {
// Cleanup test data
await db.delete(UserUsageTable).where(eq(UserUsageTable.userId, mockUserId));
});

it("should reset token usage for active subscribers", async () => {
const response = await request
.get("/api/cron/reset-tokens")
.set("authorization", `Bearer ${process.env.CRON_SECRET}`);

expect(response.status).toBe(200);
expect(response.body).toEqual({
success: true,
message: "Token usage reset successful",
});

// Verify token usage was reset
const userUsage = await db
.select()
.from(UserUsageTable)
.where(eq(UserUsageTable.userId, mockUserId));

expect(userUsage[0].tokenUsage).toBe(0);
expect(userUsage[0].maxTokenUsage).toBe(monthlyTokenLimit);
});

it("should not reset tokens for inactive subscriptions", async () => {
// Update user to inactive
await db
.update(UserUsageTable)
.set({ subscriptionStatus: "inactive" })
.where(eq(UserUsageTable.userId, mockUserId));

const response = await request
.get("/api/cron/reset-tokens")
.set("authorization", `Bearer ${process.env.CRON_SECRET}`);

expect(response.status).toBe(200);

// Verify token usage was not reset
const userUsage = await db
.select()
.from(UserUsageTable)
.where(eq(UserUsageTable.userId, mockUserId));

expect(userUsage[0].tokenUsage).toBe(1000000); // Should remain unchanged
});

it("should return 401 for unauthorized requests", async () => {
const response = await request
.get("/api/cron/reset-tokens")
.set("authorization", "Bearer invalid-token");

expect(response.status).toBe(401);
});

it("should handle database errors gracefully", async () => {
// Mock a database error
jest.spyOn(db, "update").mockRejectedValueOnce(
new Error("Database error") as never
);

const response = await request
.get("/api/cron/reset-tokens")
.set("authorization", `Bearer ${process.env.CRON_SECRET}`);

expect(response.status).toBe(500);
expect(response.body).toEqual({
success: false,
error: "Failed to reset token usage",
});
});
});
45 changes: 45 additions & 0 deletions app/api/cron/reset-tokens/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { db, UserUsageTable } from "@/drizzle/schema";
import { PRODUCTS } from "@/srm.config";
import { eq, and } from "drizzle-orm";
import { NextResponse } from "next/server";

export const runtime = "edge";

async function resetTokenUsage() {
const monthlyTokenLimit = 5000 * 1000; // 5M tokens

// Reset tokens for active subscribers with valid plans
await db
.update(UserUsageTable)
.set({
tokenUsage: 0,
maxTokenUsage: monthlyTokenLimit,
})
.where(
and(
eq(UserUsageTable.subscriptionStatus, "active"),
eq(UserUsageTable.paymentStatus, "paid")
)
);

return { success: true, message: "Token usage reset successful" };
}

export async function GET(request: Request) {
try {
// Verify that the request is coming from Vercel Cron
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new NextResponse("Unauthorized", { status: 401 });
}

const result = await resetTokenUsage();
return NextResponse.json(result);
} catch (error) {
console.error("Error resetting token usage:", error);
return NextResponse.json(
{ success: false, error: "Failed to reset token usage" },
{ status: 500 }
);
}
}
34 changes: 16 additions & 18 deletions app/api/top-up/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { getToken, handleAuthorizationV2 } from "@/lib/handleAuthorization";
import { createAnonymousUser } from "../anon";
import { createLicenseKeyFromUserId } from "@/app/actions";
import { createEmptyUserUsage } from "@/drizzle/schema";
import { getTargetUrl } from "@/srm.config";

import { config, PRICES } from "@/srm.config";
import { getUrl } from "@/lib/getUrl";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2022-11-15",
apiVersion: "2024-06-20",
});

async function createFallbackUser() {
Expand All @@ -24,7 +25,7 @@ async function createFallbackUser() {

async function ensureAuthorizedUser(req: NextRequest) {
const initialLicenseKey = getToken(req);

try {
const { userId } = await handleAuthorizationV2(req);
return { userId, licenseKey: initialLicenseKey };
Expand All @@ -36,7 +37,7 @@ async function ensureAuthorizedUser(req: NextRequest) {

export async function POST(req: NextRequest) {
let userId, licenseKey;

try {
({ userId, licenseKey } = await ensureAuthorizedUser(req));
} catch (error) {
Expand All @@ -45,20 +46,18 @@ export async function POST(req: NextRequest) {
{ status: 401 }
);
}

const baseUrl = getTargetUrl();
const targetUrl =
baseUrl === "localhost:3000" ? `http://${baseUrl}` : `https://${baseUrl}`;

const baseUrl = getUrl();
console.log("baseUrl", baseUrl);

const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
payment_intent_data: {
metadata: {
userId,
type: "top_up",
type: config.products.PayOnceTopUp.metadata.type,
plan: config.products.PayOnceTopUp.metadata.plan,
tokens: "5000000", // 5M tokens
price_key: "top_up_5m",
product_key: "top_up_5m",
},
},
line_items: [
Expand All @@ -69,21 +68,20 @@ export async function POST(req: NextRequest) {
name: "5M Tokens Top-up",
description: "One-time purchase of 5M additional tokens",
},
unit_amount: 1500, // $15 in cents
unit_amount: PRICES.TOP_UP,
},
quantity: 1,
},
],
mode: "payment",
success_url: `${targetUrl}/top-up-success`,
cancel_url: `${targetUrl}/top-up-cancelled`,
success_url: `${baseUrl}/top-up-success`,
cancel_url: `${baseUrl}/top-up-cancelled`,
allow_promotion_codes: true,
metadata: {
userId,
type: "top_up",
type: config.products.PayOnceTopUp.metadata.type,
plan: config.products.PayOnceTopUp.metadata.plan,
tokens: "5000000", // 5M tokens
price_key: "top_up_5m",
product_key: "top_up_5m",
},
});

Expand Down
6 changes: 3 additions & 3 deletions app/api/webhook/handler-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export function createWebhookHandler(
if (options.requiredMetadata) {
const metadata = event.data.object.metadata || {};
const missingFields = options.requiredMetadata.filter(
field => !metadata[field]
(field) => !metadata[field]
);

if (missingFields.length > 0) {
throw new Error(
`Missing required metadata fields: ${missingFields.join(", ")}`
Expand Down Expand Up @@ -53,4 +53,4 @@ export function createWebhookHandler(
};
}
};
}
}
Loading

0 comments on commit 60f59e5

Please sign in to comment.