239 lines
6.3 KiB
TypeScript
239 lines
6.3 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
NotFoundException,
|
|
Param,
|
|
Patch,
|
|
Post,
|
|
Put,
|
|
Query,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import { ApiBearerAuth, ApiOAuth2, ApiTags } from '@nestjs/swagger';
|
|
import { Privileges } from 'src/decorators/privileges.decorator';
|
|
import { Scopes } from 'src/decorators/scopes.decorator';
|
|
import { OAuth2Guard } from 'src/guards/oauth2.guard';
|
|
import { PrivilegesGuard } from 'src/guards/privileges.guard';
|
|
import { ScopesGuard } from 'src/guards/scopes.guard';
|
|
import { PrivilegeService } from 'src/modules/objects/privilege/privilege.service';
|
|
import { User } from 'src/modules/objects/user/user.entity';
|
|
import { UserService } from 'src/modules/objects/user/user.service';
|
|
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
|
|
import { PaginationService } from 'src/modules/utility/services/paginate.service';
|
|
import { PageOptions } from 'src/types/pagination.interfaces';
|
|
|
|
const RELATIONS = ['picture', 'privileges'];
|
|
|
|
@ApiBearerAuth()
|
|
@ApiTags('admin')
|
|
@ApiOAuth2(['management'])
|
|
@Controller('/api/admin/users')
|
|
@UseGuards(OAuth2Guard, PrivilegesGuard, ScopesGuard)
|
|
export class UserAdminController {
|
|
constructor(
|
|
private _user: UserService,
|
|
private _privilege: PrivilegeService,
|
|
private _paginate: PaginationService,
|
|
private _form: FormUtilityService,
|
|
) {}
|
|
|
|
/**
|
|
* Get a list of all users or search for a specific user
|
|
* @param options Search and pagination options
|
|
* @returns Paginated user list
|
|
*/
|
|
@Get('')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async userList(@Query() options: { q?: string } & PageOptions) {
|
|
const search = options.q ? decodeURIComponent(options.q) : null;
|
|
const resultCount = await this._user.searchUsersCount(search, RELATIONS);
|
|
|
|
const pagination = this._paginate.paginate(options, resultCount);
|
|
|
|
const [list] = await this._user.searchUsers(
|
|
pagination.pageSize,
|
|
pagination.offset,
|
|
search,
|
|
RELATIONS,
|
|
);
|
|
|
|
return {
|
|
pagination,
|
|
list: this._form.stripObjectArray(list, ['password']),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get a single user by ID
|
|
* @param id User ID
|
|
* @returns User
|
|
*/
|
|
@Get(':id')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async user(@Param('id') id: string) {
|
|
const user = await this._user.getById(parseInt(id, 10), RELATIONS);
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
return this._form.stripObject(user, ['password']);
|
|
}
|
|
|
|
@Patch(':id')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async updateUser(@Param('id') id: string, @Body() body: Partial<User>) {
|
|
const user = await this._user.getById(parseInt(id, 10));
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
|
|
const allowedFieldsOnly = this._form.pluckObject(body, [
|
|
'display_name',
|
|
'username',
|
|
'email',
|
|
'activated',
|
|
]);
|
|
|
|
if (!Object.keys(allowedFieldsOnly).length) {
|
|
throw new BadRequestException('No data was updated.');
|
|
}
|
|
|
|
Object.assign(user, allowedFieldsOnly);
|
|
await this._user.updateUser(user);
|
|
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Delete a user avatar from the server
|
|
* @param id User ID
|
|
* @returns Success
|
|
*/
|
|
@Delete(':id/avatar')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async deleteUserAvatar(@Param('id') id: string) {
|
|
const user = await this._user.getById(parseInt(id, 10), ['picture']);
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
|
|
if (user.picture) {
|
|
await this._user.deleteAvatar(user);
|
|
user.picture = null;
|
|
await this._user.updateUser(user);
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Unpaginated list of all privileges for user
|
|
* @param id User ID
|
|
* @returns Privilege list
|
|
*/
|
|
@Get(':id/privileges')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async userPrivileges(@Param('id') id: string) {
|
|
const user = await this._user.getById(parseInt(id, 10), ['privileges']);
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
return user.privileges;
|
|
}
|
|
|
|
/**
|
|
* Replace user's privileges with the new list
|
|
* @param id User ID
|
|
* @param body With `privileges`, list of privilege IDs the user should have
|
|
* @returns New privileges array
|
|
*/
|
|
@Put(':id/privileges')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async setUserPrivileges(
|
|
@Param('id') id: string,
|
|
@Body() body: { privileges: number[] },
|
|
) {
|
|
const user = await this._user.getById(parseInt(id, 10), ['privileges']);
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
|
|
if (!body.privileges) {
|
|
throw new BadRequestException('Privileges are required.');
|
|
}
|
|
|
|
if (body.privileges.length) {
|
|
const privileges = await this._privilege.getByIDs(body.privileges);
|
|
if (!privileges?.length) {
|
|
throw new BadRequestException('Privileges not found.');
|
|
}
|
|
|
|
user.privileges = privileges;
|
|
} else {
|
|
user.privileges.length = 0;
|
|
}
|
|
|
|
await this._user.updateUser(user);
|
|
|
|
return user.privileges;
|
|
}
|
|
|
|
/**
|
|
* Resend activation email to a user
|
|
* @param id User ID
|
|
* @returns Success or error
|
|
*/
|
|
@Post(':id/activation')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async activateUserEmail(@Param('id') id: string) {
|
|
const user = await this._user.getById(parseInt(id, 10));
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
|
|
let error: Error;
|
|
|
|
try {
|
|
await this._user.sendActivationEmail(user);
|
|
} catch (e: any) {
|
|
error = e as Error;
|
|
}
|
|
|
|
return { success: !error, error: error?.name, message: error?.message };
|
|
}
|
|
|
|
/**
|
|
* Send password reset email to a user
|
|
* @param id User ID
|
|
* @returns Success or error
|
|
*/
|
|
@Post(':id/password')
|
|
@Scopes('management')
|
|
@Privileges('admin', 'admin:user')
|
|
async resetPasswordEmail(@Param('id') id: string) {
|
|
const user = await this._user.getById(parseInt(id, 10));
|
|
if (!user) {
|
|
throw new NotFoundException('User not found');
|
|
}
|
|
|
|
let error: Error;
|
|
|
|
try {
|
|
await this._user.sendPasswordEmail(user);
|
|
} catch (e: any) {
|
|
error = e as Error;
|
|
}
|
|
|
|
return { success: !error, error: error?.name, message: error?.message };
|
|
}
|
|
}
|