mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
@@ -17,6 +17,77 @@ export class SubscriptionCronJobs {
|
||||
private readonly event: EventEmitter
|
||||
) {}
|
||||
|
||||
private getDateRange(after: number, base: number | Date = Date.now()) {
|
||||
const start = new Date(base);
|
||||
start.setDate(start.getDate() + after);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
const end = new Date(start);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
// TODO(@darkskygit): enable this after the cluster event system is ready
|
||||
// @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT /* everyday at 12am */)
|
||||
async notifyExpiredWorkspace() {
|
||||
const { start: after30DayStart, end: after30DayEnd } =
|
||||
this.getDateRange(30);
|
||||
const { start: todayStart, end: todayEnd } = this.getDateRange(0);
|
||||
const { start: before150DaysStart, end: before150DaysEnd } =
|
||||
this.getDateRange(-150);
|
||||
const { start: before180DaysStart, end: before180DaysEnd } =
|
||||
this.getDateRange(-180);
|
||||
|
||||
const subscriptions = await this.db.subscription.findMany({
|
||||
where: {
|
||||
plan: SubscriptionPlan.Team,
|
||||
OR: [
|
||||
{
|
||||
// subscription will cancel after 30 days
|
||||
status: 'active',
|
||||
canceledAt: { not: null },
|
||||
end: { gte: after30DayStart, lte: after30DayEnd },
|
||||
},
|
||||
{
|
||||
// subscription will cancel today
|
||||
status: 'active',
|
||||
canceledAt: { not: null },
|
||||
end: { gte: todayStart, lte: todayEnd },
|
||||
},
|
||||
{
|
||||
// subscription has been canceled for 150 days
|
||||
// workspace becomes delete after 180 days
|
||||
status: 'canceled',
|
||||
canceledAt: { gte: before150DaysStart, lte: before150DaysEnd },
|
||||
},
|
||||
{
|
||||
// subscription has been canceled for 180 days
|
||||
// workspace becomes delete after 180 days
|
||||
status: 'canceled',
|
||||
canceledAt: { gte: before180DaysStart, lte: before180DaysEnd },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
const end = subscription.end;
|
||||
if (!end) {
|
||||
// should not reach here
|
||||
continue;
|
||||
}
|
||||
this.event.emit('workspace.subscription.notify', {
|
||||
workspaceId: subscription.targetId,
|
||||
expirationDate: end,
|
||||
deletionDate:
|
||||
subscription.status === 'canceled'
|
||||
? this.getDateRange(180, end).end
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Cron(CronExpression.EVERY_HOUR)
|
||||
async cleanExpiredOnetimeSubscriptions() {
|
||||
const subscriptions = await this.db.subscription.findMany({
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FeatureModule } from '../../core/features';
|
||||
import { PermissionModule } from '../../core/permission';
|
||||
import { QuotaModule } from '../../core/quota';
|
||||
import { UserModule } from '../../core/user';
|
||||
import { WorkspaceModule } from '../../core/workspaces';
|
||||
import { Plugin } from '../registry';
|
||||
import { StripeWebhookController } from './controller';
|
||||
import { SubscriptionCronJobs } from './cron';
|
||||
@@ -24,7 +25,13 @@ import { StripeWebhook } from './webhook';
|
||||
|
||||
@Plugin({
|
||||
name: 'payment',
|
||||
imports: [FeatureModule, QuotaModule, UserModule, PermissionModule],
|
||||
imports: [
|
||||
FeatureModule,
|
||||
QuotaModule,
|
||||
UserModule,
|
||||
PermissionModule,
|
||||
WorkspaceModule,
|
||||
],
|
||||
providers: [
|
||||
StripeProvider,
|
||||
SubscriptionService,
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import type { EventPayload } from '../../base';
|
||||
import { type EventPayload } from '../../base';
|
||||
import { PermissionService } from '../../core/permission';
|
||||
import { QuotaManagementService, QuotaType } from '../../core/quota';
|
||||
import {
|
||||
QuotaManagementService,
|
||||
QuotaService,
|
||||
QuotaType,
|
||||
} from '../../core/quota';
|
||||
import { WorkspaceService } from '../../core/workspaces/resolvers';
|
||||
|
||||
@Injectable()
|
||||
export class TeamQuotaOverride {
|
||||
constructor(
|
||||
private readonly quota: QuotaService,
|
||||
private readonly manager: QuotaManagementService,
|
||||
private readonly permission: PermissionService
|
||||
private readonly permission: PermissionService,
|
||||
private readonly workspace: WorkspaceService
|
||||
) {}
|
||||
|
||||
@OnEvent('workspace.subscription.activated')
|
||||
@@ -20,7 +27,11 @@ export class TeamQuotaOverride {
|
||||
quantity,
|
||||
}: EventPayload<'workspace.subscription.activated'>) {
|
||||
switch (plan) {
|
||||
case 'team':
|
||||
case 'team': {
|
||||
const hasTeamWorkspace = await this.quota.hasWorkspaceQuota(
|
||||
workspaceId,
|
||||
QuotaType.TeamPlanV1
|
||||
);
|
||||
await this.manager.addTeamWorkspace(
|
||||
workspaceId,
|
||||
`${recurring} team subscription activated`
|
||||
@@ -31,7 +42,13 @@ export class TeamQuotaOverride {
|
||||
{ memberLimit: quantity }
|
||||
);
|
||||
await this.permission.refreshSeatStatus(workspaceId, quantity);
|
||||
if (!hasTeamWorkspace) {
|
||||
// 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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,11 @@ declare module '../../base/event/def' {
|
||||
plan: SubscriptionPlan;
|
||||
recurring: SubscriptionRecurring;
|
||||
}>;
|
||||
notify: Payload<{
|
||||
workspaceId: Workspace['id'];
|
||||
expirationDate: Date;
|
||||
deletionDate: Date | undefined;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user