2024-10-02 10:54:35 -04:00
|
|
|
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
|
2024-05-26 18:15:52 -04:00
|
|
|
import { SALT_ROUNDS } from 'src/constants';
|
2025-05-12 16:50:26 -04:00
|
|
|
import { AssetStatsDto, AssetStatsResponseDto, mapStats } from 'src/dtos/asset.dto';
|
2024-05-26 18:15:52 -04:00
|
|
|
import { AuthDto } from 'src/dtos/auth.dto';
|
2024-05-27 22:16:53 -04:00
|
|
|
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
2024-05-26 18:15:52 -04:00
|
|
|
import {
|
|
|
|
|
UserAdminCreateDto,
|
|
|
|
|
UserAdminDeleteDto,
|
|
|
|
|
UserAdminResponseDto,
|
|
|
|
|
UserAdminSearchDto,
|
|
|
|
|
UserAdminUpdateDto,
|
|
|
|
|
mapUserAdmin,
|
|
|
|
|
} from 'src/dtos/user.dto';
|
2025-02-11 17:15:56 -05:00
|
|
|
import { JobName, UserMetadataKey, UserStatus } from 'src/enum';
|
2025-02-11 14:08:13 -05:00
|
|
|
import { UserFindOptions } from 'src/repositories/user.repository';
|
2024-10-02 10:54:35 -04:00
|
|
|
import { BaseService } from 'src/services/base.service';
|
2024-05-27 22:16:53 -04:00
|
|
|
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
|
2024-05-26 18:15:52 -04:00
|
|
|
|
|
|
|
|
@Injectable()
|
2024-10-02 10:54:35 -04:00
|
|
|
export class UserAdminService extends BaseService {
|
2024-05-26 18:15:52 -04:00
|
|
|
async search(auth: AuthDto, dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
2025-05-12 16:50:26 -04:00
|
|
|
const users = await this.userRepository.getList({
|
|
|
|
|
id: dto.id,
|
|
|
|
|
withDeleted: dto.withDeleted,
|
|
|
|
|
});
|
2024-05-26 18:15:52 -04:00
|
|
|
return users.map((user) => mapUserAdmin(user));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async create(dto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
2024-10-24 17:24:37 -04:00
|
|
|
const { notify, ...userDto } = dto;
|
2024-10-17 21:54:50 +05:30
|
|
|
const config = await this.getConfig({ withCache: false });
|
2024-10-24 17:24:37 -04:00
|
|
|
if (!config.oauth.enabled && !userDto.password) {
|
2024-10-17 21:54:50 +05:30
|
|
|
throw new BadRequestException('password is required');
|
|
|
|
|
}
|
2024-10-24 17:24:37 -04:00
|
|
|
|
|
|
|
|
const user = await this.createUser(userDto);
|
2024-05-26 18:15:52 -04:00
|
|
|
|
2025-07-15 13:41:19 -04:00
|
|
|
await this.eventRepository.emit('UserSignup', {
|
2024-07-03 22:06:20 -04:00
|
|
|
notify: !!notify,
|
|
|
|
|
id: user.id,
|
2025-09-10 09:11:42 -04:00
|
|
|
password: userDto.password,
|
2024-07-03 22:06:20 -04:00
|
|
|
});
|
|
|
|
|
|
2024-05-26 18:15:52 -04:00
|
|
|
return mapUserAdmin(user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async get(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
|
|
|
|
|
const user = await this.findOrFail(id, { withDeleted: true });
|
|
|
|
|
return mapUserAdmin(user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise<UserAdminResponseDto> {
|
|
|
|
|
const user = await this.findOrFail(id, {});
|
|
|
|
|
|
2025-06-11 21:11:13 -05:00
|
|
|
if (dto.isAdmin !== undefined && dto.isAdmin !== auth.user.isAdmin && auth.user.id === id) {
|
|
|
|
|
throw new BadRequestException('Admin status can only be changed by another admin');
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 18:15:52 -04:00
|
|
|
if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
|
|
|
|
|
await this.userRepository.syncUsage(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dto.email) {
|
|
|
|
|
const duplicate = await this.userRepository.getByEmail(dto.email);
|
|
|
|
|
if (duplicate && duplicate.id !== id) {
|
|
|
|
|
throw new BadRequestException('Email already in use by another account');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dto.storageLabel) {
|
|
|
|
|
const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);
|
|
|
|
|
if (duplicate && duplicate.id !== id) {
|
|
|
|
|
throw new BadRequestException('Storage label already in use by another account');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dto.password) {
|
|
|
|
|
dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-09 16:00:58 -05:00
|
|
|
if (dto.pinCode) {
|
|
|
|
|
dto.pinCode = await this.cryptoRepository.hashBcrypt(dto.pinCode, SALT_ROUNDS);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-26 18:15:52 -04:00
|
|
|
if (dto.storageLabel === '') {
|
|
|
|
|
dto.storageLabel = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date() });
|
|
|
|
|
|
|
|
|
|
return mapUserAdmin(updatedUser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async delete(auth: AuthDto, id: string, dto: UserAdminDeleteDto): Promise<UserAdminResponseDto> {
|
|
|
|
|
const { force } = dto;
|
2025-06-11 21:11:13 -05:00
|
|
|
await this.findOrFail(id, {});
|
|
|
|
|
if (auth.user.id === id) {
|
|
|
|
|
throw new ForbiddenException('Cannot delete your own account');
|
2024-05-26 18:15:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.albumRepository.softDeleteAll(id);
|
|
|
|
|
|
2025-07-15 14:50:13 -04:00
|
|
|
const status = force ? UserStatus.Removing : UserStatus.Deleted;
|
2024-05-26 18:15:52 -04:00
|
|
|
const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
|
|
|
|
|
|
|
|
|
|
if (force) {
|
2025-07-15 18:39:00 -04:00
|
|
|
await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } });
|
2024-05-26 18:15:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mapUserAdmin(user);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
|
|
|
|
|
await this.findOrFail(id, { withDeleted: true });
|
|
|
|
|
await this.albumRepository.restoreAll(id);
|
2025-01-29 11:49:08 -05:00
|
|
|
const user = await this.userRepository.restore(id);
|
2024-05-26 18:15:52 -04:00
|
|
|
return mapUserAdmin(user);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-12 16:50:26 -04:00
|
|
|
async getStatistics(auth: AuthDto, id: string, dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
|
2025-05-13 19:55:58 +08:00
|
|
|
const stats = await this.assetRepository.getStatistics(id, dto);
|
2025-05-12 16:50:26 -04:00
|
|
|
return mapStats(stats);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-27 22:16:53 -04:00
|
|
|
async getPreferences(auth: AuthDto, id: string): Promise<UserPreferencesResponseDto> {
|
2025-04-28 09:54:51 -04:00
|
|
|
await this.findOrFail(id, { withDeleted: true });
|
2025-02-12 15:23:08 -05:00
|
|
|
const metadata = await this.userRepository.getMetadata(id);
|
2025-04-28 09:54:51 -04:00
|
|
|
return mapPreferences(getPreferences(metadata));
|
2024-05-27 22:16:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updatePreferences(auth: AuthDto, id: string, dto: UserPreferencesUpdateDto) {
|
2025-04-28 09:54:51 -04:00
|
|
|
await this.findOrFail(id, { withDeleted: false });
|
2025-02-12 15:23:08 -05:00
|
|
|
const metadata = await this.userRepository.getMetadata(id);
|
2025-04-28 09:54:51 -04:00
|
|
|
const newPreferences = mergePreferences(getPreferences(metadata), dto);
|
2024-05-27 22:16:53 -04:00
|
|
|
|
2025-02-12 15:23:08 -05:00
|
|
|
await this.userRepository.upsertMetadata(id, {
|
2025-07-15 14:50:13 -04:00
|
|
|
key: UserMetadataKey.Preferences,
|
2025-04-28 09:54:51 -04:00
|
|
|
value: getPreferencesPartial(newPreferences),
|
2024-05-27 22:16:53 -04:00
|
|
|
});
|
|
|
|
|
|
2025-02-12 15:23:08 -05:00
|
|
|
return mapPreferences(newPreferences);
|
2024-05-27 22:16:53 -04:00
|
|
|
}
|
|
|
|
|
|
2024-05-26 18:15:52 -04:00
|
|
|
private async findOrFail(id: string, options: UserFindOptions) {
|
|
|
|
|
const user = await this.userRepository.get(id, options);
|
|
|
|
|
if (!user) {
|
|
|
|
|
throw new BadRequestException('User not found');
|
|
|
|
|
}
|
|
|
|
|
return user;
|
|
|
|
|
}
|
|
|
|
|
}
|