72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
import fp from 'fastify-plugin';
|
|
import { eq } from 'drizzle-orm';
|
|
import { subscriptions } from '../db/schema/subscriptions.js';
|
|
import { forbidden } from '../utils/errors.js';
|
|
|
|
export type SubscriptionInfo = {
|
|
plan: 'free' | 'pro';
|
|
status: 'active' | 'trialing' | 'cancelled' | 'expired';
|
|
isPro: boolean;
|
|
expiresAt: Date | null;
|
|
};
|
|
|
|
declare module 'fastify' {
|
|
interface FastifyRequest {
|
|
subscription?: SubscriptionInfo | null;
|
|
}
|
|
interface FastifyInstance {
|
|
withSubscription: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
requirePro: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
}
|
|
}
|
|
|
|
async function loadSubscription(db: FastifyInstance['db'], userId: string): Promise<SubscriptionInfo | null> {
|
|
const [sub] = await db
|
|
.select()
|
|
.from(subscriptions)
|
|
.where(eq(subscriptions.userId, userId))
|
|
.limit(1);
|
|
|
|
if (!sub) return null;
|
|
|
|
const now = new Date();
|
|
const isExpired = sub.expiresAt && sub.expiresAt < now;
|
|
const isPro =
|
|
sub.plan === 'pro' &&
|
|
(sub.status === 'active' || sub.status === 'trialing') &&
|
|
!isExpired;
|
|
|
|
return {
|
|
plan: sub.plan as 'free' | 'pro',
|
|
status: sub.status as SubscriptionInfo['status'],
|
|
isPro,
|
|
expiresAt: sub.expiresAt,
|
|
};
|
|
}
|
|
|
|
export async function requirePro(req: FastifyRequest, _reply: FastifyReply): Promise<void> {
|
|
const sub = req.subscription;
|
|
|
|
if (!sub?.isPro) {
|
|
throw forbidden('Pro subscription required');
|
|
}
|
|
}
|
|
|
|
const subscriptionPlugin = async (app: FastifyInstance) => {
|
|
app.decorateRequest('subscription', undefined);
|
|
|
|
app.decorate('withSubscription', async (req: FastifyRequest, _reply: FastifyReply) => {
|
|
if (!req.user?.id) return;
|
|
const sub = await loadSubscription(app.db, req.user.id);
|
|
req.subscription = sub;
|
|
});
|
|
|
|
app.decorate('requirePro', requirePro);
|
|
};
|
|
|
|
export default fp(subscriptionPlugin, {
|
|
name: 'subscription',
|
|
dependencies: ['database', 'auth'],
|
|
});
|