feat(server,web): make user deletion delay configurable (#7663)

* feat(server,web): make user deletion delay configurable

* alphabetical order

* add min for user.deleteDelay in SettingInputField

* make config.user.deleteDelay SettingInputField min consistent format

* fix e2e test

* update description on user delete delay
This commit is contained in:
Sam Holton
2024-03-06 00:45:40 -05:00
committed by GitHub
parent 52dfe5fc92
commit 9125999d1a
33 changed files with 366 additions and 16 deletions

View File

@@ -13,16 +13,19 @@ import {
IJobRepository,
ILibraryRepository,
IStorageRepository,
ISystemConfigRepository,
IUserRepository,
UserFindOptions,
} from '../repositories';
import { StorageCore, StorageFolder } from '../storage';
import { SystemConfigCore } from '../system-config/system-config.core';
import { CreateUserDto, UpdateUserDto } from './dto';
import { CreateProfileImageResponseDto, UserResponseDto, mapCreateProfileImageResponse, mapUser } from './response-dto';
import { UserCore } from './user.core';
@Injectable()
export class UserService {
private configCore: SystemConfigCore;
private logger = new ImmichLogger(UserService.name);
private userCore: UserCore;
@@ -33,9 +36,11 @@ export class UserService {
@Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IUserRepository) private userRepository: IUserRepository,
) {
this.userCore = UserCore.create(cryptoRepository, libraryRepository, userRepository);
this.configCore = SystemConfigCore.create(configRepository);
}
async getAll(auth: AuthDto, isAll: boolean): Promise<UserResponseDto[]> {
@@ -140,22 +145,26 @@ export class UserService {
async handleUserDeleteCheck() {
const users = await this.userRepository.getDeletedUsers();
const config = await this.configCore.getConfig();
await this.jobRepository.queueAll(
users.flatMap((user) =>
this.isReadyForDeletion(user) ? [{ name: JobName.USER_DELETION, data: { id: user.id } }] : [],
this.isReadyForDeletion(user, config.user.deleteDelay)
? [{ name: JobName.USER_DELETION, data: { id: user.id } }]
: [],
),
);
return true;
}
async handleUserDelete({ id }: IEntityJob) {
const config = await this.configCore.getConfig();
const user = await this.userRepository.get(id, { withDeleted: true });
if (!user) {
return false;
}
// just for extra protection here
if (!this.isReadyForDeletion(user)) {
if (!this.isReadyForDeletion(user, config.user.deleteDelay)) {
this.logger.warn(`Skipped user that was not ready for deletion: id=${id}`);
return false;
}
@@ -184,12 +193,12 @@ export class UserService {
return true;
}
private isReadyForDeletion(user: UserEntity): boolean {
private isReadyForDeletion(user: UserEntity, deleteDelay: number): boolean {
if (!user.deletedAt) {
return false;
}
return DateTime.now().minus({ days: 7 }) > DateTime.fromJSDate(user.deletedAt);
return DateTime.now().minus({ days: deleteDelay }) > DateTime.fromJSDate(user.deletedAt);
}
private async findOrFail(id: string, options: UserFindOptions) {