2023-02-25 09:12:03 -05:00
|
|
|
import { UserEntity } from '@app/infra/db/entities';
|
|
|
|
|
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
2023-01-16 13:09:04 -05:00
|
|
|
import { randomBytes } from 'crypto';
|
2022-12-23 21:08:50 +01:00
|
|
|
import { ReadStream } from 'fs';
|
2023-02-25 09:12:03 -05:00
|
|
|
import { join } from 'path';
|
|
|
|
|
import { IAlbumRepository } from '../album/album.repository';
|
|
|
|
|
import { IKeyRepository } from '../api-key/api-key.repository';
|
|
|
|
|
import { IAssetRepository } from '../asset/asset.repository';
|
2023-01-31 13:11:49 -05:00
|
|
|
import { AuthUserDto } from '../auth';
|
2023-02-25 09:12:03 -05:00
|
|
|
import { ICryptoRepository } from '../crypto/crypto.repository';
|
2023-03-21 22:49:19 -04:00
|
|
|
import { APP_UPLOAD_LOCATION } from '../domain.constant';
|
2023-02-25 09:12:03 -05:00
|
|
|
import { IJobRepository, IUserDeletionJob, JobName } from '../job';
|
|
|
|
|
import { IStorageRepository } from '../storage/storage.repository';
|
|
|
|
|
import { IUserTokenRepository } from '../user-token/user-token.repository';
|
|
|
|
|
import { IUserRepository } from '../user/user.repository';
|
|
|
|
|
import { CreateUserDto, UpdateUserDto, UserCountDto } from './dto';
|
2022-07-08 21:26:50 -05:00
|
|
|
import {
|
|
|
|
|
CreateProfileImageResponseDto,
|
|
|
|
|
mapCreateProfileImageResponse,
|
2023-02-25 09:12:03 -05:00
|
|
|
mapUser,
|
|
|
|
|
mapUserCountResponse,
|
|
|
|
|
UserCountResponseDto,
|
|
|
|
|
UserResponseDto,
|
|
|
|
|
} from './response-dto';
|
2022-12-23 21:08:50 +01:00
|
|
|
import { UserCore } from './user.core';
|
2022-02-03 10:06:44 -06:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class UserService {
|
2023-02-25 09:12:03 -05:00
|
|
|
private logger = new Logger(UserService.name);
|
2022-12-23 21:08:50 +01:00
|
|
|
private userCore: UserCore;
|
2023-01-27 20:50:07 +00:00
|
|
|
constructor(
|
2023-02-25 09:12:03 -05:00
|
|
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
2023-01-27 20:50:07 +00:00
|
|
|
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
2023-02-25 09:12:03 -05:00
|
|
|
|
|
|
|
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
|
|
|
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
|
|
|
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
|
|
|
|
@Inject(IKeyRepository) private keyRepository: IKeyRepository,
|
|
|
|
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
|
|
|
|
@Inject(IUserTokenRepository) private tokenRepository: IUserTokenRepository,
|
2023-01-27 20:50:07 +00:00
|
|
|
) {
|
|
|
|
|
this.userCore = new UserCore(userRepository, cryptoRepository);
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
2022-05-21 02:23:55 -05:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async getAllUsers(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
2022-05-21 02:23:55 -05:00
|
|
|
if (isAll) {
|
2022-12-23 21:08:50 +01:00
|
|
|
const allUsers = await this.userCore.getList();
|
2022-07-08 21:26:50 -05:00
|
|
|
return allUsers.map(mapUser);
|
2022-05-21 02:23:55 -05:00
|
|
|
}
|
2022-04-23 21:08:45 -05:00
|
|
|
|
2022-12-23 21:08:50 +01:00
|
|
|
const allUserExceptRequestedUser = await this.userCore.getList({ excludeId: authUser.id });
|
2022-07-08 21:26:50 -05:00
|
|
|
return allUserExceptRequestedUser.map(mapUser);
|
2022-04-23 21:08:45 -05:00
|
|
|
}
|
2022-05-21 02:23:55 -05:00
|
|
|
|
2022-11-07 16:53:47 -05:00
|
|
|
async getUserById(userId: string, withDeleted = false): Promise<UserResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const user = await this.userCore.get(userId, withDeleted);
|
2022-07-16 23:52:00 -05:00
|
|
|
if (!user) {
|
|
|
|
|
throw new NotFoundException('User not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mapUser(user);
|
|
|
|
|
}
|
2022-07-17 15:09:26 -05:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async getUserInfo(authUser: AuthUserDto): Promise<UserResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const user = await this.userCore.get(authUser.id);
|
2022-07-08 21:26:50 -05:00
|
|
|
if (!user) {
|
|
|
|
|
throw new BadRequestException('User not found');
|
|
|
|
|
}
|
|
|
|
|
return mapUser(user);
|
2022-06-27 15:13:07 -05:00
|
|
|
}
|
|
|
|
|
|
2022-12-09 15:53:11 -05:00
|
|
|
async getUserCount(dto: UserCountDto): Promise<UserCountResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
let users = await this.userCore.getList();
|
2022-12-09 15:53:11 -05:00
|
|
|
|
|
|
|
|
if (dto.admin) {
|
|
|
|
|
users = users.filter((user) => user.isAdmin);
|
|
|
|
|
}
|
2022-05-21 02:23:55 -05:00
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
return mapUserCountResponse(users.length);
|
2022-05-21 02:23:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-06-25 19:53:06 +02:00
|
|
|
async createUser(createUserDto: CreateUserDto): Promise<UserResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const createdUser = await this.userCore.createUser(createUserDto);
|
|
|
|
|
return mapUser(createdUser);
|
2022-05-21 02:23:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-12-26 10:35:52 -05:00
|
|
|
async updateUser(authUser: AuthUserDto, dto: UpdateUserDto): Promise<UserResponseDto> {
|
|
|
|
|
const user = await this.userCore.get(dto.id);
|
2022-06-25 19:53:06 +02:00
|
|
|
if (!user) {
|
|
|
|
|
throw new NotFoundException('User not found');
|
|
|
|
|
}
|
2022-12-26 10:35:52 -05:00
|
|
|
const updatedUser = await this.userCore.updateUser(authUser, dto.id, dto);
|
2022-12-23 21:08:50 +01:00
|
|
|
return mapUser(updatedUser);
|
2022-05-21 02:23:55 -05:00
|
|
|
}
|
2022-05-27 22:15:35 -05:00
|
|
|
|
2022-11-07 16:53:47 -05:00
|
|
|
async deleteUser(authUser: AuthUserDto, userId: string): Promise<UserResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const user = await this.userCore.get(userId);
|
2022-11-07 16:53:47 -05:00
|
|
|
if (!user) {
|
|
|
|
|
throw new BadRequestException('User not found');
|
|
|
|
|
}
|
2022-12-23 21:08:50 +01:00
|
|
|
const deletedUser = await this.userCore.deleteUser(authUser, user);
|
|
|
|
|
return mapUser(deletedUser);
|
2022-11-07 16:53:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async restoreUser(authUser: AuthUserDto, userId: string): Promise<UserResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const user = await this.userCore.get(userId, true);
|
2022-11-07 16:53:47 -05:00
|
|
|
if (!user) {
|
|
|
|
|
throw new BadRequestException('User not found');
|
|
|
|
|
}
|
2022-12-23 21:08:50 +01:00
|
|
|
const updatedUser = await this.userCore.restoreUser(authUser, user);
|
|
|
|
|
return mapUser(updatedUser);
|
2022-11-07 16:53:47 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-08 21:26:50 -05:00
|
|
|
async createProfileImage(
|
|
|
|
|
authUser: AuthUserDto,
|
|
|
|
|
fileInfo: Express.Multer.File,
|
|
|
|
|
): Promise<CreateProfileImageResponseDto> {
|
2022-12-23 21:08:50 +01:00
|
|
|
const updatedUser = await this.userCore.createProfileImage(authUser, fileInfo.path);
|
|
|
|
|
return mapCreateProfileImageResponse(updatedUser.id, updatedUser.profileImagePath);
|
2022-05-27 22:24:58 -05:00
|
|
|
}
|
2022-05-27 22:15:35 -05:00
|
|
|
|
2022-12-23 21:08:50 +01:00
|
|
|
async getUserProfileImage(userId: string): Promise<ReadStream> {
|
|
|
|
|
const user = await this.userCore.get(userId);
|
|
|
|
|
if (!user) {
|
|
|
|
|
throw new NotFoundException('User not found');
|
2022-05-28 22:35:45 -05:00
|
|
|
}
|
2022-12-23 21:08:50 +01:00
|
|
|
return this.userCore.getUserProfileImage(user);
|
2022-05-27 22:15:35 -05:00
|
|
|
}
|
2023-01-16 13:09:04 -05:00
|
|
|
|
|
|
|
|
async resetAdminPassword(ask: (admin: UserResponseDto) => Promise<string | undefined>) {
|
|
|
|
|
const admin = await this.userCore.getAdmin();
|
|
|
|
|
if (!admin) {
|
|
|
|
|
throw new BadRequestException('Admin account does not exist');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const providedPassword = await ask(admin);
|
|
|
|
|
const password = providedPassword || randomBytes(24).toString('base64').replace(/\W/g, '');
|
|
|
|
|
|
|
|
|
|
await this.userCore.updateUser(admin, admin.id, { password });
|
|
|
|
|
|
|
|
|
|
return { admin, password, provided: !!providedPassword };
|
|
|
|
|
}
|
2023-02-25 09:12:03 -05:00
|
|
|
|
2023-03-24 08:19:48 -04:00
|
|
|
async handleQueueUserDelete() {
|
2023-02-25 09:12:03 -05:00
|
|
|
const users = await this.userRepository.getDeletedUsers();
|
|
|
|
|
for (const user of users) {
|
|
|
|
|
if (this.isReadyForDeletion(user)) {
|
|
|
|
|
await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { user } });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleUserDelete(data: IUserDeletionJob) {
|
|
|
|
|
const { user } = data;
|
|
|
|
|
|
|
|
|
|
// just for extra protection here
|
|
|
|
|
if (!this.isReadyForDeletion(user)) {
|
|
|
|
|
this.logger.warn(`Skipped user that was not ready for deletion: id=${user.id}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.log(`Deleting user: ${user.id}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const userAssetDir = join(APP_UPLOAD_LOCATION, user.id);
|
|
|
|
|
this.logger.warn(`Removing user from filesystem: ${userAssetDir}`);
|
|
|
|
|
await this.storageRepository.unlinkDir(userAssetDir, { recursive: true, force: true });
|
|
|
|
|
|
|
|
|
|
this.logger.warn(`Removing user from database: ${user.id}`);
|
|
|
|
|
|
|
|
|
|
await this.tokenRepository.deleteAll(user.id);
|
|
|
|
|
await this.keyRepository.deleteAll(user.id);
|
|
|
|
|
await this.albumRepository.deleteAll(user.id);
|
|
|
|
|
await this.assetRepository.deleteAll(user.id);
|
|
|
|
|
await this.userRepository.delete(user, true);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
this.logger.error(`Failed to remove user`, error, { id: user.id });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isReadyForDeletion(user: UserEntity): boolean {
|
|
|
|
|
if (!user.deletedAt) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const msInDay = 86400000;
|
|
|
|
|
const msDeleteWait = msInDay * 7;
|
|
|
|
|
const msSinceDelete = new Date().getTime() - (Date.parse(user.deletedAt.toString()) || 0);
|
|
|
|
|
|
|
|
|
|
return msSinceDelete >= msDeleteWait;
|
|
|
|
|
}
|
2022-02-03 10:06:44 -06:00
|
|
|
}
|