refactor(server): use feature model (#9932)

This commit is contained in:
forehalo
2025-02-05 10:27:26 +00:00
parent 0ff8d3af6f
commit 7826e2b7c8
121 changed files with 1723 additions and 3826 deletions
@@ -15,10 +15,7 @@ import {
TooManyRequest,
URLHelper,
} from '../../../base';
import {
EarlyAccessType,
FeatureManagementService,
} from '../../../core/features';
import { EarlyAccessType, FeatureService } from '../../../core/features';
import {
CouponType,
KnownStripeInvoice,
@@ -59,7 +56,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
stripe: Stripe,
db: PrismaClient,
private readonly runtime: Runtime,
private readonly feature: FeatureManagementService,
private readonly feature: FeatureService,
private readonly event: EventBus,
private readonly url: URLHelper,
private readonly mutex: Mutex
@@ -1,25 +1,17 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { FeatureManagementService } from '../../core/features';
import { PermissionService } from '../../core/permission';
import {
QuotaManagementService,
QuotaService,
QuotaType,
} from '../../core/quota';
import { WorkspaceService } from '../../core/workspaces/resolvers';
import { Models } from '../../models';
import { SubscriptionPlan } from './types';
@Injectable()
export class QuotaOverride {
constructor(
private readonly quota: QuotaService,
private readonly manager: QuotaManagementService,
private readonly permission: PermissionService,
private readonly workspace: WorkspaceService,
private readonly feature: FeatureManagementService,
private readonly quotaService: QuotaService
private readonly models: Models
) {}
@OnEvent('workspace.subscription.activated')
@@ -31,21 +23,17 @@ export class QuotaOverride {
}: Events['workspace.subscription.activated']) {
switch (plan) {
case 'team': {
const hasTeamWorkspace = await this.quota.hasWorkspaceQuota(
const isTeam = await this.workspace.isTeamWorkspace(workspaceId);
await this.models.workspaceFeature.add(
workspaceId,
QuotaType.TeamPlanV1
);
await this.manager.addTeamWorkspace(
workspaceId,
`${recurring} team subscription activated`
);
await this.quota.updateWorkspaceConfig(
workspaceId,
QuotaType.TeamPlanV1,
{ memberLimit: quantity }
'team_plan_v1',
`${recurring} team subscription activated`,
{
memberLimit: quantity,
}
);
await this.permission.refreshSeatStatus(workspaceId, quantity);
if (!hasTeamWorkspace) {
if (!isTeam) {
// this event will triggered when subscription is activated or changed
// we only send emails when the team workspace is activated
await this.workspace.sendTeamWorkspaceUpgradedEmail(workspaceId);
@@ -64,7 +52,7 @@ export class QuotaOverride {
}: Events['workspace.subscription.canceled']) {
switch (plan) {
case SubscriptionPlan.Team:
await this.manager.removeTeamWorkspace(workspaceId);
await this.models.workspaceFeature.remove(workspaceId, 'team_plan_v1');
break;
default:
break;
@@ -79,14 +67,16 @@ export class QuotaOverride {
}: Events['user.subscription.activated']) {
switch (plan) {
case SubscriptionPlan.AI:
await this.feature.addCopilot(userId, 'subscription activated');
await this.models.userFeature.add(
userId,
'unlimited_copilot',
'subscription activated'
);
break;
case SubscriptionPlan.Pro:
await this.quotaService.switchUserQuota(
await this.models.userFeature.add(
userId,
recurring === 'lifetime'
? QuotaType.LifetimeProPlanV1
: QuotaType.ProPlanV1,
recurring === 'lifetime' ? 'lifetime_pro_plan_v1' : 'pro_plan_v1',
'subscription activated'
);
break;
@@ -102,16 +92,20 @@ export class QuotaOverride {
}: Events['user.subscription.canceled']) {
switch (plan) {
case SubscriptionPlan.AI:
await this.feature.removeCopilot(userId);
await this.models.userFeature.remove(userId, 'unlimited_copilot');
break;
case SubscriptionPlan.Pro: {
// edge case: when user switch from recurring Pro plan to `Lifetime` plan,
// a subscription canceled event will be triggered because `Lifetime` plan is not subscription based
const quota = await this.quotaService.getUserQuota(userId);
if (quota.feature.name !== QuotaType.LifetimeProPlanV1) {
await this.quotaService.switchUserQuota(
const isLifetimeUser = await this.models.userFeature.has(
userId,
'lifetime_pro_plan_v1'
);
if (!isLifetimeUser) {
await this.models.userFeature.switchQuota(
userId,
QuotaType.FreePlanV1,
'free_plan_v1',
'subscription canceled'
);
}
@@ -26,7 +26,7 @@ import {
UserNotFound,
} from '../../base';
import { CurrentUser } from '../../core/auth';
import { FeatureManagementService } from '../../core/features';
import { FeatureService } from '../../core/features';
import { Models } from '../../models';
import {
CheckoutParams,
@@ -83,7 +83,7 @@ export class SubscriptionService implements OnApplicationBootstrap {
private readonly config: Config,
private readonly stripe: Stripe,
private readonly db: PrismaClient,
private readonly feature: FeatureManagementService,
private readonly feature: FeatureService,
private readonly models: Models,
private readonly userManager: UserSubscriptionManager,
private readonly workspaceManager: WorkspaceSubscriptionManager,