mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(admin): add self-host setup and user management page (#7537)
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
Throttle,
|
||||
URLHelper,
|
||||
} from '../../fundamentals';
|
||||
import { Admin } from '../common';
|
||||
import { UserService } from '../user';
|
||||
import { UserType } from '../user/types';
|
||||
import { validators } from '../utils/validators';
|
||||
@@ -291,4 +292,19 @@ export class AuthResolver {
|
||||
|
||||
return emailVerifiedAt !== null;
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => String, {
|
||||
description: 'Create change password url',
|
||||
})
|
||||
async createChangePasswordUrl(
|
||||
@Args('userId') userId: string,
|
||||
@Args('callbackUrl') callbackUrl: string
|
||||
): Promise<string> {
|
||||
const token = await this.token.createToken(
|
||||
TokenType.ChangePassword,
|
||||
userId
|
||||
);
|
||||
return this.url.link(callbackUrl, { token });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ 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,
|
||||
|
||||
@@ -57,12 +57,15 @@ export class FeatureManagementResolver {
|
||||
|
||||
@Admin()
|
||||
@Mutation(() => Int)
|
||||
async removeEarlyAccess(@Args('email') email: string): Promise<number> {
|
||||
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);
|
||||
return this.feature.removeEarlyAccess(user.id, type);
|
||||
}
|
||||
|
||||
@Admin()
|
||||
@@ -90,4 +93,18 @@ export class FeatureManagementResolver {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { validators } from '../utils/validators';
|
||||
import { UserService } from './service';
|
||||
import {
|
||||
DeleteAccount,
|
||||
ManageUserInput,
|
||||
RemoveAvatar,
|
||||
UpdateUserInput,
|
||||
UserOrLimitedUser,
|
||||
@@ -174,6 +175,13 @@ export class UserManagementResolver {
|
||||
private readonly user: UserService
|
||||
) {}
|
||||
|
||||
@Query(() => Int, {
|
||||
description: 'Get users count',
|
||||
})
|
||||
async usersCount(): Promise<number> {
|
||||
return this.db.user.count();
|
||||
}
|
||||
|
||||
@Query(() => [UserType], {
|
||||
description: 'List registered users',
|
||||
})
|
||||
@@ -208,6 +216,26 @@ export class UserManagementResolver {
|
||||
return sessionUser(user);
|
||||
}
|
||||
|
||||
@Query(() => UserType, {
|
||||
name: 'userByEmail',
|
||||
description: 'Get user by email for admin',
|
||||
nullable: true,
|
||||
})
|
||||
async getUserByEmail(@Args('email') email: string) {
|
||||
const user = await this.db.user.findUnique({
|
||||
select: { ...this.user.defaultUserSelect, password: true },
|
||||
where: {
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sessionUser(user);
|
||||
}
|
||||
|
||||
@Mutation(() => UserType, {
|
||||
description: 'Create a new user',
|
||||
})
|
||||
@@ -231,4 +259,36 @@ export class UserManagementResolver {
|
||||
await this.user.deleteUser(id);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Mutation(() => UserType, {
|
||||
description: 'Update a user',
|
||||
})
|
||||
async updateUser(
|
||||
@Args('id') id: string,
|
||||
@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');
|
||||
}
|
||||
}
|
||||
return sessionUser(
|
||||
await this.user.updateUser(user.id, {
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,15 @@ export class UpdateUserInput implements Partial<User> {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class ManageUserInput {
|
||||
@Field({ description: 'User name', nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Field({ description: 'User email' })
|
||||
email!: string;
|
||||
}
|
||||
|
||||
declare module '../../fundamentals/event/def' {
|
||||
interface UserEvents {
|
||||
admin: {
|
||||
|
||||
Reference in New Issue
Block a user