mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(server): support ai plan (#6216)
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[user_id,plan]` on the table `user_subscriptions` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "user_subscriptions_user_id_key";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "user_subscriptions_user_id_plan_key" ON "user_subscriptions"("user_id", "plan");
|
||||||
@@ -24,7 +24,7 @@ model User {
|
|||||||
|
|
||||||
features UserFeatures[]
|
features UserFeatures[]
|
||||||
customer UserStripeCustomer?
|
customer UserStripeCustomer?
|
||||||
subscription UserSubscription?
|
subscriptions UserSubscription[]
|
||||||
invoices UserInvoice[]
|
invoices UserInvoice[]
|
||||||
workspacePermissions WorkspaceUserPermission[]
|
workspacePermissions WorkspaceUserPermission[]
|
||||||
pagePermissions WorkspacePageUserPermission[]
|
pagePermissions WorkspacePageUserPermission[]
|
||||||
@@ -369,7 +369,7 @@ model UserStripeCustomer {
|
|||||||
|
|
||||||
model UserSubscription {
|
model UserSubscription {
|
||||||
id Int @id @default(autoincrement()) @db.Integer
|
id Int @id @default(autoincrement()) @db.Integer
|
||||||
userId String @unique @map("user_id") @db.VarChar(36)
|
userId String @map("user_id") @db.VarChar(36)
|
||||||
plan String @db.VarChar(20)
|
plan String @db.VarChar(20)
|
||||||
// yearly/monthly
|
// yearly/monthly
|
||||||
recurring String @db.VarChar(20)
|
recurring String @db.VarChar(20)
|
||||||
@@ -395,6 +395,7 @@ model UserSubscription {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([userId, plan])
|
||||||
@@map("user_subscriptions")
|
@@map("user_subscriptions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export class SubscriptionResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extend it when new plans are added
|
// extend it when new plans are added
|
||||||
const fixedPlans = [SubscriptionPlan.Pro];
|
const fixedPlans = [SubscriptionPlan.Pro, SubscriptionPlan.AI];
|
||||||
|
|
||||||
return fixedPlans.reduce((prices, plan) => {
|
return fixedPlans.reduce((prices, plan) => {
|
||||||
const price = findPrice(plan);
|
const price = findPrice(plan);
|
||||||
@@ -242,17 +242,35 @@ export class SubscriptionResolver {
|
|||||||
@Mutation(() => UserSubscriptionType)
|
@Mutation(() => UserSubscriptionType)
|
||||||
async cancelSubscription(
|
async cancelSubscription(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args({
|
||||||
|
name: 'plan',
|
||||||
|
type: () => SubscriptionPlan,
|
||||||
|
nullable: true,
|
||||||
|
defaultValue: SubscriptionPlan.Pro,
|
||||||
|
})
|
||||||
|
plan: SubscriptionPlan,
|
||||||
@Args('idempotencyKey') idempotencyKey: string
|
@Args('idempotencyKey') idempotencyKey: string
|
||||||
) {
|
) {
|
||||||
return this.service.cancelSubscription(idempotencyKey, user.id);
|
return this.service.cancelSubscription(idempotencyKey, user.id, plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => UserSubscriptionType)
|
@Mutation(() => UserSubscriptionType)
|
||||||
async resumeSubscription(
|
async resumeSubscription(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args({
|
||||||
|
name: 'plan',
|
||||||
|
type: () => SubscriptionPlan,
|
||||||
|
nullable: true,
|
||||||
|
defaultValue: SubscriptionPlan.Pro,
|
||||||
|
})
|
||||||
|
plan: SubscriptionPlan,
|
||||||
@Args('idempotencyKey') idempotencyKey: string
|
@Args('idempotencyKey') idempotencyKey: string
|
||||||
) {
|
) {
|
||||||
return this.service.resumeCanceledSubscription(idempotencyKey, user.id);
|
return this.service.resumeCanceledSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
user.id,
|
||||||
|
plan
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => UserSubscriptionType)
|
@Mutation(() => UserSubscriptionType)
|
||||||
@@ -260,11 +278,19 @@ export class SubscriptionResolver {
|
|||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
@Args({ name: 'recurring', type: () => SubscriptionRecurring })
|
@Args({ name: 'recurring', type: () => SubscriptionRecurring })
|
||||||
recurring: SubscriptionRecurring,
|
recurring: SubscriptionRecurring,
|
||||||
|
@Args({
|
||||||
|
name: 'plan',
|
||||||
|
type: () => SubscriptionPlan,
|
||||||
|
nullable: true,
|
||||||
|
defaultValue: SubscriptionPlan.Pro,
|
||||||
|
})
|
||||||
|
plan: SubscriptionPlan,
|
||||||
@Args('idempotencyKey') idempotencyKey: string
|
@Args('idempotencyKey') idempotencyKey: string
|
||||||
) {
|
) {
|
||||||
return this.service.updateSubscriptionRecurring(
|
return this.service.updateSubscriptionRecurring(
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
user.id,
|
user.id,
|
||||||
|
plan,
|
||||||
recurring
|
recurring
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -277,11 +303,21 @@ export class UserSubscriptionResolver {
|
|||||||
private readonly db: PrismaClient
|
private readonly db: PrismaClient
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ResolveField(() => UserSubscriptionType, { nullable: true })
|
@ResolveField(() => UserSubscriptionType, {
|
||||||
|
nullable: true,
|
||||||
|
deprecationReason: 'use `UserType.subscriptions`',
|
||||||
|
})
|
||||||
async subscription(
|
async subscription(
|
||||||
@Context() ctx: { isAdminQuery: boolean },
|
@Context() ctx: { isAdminQuery: boolean },
|
||||||
@CurrentUser() me: User,
|
@CurrentUser() me: User,
|
||||||
@Parent() user: User
|
@Parent() user: User,
|
||||||
|
@Args({
|
||||||
|
name: 'plan',
|
||||||
|
type: () => SubscriptionPlan,
|
||||||
|
nullable: true,
|
||||||
|
defaultValue: SubscriptionPlan.Pro,
|
||||||
|
})
|
||||||
|
plan: SubscriptionPlan
|
||||||
) {
|
) {
|
||||||
// allow admin to query other user's subscription
|
// allow admin to query other user's subscription
|
||||||
if (!ctx.isAdminQuery && me.id !== user.id) {
|
if (!ctx.isAdminQuery && me.id !== user.id) {
|
||||||
@@ -311,12 +347,33 @@ export class UserSubscriptionResolver {
|
|||||||
|
|
||||||
return this.db.userSubscription.findUnique({
|
return this.db.userSubscription.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId_plan: {
|
||||||
|
userId: user.id,
|
||||||
|
plan,
|
||||||
|
},
|
||||||
status: SubscriptionStatus.Active,
|
status: SubscriptionStatus.Active,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => [UserSubscriptionType])
|
||||||
|
async subscriptions(
|
||||||
|
@CurrentUser() me: User,
|
||||||
|
@Parent() user: User
|
||||||
|
): Promise<UserSubscription[]> {
|
||||||
|
if (me.id !== user.id) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
'You are not allowed to access this subscription.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.db.userSubscription.findMany({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@ResolveField(() => [UserInvoiceType])
|
@ResolveField(() => [UserInvoiceType])
|
||||||
async invoices(
|
async invoices(
|
||||||
@CurrentUser() me: User,
|
@CurrentUser() me: User,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { OnEvent as RawOnEvent } from '@nestjs/event-emitter';
|
import { OnEvent as RawOnEvent } from '@nestjs/event-emitter';
|
||||||
import type {
|
import type {
|
||||||
Prisma,
|
Prisma,
|
||||||
@@ -88,12 +88,15 @@ export class SubscriptionService {
|
|||||||
const currentSubscription = await this.db.userSubscription.findFirst({
|
const currentSubscription = await this.db.userSubscription.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
plan,
|
||||||
status: SubscriptionStatus.Active,
|
status: SubscriptionStatus.Active,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentSubscription) {
|
if (currentSubscription) {
|
||||||
throw new Error('You already have a subscription');
|
throw new BadRequestException(
|
||||||
|
`You've already subscripted to the ${plan} plan`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = await this.getPrice(plan, recurring);
|
const price = await this.getPrice(plan, recurring);
|
||||||
@@ -154,35 +157,47 @@ export class SubscriptionService {
|
|||||||
|
|
||||||
async cancelSubscription(
|
async cancelSubscription(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
userId: string
|
userId: string,
|
||||||
|
plan: SubscriptionPlan
|
||||||
): Promise<UserSubscription> {
|
): Promise<UserSubscription> {
|
||||||
const user = await this.db.user.findUnique({
|
const user = await this.db.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
subscription: true,
|
subscriptions: {
|
||||||
|
where: {
|
||||||
|
plan,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.subscription) {
|
if (!user) {
|
||||||
throw new Error('You do not have any subscription');
|
throw new BadRequestException('Unknown user');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.subscription.canceledAt) {
|
const subscriptionInDB = user?.subscriptions.find(s => s.plan === plan);
|
||||||
throw new Error('Your subscription has already been canceled');
|
if (!subscriptionInDB) {
|
||||||
|
throw new BadRequestException(`You didn't subscript to the ${plan} plan`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptionInDB.canceledAt) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Your subscription has already been canceled'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// should release the schedule first
|
// should release the schedule first
|
||||||
if (user.subscription.stripeScheduleId) {
|
if (subscriptionInDB.stripeScheduleId) {
|
||||||
const manager = await this.scheduleManager.fromSchedule(
|
const manager = await this.scheduleManager.fromSchedule(
|
||||||
user.subscription.stripeScheduleId
|
subscriptionInDB.stripeScheduleId
|
||||||
);
|
);
|
||||||
await manager.cancel(idempotencyKey);
|
await manager.cancel(idempotencyKey);
|
||||||
return this.saveSubscription(
|
return this.saveSubscription(
|
||||||
user,
|
user,
|
||||||
await this.stripe.subscriptions.retrieve(
|
await this.stripe.subscriptions.retrieve(
|
||||||
user.subscription.stripeSubscriptionId
|
subscriptionInDB.stripeSubscriptionId
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@@ -190,7 +205,7 @@ export class SubscriptionService {
|
|||||||
// let customer contact support if they want to cancel immediately
|
// let customer contact support if they want to cancel immediately
|
||||||
// see https://stripe.com/docs/billing/subscriptions/cancel
|
// see https://stripe.com/docs/billing/subscriptions/cancel
|
||||||
const subscription = await this.stripe.subscriptions.update(
|
const subscription = await this.stripe.subscriptions.update(
|
||||||
user.subscription.stripeSubscriptionId,
|
subscriptionInDB.stripeSubscriptionId,
|
||||||
{ cancel_at_period_end: true },
|
{ cancel_at_period_end: true },
|
||||||
{ idempotencyKey }
|
{ idempotencyKey }
|
||||||
);
|
);
|
||||||
@@ -200,44 +215,52 @@ export class SubscriptionService {
|
|||||||
|
|
||||||
async resumeCanceledSubscription(
|
async resumeCanceledSubscription(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
userId: string
|
userId: string,
|
||||||
|
plan: SubscriptionPlan
|
||||||
): Promise<UserSubscription> {
|
): Promise<UserSubscription> {
|
||||||
const user = await this.db.user.findUnique({
|
const user = await this.db.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
subscription: true,
|
subscriptions: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.subscription) {
|
if (!user) {
|
||||||
throw new Error('You do not have any subscription');
|
throw new BadRequestException('Unknown user');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.subscription.canceledAt) {
|
const subscriptionInDB = user?.subscriptions.find(s => s.plan === plan);
|
||||||
throw new Error('Your subscription has not been canceled');
|
if (!subscriptionInDB) {
|
||||||
|
throw new BadRequestException(`You didn't subscript to the ${plan} plan`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.subscription.end < new Date()) {
|
if (!subscriptionInDB.canceledAt) {
|
||||||
throw new Error('Your subscription is expired, please checkout again.');
|
throw new BadRequestException('Your subscription has not been canceled');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.subscription.stripeScheduleId) {
|
if (subscriptionInDB.end < new Date()) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Your subscription is expired, please checkout again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptionInDB.stripeScheduleId) {
|
||||||
const manager = await this.scheduleManager.fromSchedule(
|
const manager = await this.scheduleManager.fromSchedule(
|
||||||
user.subscription.stripeScheduleId
|
subscriptionInDB.stripeScheduleId
|
||||||
);
|
);
|
||||||
await manager.resume(idempotencyKey);
|
await manager.resume(idempotencyKey);
|
||||||
return this.saveSubscription(
|
return this.saveSubscription(
|
||||||
user,
|
user,
|
||||||
await this.stripe.subscriptions.retrieve(
|
await this.stripe.subscriptions.retrieve(
|
||||||
user.subscription.stripeSubscriptionId
|
subscriptionInDB.stripeSubscriptionId
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const subscription = await this.stripe.subscriptions.update(
|
const subscription = await this.stripe.subscriptions.update(
|
||||||
user.subscription.stripeSubscriptionId,
|
subscriptionInDB.stripeSubscriptionId,
|
||||||
{ cancel_at_period_end: false },
|
{ cancel_at_period_end: false },
|
||||||
{ idempotencyKey }
|
{ idempotencyKey }
|
||||||
);
|
);
|
||||||
@@ -249,6 +272,7 @@ export class SubscriptionService {
|
|||||||
async updateSubscriptionRecurring(
|
async updateSubscriptionRecurring(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
|
plan: SubscriptionPlan,
|
||||||
recurring: SubscriptionRecurring
|
recurring: SubscriptionRecurring
|
||||||
): Promise<UserSubscription> {
|
): Promise<UserSubscription> {
|
||||||
const user = await this.db.user.findUnique({
|
const user = await this.db.user.findUnique({
|
||||||
@@ -256,30 +280,38 @@ export class SubscriptionService {
|
|||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
subscription: true,
|
subscriptions: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user?.subscription) {
|
if (!user) {
|
||||||
throw new Error('You do not have any subscription');
|
throw new BadRequestException('Unknown user');
|
||||||
|
}
|
||||||
|
const subscriptionInDB = user?.subscriptions.find(s => s.plan === plan);
|
||||||
|
if (!subscriptionInDB) {
|
||||||
|
throw new BadRequestException(`You didn't subscript to the ${plan} plan`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.subscription.canceledAt) {
|
if (subscriptionInDB.canceledAt) {
|
||||||
throw new Error('Your subscription has already been canceled ');
|
throw new BadRequestException(
|
||||||
|
'Your subscription has already been canceled '
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.subscription.recurring === recurring) {
|
if (subscriptionInDB.recurring === recurring) {
|
||||||
throw new Error('You have already subscribed to this plan');
|
throw new BadRequestException(
|
||||||
|
`You are already in ${recurring} recurring`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = await this.getPrice(
|
const price = await this.getPrice(
|
||||||
user.subscription.plan as SubscriptionPlan,
|
subscriptionInDB.plan as SubscriptionPlan,
|
||||||
recurring
|
recurring
|
||||||
);
|
);
|
||||||
|
|
||||||
const manager = await this.scheduleManager.fromSubscription(
|
const manager = await this.scheduleManager.fromSubscription(
|
||||||
`${idempotencyKey}-fromSubscription`,
|
`${idempotencyKey}-fromSubscription`,
|
||||||
user.subscription.stripeSubscriptionId
|
subscriptionInDB.stripeSubscriptionId
|
||||||
);
|
);
|
||||||
|
|
||||||
await manager.update(
|
await manager.update(
|
||||||
@@ -295,7 +327,7 @@ export class SubscriptionService {
|
|||||||
|
|
||||||
return await this.db.userSubscription.update({
|
return await this.db.userSubscription.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.subscription.id,
|
id: subscriptionInDB.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
stripeScheduleId: manager.schedule?.id ?? null, // update schedule id or set to null(undefined means untouched)
|
stripeScheduleId: manager.schedule?.id ?? null, // update schedule id or set to null(undefined means untouched)
|
||||||
@@ -312,7 +344,7 @@ export class SubscriptionService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('Unknown user');
|
throw new BadRequestException('Unknown user');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -323,7 +355,7 @@ export class SubscriptionService {
|
|||||||
return portal.url;
|
return portal.url;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error('Failed to create customer portal.', e);
|
this.logger.error('Failed to create customer portal.', e);
|
||||||
throw new Error('Failed to create customer portal');
|
throw new BadRequestException('Failed to create customer portal');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,7 +552,10 @@ export class SubscriptionService {
|
|||||||
|
|
||||||
const currentSubscription = await this.db.userSubscription.findUnique({
|
const currentSubscription = await this.db.userSubscription.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId_plan: {
|
||||||
|
userId: user.id,
|
||||||
|
plan,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -643,8 +678,8 @@ export class SubscriptionService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!prices.data.length) {
|
if (!prices.data.length) {
|
||||||
throw new Error(
|
throw new BadRequestException(
|
||||||
`Unknown subscription plan ${plan} with recurring ${recurring}`
|
`Unknown subscription plan ${plan} with ${recurring} recurring`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export enum SubscriptionRecurring {
|
|||||||
export enum SubscriptionPlan {
|
export enum SubscriptionPlan {
|
||||||
Free = 'free',
|
Free = 'free',
|
||||||
Pro = 'pro',
|
Pro = 'pro',
|
||||||
|
AI = 'ai',
|
||||||
Team = 'team',
|
Team = 'team',
|
||||||
Enterprise = 'enterprise',
|
Enterprise = 'enterprise',
|
||||||
SelfHosted = 'selfhosted',
|
SelfHosted = 'selfhosted',
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ type Mutation {
|
|||||||
acceptInviteById(inviteId: String!, sendAcceptMail: Boolean, workspaceId: String!): Boolean!
|
acceptInviteById(inviteId: String!, sendAcceptMail: Boolean, workspaceId: String!): Boolean!
|
||||||
addToEarlyAccess(email: String!): Int!
|
addToEarlyAccess(email: String!): Int!
|
||||||
addWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
addWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||||
cancelSubscription(idempotencyKey: String!): UserSubscription!
|
cancelSubscription(idempotencyKey: String!, plan: SubscriptionPlan = Pro): UserSubscription!
|
||||||
changeEmail(email: String!, token: String!): UserType!
|
changeEmail(email: String!, token: String!): UserType!
|
||||||
changePassword(newPassword: String!, token: String!): UserType!
|
changePassword(newPassword: String!, token: String!): UserType!
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ type Mutation {
|
|||||||
removeAvatar: RemoveAvatar!
|
removeAvatar: RemoveAvatar!
|
||||||
removeEarlyAccess(email: String!): Int!
|
removeEarlyAccess(email: String!): Int!
|
||||||
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||||
resumeSubscription(idempotencyKey: String!): UserSubscription!
|
resumeSubscription(idempotencyKey: String!, plan: SubscriptionPlan = Pro): UserSubscription!
|
||||||
revoke(userId: String!, workspaceId: String!): Boolean!
|
revoke(userId: String!, workspaceId: String!): Boolean!
|
||||||
revokePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "use revokePublicPage")
|
revokePage(pageId: String!, workspaceId: String!): Boolean! @deprecated(reason: "use revokePublicPage")
|
||||||
revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage!
|
revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage!
|
||||||
@@ -149,7 +149,7 @@ type Mutation {
|
|||||||
signIn(email: String!, password: String!): UserType!
|
signIn(email: String!, password: String!): UserType!
|
||||||
signUp(email: String!, name: String!, password: String!): UserType!
|
signUp(email: String!, name: String!, password: String!): UserType!
|
||||||
updateProfile(input: UpdateUserInput!): UserType!
|
updateProfile(input: UpdateUserInput!): UserType!
|
||||||
updateSubscriptionRecurring(idempotencyKey: String!, recurring: SubscriptionRecurring!): UserSubscription!
|
updateSubscriptionRecurring(idempotencyKey: String!, plan: SubscriptionPlan = Pro, recurring: SubscriptionRecurring!): UserSubscription!
|
||||||
|
|
||||||
"""Update workspace"""
|
"""Update workspace"""
|
||||||
updateWorkspace(input: UpdateWorkspaceInput!): WorkspaceType!
|
updateWorkspace(input: UpdateWorkspaceInput!): WorkspaceType!
|
||||||
@@ -264,6 +264,7 @@ enum ServerFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum SubscriptionPlan {
|
enum SubscriptionPlan {
|
||||||
|
AI
|
||||||
Enterprise
|
Enterprise
|
||||||
Free
|
Free
|
||||||
Pro
|
Pro
|
||||||
@@ -385,7 +386,8 @@ type UserType {
|
|||||||
"""User name"""
|
"""User name"""
|
||||||
name: String!
|
name: String!
|
||||||
quota: UserQuota
|
quota: UserQuota
|
||||||
subscription: UserSubscription
|
subscription(plan: SubscriptionPlan = Pro): UserSubscription @deprecated(reason: "use `UserType.subscriptions`")
|
||||||
|
subscriptions: [UserSubscription!]!
|
||||||
token: tokenType! @deprecated(reason: "use [/api/auth/authorize]")
|
token: tokenType! @deprecated(reason: "use [/api/auth/authorize]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user