2023-03-30 15:38:55 -04:00
|
|
|
import { UserEntity } from '@app/infra/entities';
|
2023-02-25 09:12:03 -05:00
|
|
|
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-01-31 13:11:49 -05:00
|
|
|
import { AuthUserDto } from '../auth';
|
2023-10-09 10:25:03 -04:00
|
|
|
import { IEntityJob, JobName } from '../job';
|
|
|
|
|
import {
|
|
|
|
|
IAlbumRepository,
|
|
|
|
|
IAssetRepository,
|
|
|
|
|
ICryptoRepository,
|
|
|
|
|
IJobRepository,
|
|
|
|
|
ILibraryRepository,
|
|
|
|
|
IStorageRepository,
|
|
|
|
|
IUserRepository,
|
|
|
|
|
} from '../repositories';
|
2023-03-25 10:50:57 -04:00
|
|
|
import { StorageCore, StorageFolder } from '../storage';
|
2023-02-25 09:12:03 -05:00
|
|
|
import { CreateUserDto, UpdateUserDto, UserCountDto } from './dto';
|
2022-07-08 21:26:50 -05:00
|
|
|
import {
|
|
|
|
|
CreateProfileImageResponseDto,
|
2023-09-04 15:45:59 -04:00
|
|
|
UserCountResponseDto,
|
|
|
|
|
UserResponseDto,
|
2022-07-08 21:26:50 -05:00
|
|
|
mapCreateProfileImageResponse,
|
2023-02-25 09:12:03 -05:00
|
|
|
mapUser,
|
|
|
|
|
mapUserCountResponse,
|
|
|
|
|
} 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);
|
2023-09-25 17:07:21 +02:00
|
|
|
private storageCore: StorageCore;
|
2022-12-23 21:08:50 +01:00
|
|
|
private userCore: UserCore;
|
2023-03-25 10:50:57 -04:00
|
|
|
|
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-09-20 13:16:33 +02:00
|
|
|
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
|
2023-02-25 09:12:03 -05:00
|
|
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
|
|
|
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
|
|
|
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
|
|
|
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
2023-01-27 20:50:07 +00:00
|
|
|
) {
|
2023-09-25 17:07:21 +02:00
|
|
|
this.storageCore = new StorageCore(storageRepository);
|
2023-09-20 13:16:33 +02:00
|
|
|
this.userCore = new UserCore(userRepository, libraryRepository, cryptoRepository);
|
2022-12-23 21:08:50 +01:00
|
|
|
}
|
2022-05-21 02:23:55 -05:00
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async getAll(authUser: AuthUserDto, isAll: boolean): Promise<UserResponseDto[]> {
|
2023-05-21 23:18:10 -04:00
|
|
|
const users = await this.userCore.getList({ withDeleted: !isAll });
|
|
|
|
|
return users.map(mapUser);
|
2022-04-23 21:08:45 -05:00
|
|
|
}
|
2022-05-21 02:23:55 -05:00
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async get(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
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async getMe(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
|
|
|
}
|
|
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async getCount(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
|
|
|
}
|
|
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async create(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
|
|
|
}
|
|
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async update(authUser: AuthUserDto, dto: UpdateUserDto): Promise<UserResponseDto> {
|
2022-12-26 10:35:52 -05:00
|
|
|
const user = await this.userCore.get(dto.id);
|
2022-06-25 19:53:06 +02:00
|
|
|
if (!user) {
|
|
|
|
|
throw new NotFoundException('User not found');
|
|
|
|
|
}
|
2023-04-01 11:43:45 -05:00
|
|
|
|
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
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async delete(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');
|
|
|
|
|
}
|
2023-09-18 17:56:50 +02:00
|
|
|
await this.albumRepository.softDeleteAll(userId);
|
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
|
|
|
}
|
|
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async restore(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);
|
2023-09-18 17:56:50 +02:00
|
|
|
await this.albumRepository.restoreAll(userId);
|
2022-12-23 21:08:50 +01:00
|
|
|
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
|
|
|
|
2023-08-03 14:17:38 -04:00
|
|
|
async getProfileImage(userId: string): Promise<ReadStream> {
|
2022-12-23 21:08:50 +01:00
|
|
|
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-05-17 13:07:17 -04:00
|
|
|
async handleUserDeleteCheck() {
|
2023-02-25 09:12:03 -05:00
|
|
|
const users = await this.userRepository.getDeletedUsers();
|
|
|
|
|
for (const user of users) {
|
|
|
|
|
if (this.isReadyForDeletion(user)) {
|
2023-05-26 15:43:24 -04:00
|
|
|
await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { id: user.id } });
|
2023-02-25 09:12:03 -05:00
|
|
|
}
|
|
|
|
|
}
|
2023-05-26 15:43:24 -04:00
|
|
|
|
|
|
|
|
return true;
|
2023-02-25 09:12:03 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-26 15:43:24 -04:00
|
|
|
async handleUserDelete({ id }: IEntityJob) {
|
|
|
|
|
const user = await this.userRepository.get(id, true);
|
|
|
|
|
if (!user) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-02-25 09:12:03 -05:00
|
|
|
|
|
|
|
|
// just for extra protection here
|
|
|
|
|
if (!this.isReadyForDeletion(user)) {
|
2023-05-26 15:43:24 -04:00
|
|
|
this.logger.warn(`Skipped user that was not ready for deletion: id=${id}`);
|
|
|
|
|
return false;
|
2023-02-25 09:12:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.log(`Deleting user: ${user.id}`);
|
|
|
|
|
|
2023-05-26 15:43:24 -04:00
|
|
|
const folders = [
|
|
|
|
|
this.storageCore.getLibraryFolder(user),
|
|
|
|
|
this.storageCore.getFolderLocation(StorageFolder.UPLOAD, user.id),
|
|
|
|
|
this.storageCore.getFolderLocation(StorageFolder.PROFILE, user.id),
|
|
|
|
|
this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, user.id),
|
|
|
|
|
this.storageCore.getFolderLocation(StorageFolder.ENCODED_VIDEO, user.id),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const folder of folders) {
|
|
|
|
|
this.logger.warn(`Removing user from filesystem: ${folder}`);
|
|
|
|
|
await this.storageRepository.unlinkDir(folder, { recursive: true, force: true });
|
|
|
|
|
}
|
2023-02-25 09:12:03 -05:00
|
|
|
|
2023-05-26 15:43:24 -04:00
|
|
|
this.logger.warn(`Removing user from database: ${user.id}`);
|
2023-02-25 09:12:03 -05:00
|
|
|
|
2023-05-26 15:43:24 -04:00
|
|
|
await this.albumRepository.deleteAll(user.id);
|
|
|
|
|
await this.assetRepository.deleteAll(user.id);
|
|
|
|
|
await this.userRepository.delete(user, true);
|
|
|
|
|
|
|
|
|
|
return true;
|
2023-02-25 09:12:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|