mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 02:00:49 +08:00
fix(server): dirty data handle (#15034)
#### PR Dependency Tree * **PR #15034** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Consolidated subscription visibility and “active” selection logic so all subscription queries use a shared, consistent filter across the platform. * **Tests** * Added a test to ensure expired subscriptions are excluded from active subscription results. * Updated test fixtures to differentiate expired, unexpired, and onetime subscriptions for more accurate coverage. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/15034?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1043,3 +1043,44 @@ test('should refresh user subscriptions (empty / revenuecat / stripe-only)', asy
|
||||
t.is(subs.length, 1, 'case3: only stripe subscription returned');
|
||||
}
|
||||
});
|
||||
|
||||
test('user subscriptions ignore active rows after their current period ended', async t => {
|
||||
const { db, subResolver } = t.context;
|
||||
|
||||
await db.subscription.createMany({
|
||||
data: [
|
||||
{
|
||||
targetId: user.id,
|
||||
plan: 'ai',
|
||||
provider: 'stripe',
|
||||
status: 'active',
|
||||
recurring: 'yearly',
|
||||
start: new Date('2025-01-01T00:00:00.000Z'),
|
||||
end: new Date('2025-01-08T00:00:00.000Z'),
|
||||
stripeSubscriptionId: 'sub_expired_ai',
|
||||
},
|
||||
{
|
||||
targetId: user.id,
|
||||
plan: 'pro',
|
||||
provider: 'stripe',
|
||||
status: 'active',
|
||||
recurring: 'yearly',
|
||||
start: new Date('2025-01-01T00:00:00.000Z'),
|
||||
end: new Date('2099-01-01T00:00:00.000Z'),
|
||||
stripeSubscriptionId: 'sub_current_pro',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const subscriptions = await subResolver.subscriptions(user, user);
|
||||
t.deepEqual(subscriptions.map(subscription => subscription.plan).sort(), [
|
||||
'pro',
|
||||
]);
|
||||
|
||||
const manager = t.context.module.get(UserSubscriptionManager);
|
||||
const activeAI = await manager.getActiveSubscription({
|
||||
userId: user.id,
|
||||
plan: SubscriptionPlan.AI,
|
||||
});
|
||||
t.is(activeAI, null);
|
||||
});
|
||||
|
||||
@@ -420,7 +420,7 @@ test('should throw if user has subscription already', async t => {
|
||||
recurring: SubscriptionRecurring.Monthly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -848,7 +848,7 @@ test('should be able to cancel subscription', async t => {
|
||||
recurring: SubscriptionRecurring.Yearly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1368,7 +1368,7 @@ test('should be able to subscribe to lifetime recurring with old subscription',
|
||||
recurring: SubscriptionRecurring.Monthly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1402,7 +1402,7 @@ test('should not be able to cancel lifetime subscription', async t => {
|
||||
recurring: SubscriptionRecurring.Lifetime,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1426,7 +1426,7 @@ test('should not be able to update lifetime recurring', async t => {
|
||||
recurring: SubscriptionRecurring.Lifetime,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1481,7 +1481,7 @@ test('should be able to checkout onetime payment if previous subscription is one
|
||||
variant: SubscriptionVariant.Onetime,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1518,7 +1518,7 @@ test('should not be able to checkout out onetime payment if previous subscriptio
|
||||
recurring: SubscriptionRecurring.Monthly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1698,7 +1698,7 @@ test('should not be able to checkout for workspace if subscribed', async t => {
|
||||
recurring: SubscriptionRecurring.Monthly,
|
||||
status: SubscriptionStatus.Active,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
end: new Date(Date.now() + 100000),
|
||||
quantity: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient, UserStripeCustomer } from '@prisma/client';
|
||||
import { type Prisma, PrismaClient, UserStripeCustomer } from '@prisma/client';
|
||||
import Stripe from 'stripe';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -13,9 +13,40 @@ import {
|
||||
LookupKey,
|
||||
SubscriptionPlan,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionStatus,
|
||||
SubscriptionVariant,
|
||||
} from '../types';
|
||||
|
||||
export function validSubscriptionPeriodWhere(
|
||||
now = new Date()
|
||||
): Prisma.SubscriptionWhereInput {
|
||||
return { OR: [{ end: null }, { end: { gt: now } }] };
|
||||
}
|
||||
|
||||
export function activeSubscriptionWhere(
|
||||
now = new Date()
|
||||
): Prisma.SubscriptionWhereInput {
|
||||
return {
|
||||
status: { in: [SubscriptionStatus.Active, SubscriptionStatus.Trialing] },
|
||||
...validSubscriptionPeriodWhere(now),
|
||||
};
|
||||
}
|
||||
|
||||
export function visibleSubscriptionWhere(
|
||||
now = new Date()
|
||||
): Prisma.SubscriptionWhereInput {
|
||||
return {
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
...validSubscriptionPeriodWhere(now),
|
||||
};
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
stripeSubscriptionId: string | null;
|
||||
stripeScheduleId: string | null;
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
LookupKey,
|
||||
SubscriptionPlan,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionStatus,
|
||||
} from '../types';
|
||||
import {
|
||||
activeSubscriptionWhere,
|
||||
CheckoutParams,
|
||||
Invoice,
|
||||
Subscription,
|
||||
@@ -199,9 +199,7 @@ export class SelfhostTeamSubscriptionManager extends SubscriptionManager {
|
||||
where: {
|
||||
targetId: identity.key,
|
||||
plan: identity.plan,
|
||||
status: {
|
||||
in: [SubscriptionStatus.Active, SubscriptionStatus.Trialing],
|
||||
},
|
||||
...activeSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,7 +34,12 @@ import {
|
||||
SubscriptionStatus,
|
||||
SubscriptionVariant,
|
||||
} from '../types';
|
||||
import { CheckoutParams, Subscription, SubscriptionManager } from './common';
|
||||
import {
|
||||
activeSubscriptionWhere,
|
||||
CheckoutParams,
|
||||
Subscription,
|
||||
SubscriptionManager,
|
||||
} from './common';
|
||||
|
||||
interface PriceStrategyStatus {
|
||||
proEarlyAccess: boolean;
|
||||
@@ -224,9 +229,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
where: {
|
||||
targetId: args.userId,
|
||||
plan: args.plan,
|
||||
status: {
|
||||
in: [SubscriptionStatus.Active, SubscriptionStatus.Trialing],
|
||||
},
|
||||
...activeSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
SubscriptionStatus,
|
||||
} from '../types';
|
||||
import {
|
||||
activeSubscriptionWhere,
|
||||
CheckoutParams,
|
||||
Invoice,
|
||||
Subscription,
|
||||
@@ -225,9 +226,7 @@ export class WorkspaceSubscriptionManager extends SubscriptionManager {
|
||||
return this.db.subscription.findFirst({
|
||||
where: {
|
||||
targetId: identity.workspaceId,
|
||||
status: {
|
||||
in: [SubscriptionStatus.Active, SubscriptionStatus.Trialing],
|
||||
},
|
||||
...activeSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ import { CurrentUser, Public } from '../../core/auth';
|
||||
import { PermissionAccess } from '../../core/permission';
|
||||
import { UserType } from '../../core/user';
|
||||
import { WorkspaceType } from '../../core/workspaces';
|
||||
import { Invoice, Subscription, WorkspaceSubscriptionManager } from './manager';
|
||||
import {
|
||||
Invoice,
|
||||
Subscription,
|
||||
visibleSubscriptionWhere,
|
||||
WorkspaceSubscriptionManager,
|
||||
} from './manager';
|
||||
import { RevenueCatWebhookHandler } from './revenuecat';
|
||||
import { CheckoutParams, SubscriptionService } from './service';
|
||||
import {
|
||||
@@ -493,13 +498,7 @@ export class UserSubscriptionResolver {
|
||||
const subscriptions = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
...visibleSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -577,13 +576,7 @@ export class UserSubscriptionResolver {
|
||||
current = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
...visibleSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
// ignore errors
|
||||
@@ -608,13 +601,7 @@ export class UserSubscriptionResolver {
|
||||
let current = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
...visibleSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -641,13 +628,7 @@ export class UserSubscriptionResolver {
|
||||
current = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
...visibleSubscriptionWhere(),
|
||||
},
|
||||
});
|
||||
// ignore errors
|
||||
|
||||
Reference in New Issue
Block a user