mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
fix(admin): organize admin panel (#7840)
This commit is contained in:
@@ -155,7 +155,7 @@ function buildAppModule() {
|
||||
.useIf(config => config.flavor.sync, WebSocketModule)
|
||||
|
||||
// auth
|
||||
.use(AuthModule)
|
||||
.use(UserModule, AuthModule)
|
||||
|
||||
// business modules
|
||||
.use(DocModule)
|
||||
@@ -169,7 +169,6 @@ function buildAppModule() {
|
||||
ServerConfigModule,
|
||||
GqlModule,
|
||||
StorageModule,
|
||||
UserModule,
|
||||
WorkspaceModule,
|
||||
FeatureModule,
|
||||
QuotaModule
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import type { User, UserSession } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import type { CookieOptions, Request, Response } from 'express';
|
||||
import { assign, omit } from 'lodash-es';
|
||||
import { assign, pick } from 'lodash-es';
|
||||
|
||||
import { Config, EmailAlreadyUsed, MailService } from '../../fundamentals';
|
||||
import { FeatureManagementService } from '../features/management';
|
||||
@@ -41,13 +41,11 @@ export function sessionUser(
|
||||
'id' | 'email' | 'avatarUrl' | 'name' | 'emailVerifiedAt'
|
||||
> & { password?: string | null }
|
||||
): CurrentUser {
|
||||
return assign(
|
||||
omit(user, 'password', 'registered', 'emailVerifiedAt', 'createdAt'),
|
||||
{
|
||||
hasPassword: user.password !== null,
|
||||
emailVerified: user.emailVerifiedAt !== null,
|
||||
}
|
||||
);
|
||||
// use pick to avoid unexpected fields
|
||||
return assign(pick(user, 'id', 'email', 'avatarUrl', 'name'), {
|
||||
hasPassword: user.password !== null,
|
||||
emailVerified: user.emailVerifiedAt !== null,
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -16,5 +16,5 @@ import {
|
||||
],
|
||||
})
|
||||
export class ServerConfigModule {}
|
||||
export { ADD_ENABLED_FEATURES, ServerConfigType } from './resolver';
|
||||
export { ADD_ENABLED_FEATURES } from './server-feature';
|
||||
export { ServerFeature } from './types';
|
||||
|
||||
@@ -12,24 +12,14 @@ import {
|
||||
import { PrismaClient, RuntimeConfig, RuntimeConfigType } from '@prisma/client';
|
||||
import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
|
||||
|
||||
import { Config, DeploymentType, URLHelper } from '../../fundamentals';
|
||||
import { Config, URLHelper } from '../../fundamentals';
|
||||
import { Public } from '../auth';
|
||||
import { Admin } from '../common';
|
||||
import { FeatureType } from '../features';
|
||||
import { AvailableUserFeatureConfig } from '../features/resolver';
|
||||
import { ServerFlags } from './config';
|
||||
import { ServerFeature } from './types';
|
||||
|
||||
const ENABLED_FEATURES: Set<ServerFeature> = new Set();
|
||||
export function ADD_ENABLED_FEATURES(feature: ServerFeature) {
|
||||
ENABLED_FEATURES.add(feature);
|
||||
}
|
||||
|
||||
registerEnumType(ServerFeature, {
|
||||
name: 'ServerFeature',
|
||||
});
|
||||
|
||||
registerEnumType(DeploymentType, {
|
||||
name: 'ServerDeploymentType',
|
||||
});
|
||||
import { ENABLED_FEATURES } from './server-feature';
|
||||
import { ServerConfigType } from './types';
|
||||
|
||||
@ObjectType()
|
||||
export class PasswordLimitsType {
|
||||
@@ -45,36 +35,6 @@ export class CredentialsRequirementType {
|
||||
password!: PasswordLimitsType;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ServerConfigType {
|
||||
@Field({
|
||||
description:
|
||||
'server identical name could be shown as badge on user interface',
|
||||
})
|
||||
name!: string;
|
||||
|
||||
@Field({ description: 'server version' })
|
||||
version!: string;
|
||||
|
||||
@Field({ description: 'server base url' })
|
||||
baseUrl!: string;
|
||||
|
||||
@Field(() => DeploymentType, { description: 'server type' })
|
||||
type!: DeploymentType;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Field({ description: 'server flavor', deprecationReason: 'use `features`' })
|
||||
flavor!: string;
|
||||
|
||||
@Field(() => [ServerFeature], { description: 'enabled server features' })
|
||||
features!: ServerFeature[];
|
||||
|
||||
@Field({ description: 'enable telemetry' })
|
||||
enableTelemetry!: boolean;
|
||||
}
|
||||
|
||||
registerEnumType(RuntimeConfigType, {
|
||||
name: 'RuntimeConfigType',
|
||||
});
|
||||
@@ -175,6 +135,20 @@ export class ServerConfigResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Resolver(() => ServerConfigType)
|
||||
export class ServerFeatureConfigResolver extends AvailableUserFeatureConfig {
|
||||
constructor(config: Config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@ResolveField(() => [FeatureType], {
|
||||
description: 'Features for user that can be configured',
|
||||
})
|
||||
override availableUserFeatures() {
|
||||
return super.availableUserFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class ServerServiceConfig {
|
||||
@Field()
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ServerFeature } from './types';
|
||||
|
||||
export const ENABLED_FEATURES: Set<ServerFeature> = new Set();
|
||||
export function ADD_ENABLED_FEATURES(feature: ServerFeature) {
|
||||
ENABLED_FEATURES.add(feature);
|
||||
}
|
||||
export { ServerFeature };
|
||||
@@ -1,5 +1,47 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { DeploymentType } from '../../fundamentals';
|
||||
|
||||
export enum ServerFeature {
|
||||
Copilot = 'copilot',
|
||||
Payment = 'payment',
|
||||
OAuth = 'oauth',
|
||||
}
|
||||
|
||||
registerEnumType(ServerFeature, {
|
||||
name: 'ServerFeature',
|
||||
});
|
||||
|
||||
registerEnumType(DeploymentType, {
|
||||
name: 'ServerDeploymentType',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
export class ServerConfigType {
|
||||
@Field({
|
||||
description:
|
||||
'server identical name could be shown as badge on user interface',
|
||||
})
|
||||
name!: string;
|
||||
|
||||
@Field({ description: 'server version' })
|
||||
version!: string;
|
||||
|
||||
@Field({ description: 'server base url' })
|
||||
baseUrl!: string;
|
||||
|
||||
@Field(() => DeploymentType, { description: 'server type' })
|
||||
type!: DeploymentType;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Field({ description: 'server flavor', deprecationReason: 'use `features`' })
|
||||
flavor!: string;
|
||||
|
||||
@Field(() => [ServerFeature], { description: 'enabled server features' })
|
||||
features!: ServerFeature[];
|
||||
|
||||
@Field({ description: 'enable telemetry' })
|
||||
enableTelemetry!: boolean;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { UserModule } from '../user';
|
||||
import { EarlyAccessType, FeatureManagementService } from './management';
|
||||
import { FeatureManagementResolver } from './resolver';
|
||||
import {
|
||||
AdminFeatureManagementResolver,
|
||||
FeatureManagementResolver,
|
||||
} from './resolver';
|
||||
import { FeatureService } from './service';
|
||||
|
||||
/**
|
||||
@@ -17,6 +20,7 @@ import { FeatureService } from './service';
|
||||
FeatureService,
|
||||
FeatureManagementService,
|
||||
FeatureManagementResolver,
|
||||
AdminFeatureManagementResolver,
|
||||
],
|
||||
exports: [FeatureService, FeatureManagementService],
|
||||
})
|
||||
|
||||
@@ -42,10 +42,6 @@ export class FeatureManagementService {
|
||||
return this.feature.addUserFeature(userId, FeatureType.Admin, 'Admin user');
|
||||
}
|
||||
|
||||
removeAdmin(userId: string) {
|
||||
return this.feature.removeUserFeature(userId, FeatureType.Admin);
|
||||
}
|
||||
|
||||
// ======== Early Access ========
|
||||
async addEarlyAccess(
|
||||
userId: string,
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import {
|
||||
Args,
|
||||
Context,
|
||||
Int,
|
||||
Mutation,
|
||||
Parent,
|
||||
Query,
|
||||
registerEnumType,
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import { difference } from 'lodash-es';
|
||||
|
||||
import { UserNotFound } from '../../fundamentals';
|
||||
import { sessionUser } from '../auth/service';
|
||||
import { Config } from '../../fundamentals';
|
||||
import { Admin } from '../common';
|
||||
import { UserService } from '../user/service';
|
||||
import { UserType } from '../user/types';
|
||||
import { EarlyAccessType, FeatureManagementService } from './management';
|
||||
import { FeatureService } from './service';
|
||||
import { FeatureType } from './types';
|
||||
|
||||
registerEnumType(EarlyAccessType, {
|
||||
@@ -24,10 +21,7 @@ registerEnumType(EarlyAccessType, {
|
||||
|
||||
@Resolver(() => UserType)
|
||||
export class FeatureManagementResolver {
|
||||
constructor(
|
||||
private readonly users: UserService,
|
||||
private readonly feature: FeatureManagementService
|
||||
) {}
|
||||
constructor(private readonly feature: FeatureManagementService) {}
|
||||
|
||||
@ResolveField(() => [FeatureType], {
|
||||
name: 'features',
|
||||
@@ -36,75 +30,48 @@ export class FeatureManagementResolver {
|
||||
async userFeatures(@Parent() user: UserType) {
|
||||
return this.feature.getActivatedUserFeatures(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => Int)
|
||||
async addToEarlyAccess(
|
||||
@Args('email') email: string,
|
||||
@Args({ name: 'type', type: () => EarlyAccessType }) type: EarlyAccessType
|
||||
): Promise<number> {
|
||||
const user = await this.users.findUserByEmail(email);
|
||||
if (user) {
|
||||
return this.feature.addEarlyAccess(user.id, type);
|
||||
} else {
|
||||
const user = await this.users.createUser({
|
||||
email,
|
||||
registered: false,
|
||||
});
|
||||
return this.feature.addEarlyAccess(user.id, type);
|
||||
}
|
||||
}
|
||||
export class AvailableUserFeatureConfig {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => Int)
|
||||
async removeEarlyAccess(
|
||||
@Args('email') email: string,
|
||||
@Args({ name: 'type', type: () => EarlyAccessType }) type: EarlyAccessType
|
||||
): Promise<number> {
|
||||
const user = await this.users.findUserByEmail(email);
|
||||
if (!user) {
|
||||
throw new UserNotFound();
|
||||
}
|
||||
return this.feature.removeEarlyAccess(user.id, type);
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Query(() => [UserType])
|
||||
async earlyAccessUsers(
|
||||
@Context() ctx: { isAdminQuery: boolean }
|
||||
): Promise<UserType[]> {
|
||||
// allow query other user's subscription
|
||||
ctx.isAdminQuery = true;
|
||||
return this.feature.listEarlyAccess().then(users => {
|
||||
return users.map(sessionUser);
|
||||
});
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => Boolean)
|
||||
async addAdminister(@Args('email') email: string): Promise<boolean> {
|
||||
const user = await this.users.findUserByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
throw new UserNotFound();
|
||||
}
|
||||
|
||||
await this.feature.addAdmin(user.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => Boolean)
|
||||
async removeAdminister(@Args('email') email: string): Promise<boolean> {
|
||||
const user = await this.users.findUserByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
throw new UserNotFound();
|
||||
}
|
||||
|
||||
await this.feature.removeAdmin(user.id);
|
||||
|
||||
return true;
|
||||
async availableUserFeatures() {
|
||||
return this.config.isSelfhosted
|
||||
? [FeatureType.Admin]
|
||||
: [FeatureType.EarlyAccess, FeatureType.AIEarlyAccess, FeatureType.Admin];
|
||||
}
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Resolver(() => Boolean)
|
||||
export class AdminFeatureManagementResolver extends AvailableUserFeatureConfig {
|
||||
constructor(
|
||||
config: Config,
|
||||
private readonly feature: FeatureService
|
||||
) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Mutation(() => [FeatureType], {
|
||||
description: 'update user enabled feature',
|
||||
})
|
||||
async updateUserFeatures(
|
||||
@Args('id') id: string,
|
||||
@Args({ name: 'features', type: () => [FeatureType] })
|
||||
features: FeatureType[]
|
||||
) {
|
||||
const configurableFeatures = await this.availableUserFeatures();
|
||||
|
||||
const removed = difference(configurableFeatures, features);
|
||||
await Promise.all(
|
||||
features.map(feature =>
|
||||
this.feature.addUserFeature(id, feature, 'admin panel')
|
||||
)
|
||||
);
|
||||
await Promise.all(
|
||||
removed.map(feature => this.feature.removeUserFeature(id, feature))
|
||||
);
|
||||
|
||||
return features;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import { CannotDeleteAllAdminAccount } from '../../fundamentals';
|
||||
import { WorkspaceType } from '../workspaces/types';
|
||||
import { FeatureConfigType, getFeature } from './feature';
|
||||
import { FeatureKind, FeatureType } from './types';
|
||||
@@ -81,6 +82,9 @@ export class FeatureService {
|
||||
}
|
||||
|
||||
async removeUserFeature(userId: string, feature: FeatureType) {
|
||||
if (feature === FeatureType.Admin) {
|
||||
await this.ensureNotLastAdmin(userId);
|
||||
}
|
||||
return this.prisma.userFeature
|
||||
.updateMany({
|
||||
where: {
|
||||
@@ -98,6 +102,20 @@ export class FeatureService {
|
||||
.then(r => r.count);
|
||||
}
|
||||
|
||||
async ensureNotLastAdmin(userId: string) {
|
||||
const count = await this.prisma.userFeature.count({
|
||||
where: {
|
||||
userId: { not: userId },
|
||||
feature: { feature: FeatureType.Admin, type: FeatureKind.Feature },
|
||||
activated: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
throw new CannotDeleteAllAdminAccount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get user's features, will included inactivated features
|
||||
* @param userId user id
|
||||
|
||||
@@ -12,7 +12,12 @@ import { PrismaClient } from '@prisma/client';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
import { isNil, omitBy } from 'lodash-es';
|
||||
|
||||
import { type FileUpload, Throttle, UserNotFound } from '../../fundamentals';
|
||||
import {
|
||||
CannotDeleteOwnAccount,
|
||||
type FileUpload,
|
||||
Throttle,
|
||||
UserNotFound,
|
||||
} from '../../fundamentals';
|
||||
import { CurrentUser } from '../auth/current-user';
|
||||
import { Public } from '../auth/guard';
|
||||
import { sessionUser } from '../auth/service';
|
||||
@@ -162,9 +167,6 @@ class CreateUserInput {
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
name!: string | null;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
password!: string | null;
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@@ -244,7 +246,6 @@ export class UserManagementResolver {
|
||||
) {
|
||||
const { id } = await this.user.createUser({
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
registered: true,
|
||||
});
|
||||
|
||||
@@ -255,7 +256,13 @@ export class UserManagementResolver {
|
||||
@Mutation(() => DeleteAccount, {
|
||||
description: 'Delete a user account',
|
||||
})
|
||||
async deleteUser(@Args('id') id: string): Promise<DeleteAccount> {
|
||||
async deleteUser(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('id') id: string
|
||||
): Promise<DeleteAccount> {
|
||||
if (user.id === id) {
|
||||
throw new CannotDeleteOwnAccount();
|
||||
}
|
||||
await this.user.deleteUser(id);
|
||||
return { success: true };
|
||||
}
|
||||
@@ -268,26 +275,22 @@ export class UserManagementResolver {
|
||||
@Args('input') input: ManageUserInput
|
||||
): Promise<UserType> {
|
||||
const user = await this.db.user.findUnique({
|
||||
select: { ...this.user.defaultUserSelect, password: true },
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UserNotFound();
|
||||
}
|
||||
validators.assertValidEmail(input.email);
|
||||
if (input.email !== user.email) {
|
||||
const exists = await this.db.user.findFirst({
|
||||
where: { email: input.email },
|
||||
});
|
||||
if (exists) {
|
||||
throw new Error('Email already exists');
|
||||
}
|
||||
|
||||
input = omitBy(input, isNil);
|
||||
if (Object.keys(input).length === 0) {
|
||||
return sessionUser(user);
|
||||
}
|
||||
|
||||
return sessionUser(
|
||||
await this.user.updateUser(user.id, {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,9 +194,7 @@ export class UserService {
|
||||
|
||||
async updateUser(
|
||||
id: string,
|
||||
data: Omit<Prisma.UserUpdateInput, 'password'> & {
|
||||
password?: string | null;
|
||||
},
|
||||
data: Omit<Partial<Prisma.UserCreateInput>, 'id'>,
|
||||
select: Prisma.UserSelect = this.defaultUserSelect
|
||||
) {
|
||||
if (data.password) {
|
||||
@@ -211,6 +209,23 @@ export class UserService {
|
||||
|
||||
data.password = await this.crypto.encryptPassword(data.password);
|
||||
}
|
||||
|
||||
if (data.email) {
|
||||
validators.assertValidEmail(data.email);
|
||||
const emailTaken = await this.prisma.user.count({
|
||||
where: {
|
||||
email: data.email,
|
||||
id: {
|
||||
not: id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (emailTaken) {
|
||||
throw new EmailAlreadyUsed();
|
||||
}
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.update({ where: { id }, data, select });
|
||||
|
||||
this.emitter.emit('user.updated', user);
|
||||
|
||||
@@ -85,11 +85,11 @@ export class UpdateUserInput implements Partial<User> {
|
||||
|
||||
@InputType()
|
||||
export class ManageUserInput {
|
||||
@Field({ description: 'User email', nullable: true })
|
||||
email?: string;
|
||||
|
||||
@Field({ description: 'User name', nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Field({ description: 'User email' })
|
||||
email!: string;
|
||||
}
|
||||
|
||||
declare module '../../fundamentals/event/def' {
|
||||
|
||||
@@ -498,4 +498,12 @@ export const USER_FRIENDLY_ERRORS = {
|
||||
type: 'internal_server_error',
|
||||
message: 'Mailer service is not configured.',
|
||||
},
|
||||
cannot_delete_all_admin_account: {
|
||||
type: 'action_forbidden',
|
||||
message: 'Cannot delete all admin accounts.',
|
||||
},
|
||||
cannot_delete_own_account: {
|
||||
type: 'action_forbidden',
|
||||
message: 'Cannot delete own account.',
|
||||
},
|
||||
} satisfies Record<string, UserFriendlyErrorOptions>;
|
||||
|
||||
@@ -487,6 +487,18 @@ export class MailerServiceIsNotConfigured extends UserFriendlyError {
|
||||
super('internal_server_error', 'mailer_service_is_not_configured', message);
|
||||
}
|
||||
}
|
||||
|
||||
export class CannotDeleteAllAdminAccount extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('action_forbidden', 'cannot_delete_all_admin_account', message);
|
||||
}
|
||||
}
|
||||
|
||||
export class CannotDeleteOwnAccount extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('action_forbidden', 'cannot_delete_own_account', message);
|
||||
}
|
||||
}
|
||||
export enum ErrorNames {
|
||||
INTERNAL_SERVER_ERROR,
|
||||
TOO_MANY_REQUEST,
|
||||
@@ -551,7 +563,9 @@ export enum ErrorNames {
|
||||
COPILOT_QUOTA_EXCEEDED,
|
||||
RUNTIME_CONFIG_NOT_FOUND,
|
||||
INVALID_RUNTIME_CONFIG_TYPE,
|
||||
MAILER_SERVICE_IS_NOT_CONFIGURED
|
||||
MAILER_SERVICE_IS_NOT_CONFIGURED,
|
||||
CANNOT_DELETE_ALL_ADMIN_ACCOUNT,
|
||||
CANNOT_DELETE_OWN_ACCOUNT
|
||||
}
|
||||
registerEnumType(ErrorNames, {
|
||||
name: 'ErrorNames'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerEnumType, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { ServerConfigType } from '../../core/config';
|
||||
import { ServerConfigType } from '../../core/config/types';
|
||||
import { OAuthProviderName } from './config';
|
||||
import { OAuthProviderFactory } from './register';
|
||||
|
||||
|
||||
@@ -152,7 +152,6 @@ input CreateCopilotPromptInput {
|
||||
input CreateUserInput {
|
||||
email: String!
|
||||
name: String
|
||||
password: String
|
||||
}
|
||||
|
||||
type CredentialsRequirementType {
|
||||
@@ -196,11 +195,6 @@ type DocNotFoundDataType {
|
||||
workspaceId: String!
|
||||
}
|
||||
|
||||
enum EarlyAccessType {
|
||||
AI
|
||||
App
|
||||
}
|
||||
|
||||
union ErrorDataUnion = BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInWorkspaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType | WorkspaceAccessDeniedDataType | WorkspaceNotFoundDataType | WorkspaceOwnerNotFoundDataType
|
||||
|
||||
enum ErrorNames {
|
||||
@@ -209,6 +203,8 @@ enum ErrorNames {
|
||||
AUTHENTICATION_REQUIRED
|
||||
BLOB_NOT_FOUND
|
||||
BLOB_QUOTA_EXCEEDED
|
||||
CANNOT_DELETE_ALL_ADMIN_ACCOUNT
|
||||
CANNOT_DELETE_OWN_ACCOUNT
|
||||
CANT_CHANGE_WORKSPACE_OWNER
|
||||
CANT_UPDATE_LIFETIME_SUBSCRIPTION
|
||||
COPILOT_ACTION_TAKEN
|
||||
@@ -398,7 +394,7 @@ input ListUserInput {
|
||||
|
||||
input ManageUserInput {
|
||||
"""User email"""
|
||||
email: String!
|
||||
email: String
|
||||
|
||||
"""User name"""
|
||||
name: String
|
||||
@@ -410,8 +406,6 @@ type MissingOauthQueryParameterDataType {
|
||||
|
||||
type Mutation {
|
||||
acceptInviteById(inviteId: String!, sendAcceptMail: Boolean, workspaceId: String!): Boolean!
|
||||
addAdminister(email: String!): Boolean!
|
||||
addToEarlyAccess(email: String!, type: EarlyAccessType!): Int!
|
||||
addWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||
cancelSubscription(idempotencyKey: String!, plan: SubscriptionPlan = Pro): UserSubscription!
|
||||
changeEmail(email: String!, token: String!): UserType!
|
||||
@@ -456,11 +450,9 @@ type Mutation {
|
||||
leaveWorkspace(sendLeaveMail: Boolean, workspaceId: String!, workspaceName: String!): Boolean!
|
||||
publishPage(mode: PublicPageMode = Page, pageId: String!, workspaceId: String!): WorkspacePage!
|
||||
recoverDoc(guid: String!, timestamp: DateTime!, workspaceId: String!): DateTime!
|
||||
removeAdminister(email: String!): Boolean!
|
||||
|
||||
"""Remove user avatar"""
|
||||
removeAvatar: RemoveAvatar!
|
||||
removeEarlyAccess(email: String!, type: EarlyAccessType!): Int!
|
||||
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int!
|
||||
resumeSubscription(idempotencyKey: String!, plan: SubscriptionPlan = Pro): UserSubscription!
|
||||
revoke(userId: String!, workspaceId: String!): Boolean!
|
||||
@@ -489,6 +481,9 @@ type Mutation {
|
||||
"""Update a user"""
|
||||
updateUser(id: String!, input: ManageUserInput!): UserType!
|
||||
|
||||
"""update user enabled feature"""
|
||||
updateUserFeatures(features: [FeatureType!]!, id: String!): [FeatureType!]!
|
||||
|
||||
"""Update workspace"""
|
||||
updateWorkspace(input: UpdateWorkspaceInput!): WorkspaceType!
|
||||
|
||||
@@ -532,7 +527,6 @@ type Query {
|
||||
|
||||
"""Get current user"""
|
||||
currentUser: UserType
|
||||
earlyAccessUsers: [UserType!]!
|
||||
error(name: ErrorNames!): ErrorDataUnion!
|
||||
|
||||
"""send workspace invitation"""
|
||||
|
||||
Reference in New Issue
Block a user