refactor(server): job handlers (#2572)

* refactor(server): job handlers

* chore: remove comment

* chore: add comments for
This commit is contained in:
Jason Rasmussen
2023-05-26 15:43:24 -04:00
committed by GitHub
parent d6756f3d81
commit 1c2d83e2c7
33 changed files with 807 additions and 1082 deletions
@@ -455,21 +455,22 @@ describe(UserService.name, () => {
});
it('should queue user ready for deletion', async () => {
const user = { deletedAt: makeDeletedAt(10) };
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) };
userMock.getDeletedUsers.mockResolvedValue([user] as UserEntity[]);
await sut.handleUserDeleteCheck();
expect(userMock.getDeletedUsers).toHaveBeenCalled();
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.USER_DELETION, data: { user } });
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.USER_DELETION, data: { id: user.id } });
});
});
describe('handleUserDelete', () => {
it('should skip users not ready for deletion', async () => {
const user = { deletedAt: makeDeletedAt(5) } as UserEntity;
const user = { id: 'user-1', deletedAt: makeDeletedAt(5) } as UserEntity;
userMock.get.mockResolvedValue(user);
await sut.handleUserDelete({ user });
await sut.handleUserDelete({ id: user.id });
expect(storageMock.unlinkDir).not.toHaveBeenCalled();
expect(userMock.delete).not.toHaveBeenCalled();
@@ -477,8 +478,9 @@ describe(UserService.name, () => {
it('should delete the user and associated assets', async () => {
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserEntity;
userMock.get.mockResolvedValue(user);
await sut.handleUserDelete({ user });
await sut.handleUserDelete({ id: user.id });
const options = { force: true, recursive: true };
@@ -494,22 +496,13 @@ describe(UserService.name, () => {
it('should delete the library path for a storage label', async () => {
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10), storageLabel: 'admin' } as UserEntity;
userMock.get.mockResolvedValue(user);
await sut.handleUserDelete({ user });
await sut.handleUserDelete({ id: user.id });
const options = { force: true, recursive: true };
expect(storageMock.unlinkDir).toHaveBeenCalledWith('upload/library/admin', options);
});
it('should handle an error', async () => {
const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserEntity;
storageMock.unlinkDir.mockRejectedValue(new Error('Read only filesystem'));
await sut.handleUserDelete({ user });
expect(userMock.delete).not.toHaveBeenCalled();
});
});
});
+29 -26
View File
@@ -6,7 +6,7 @@ import { IAlbumRepository } from '../album/album.repository';
import { IAssetRepository } from '../asset/asset.repository';
import { AuthUserDto } from '../auth';
import { ICryptoRepository } from '../crypto/crypto.repository';
import { IJobRepository, IUserDeletionJob, JobName } from '../job';
import { IEntityJob, IJobRepository, JobName } from '../job';
import { StorageCore, StorageFolder } from '../storage';
import { IStorageRepository } from '../storage/storage.repository';
import { IUserRepository } from '../user/user.repository';
@@ -138,44 +138,47 @@ export class UserService {
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 } });
await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { id: user.id } });
}
}
return true;
}
async handleUserDelete(data: IUserDeletionJob) {
const { user } = data;
async handleUserDelete({ id }: IEntityJob) {
const user = await this.userRepository.get(id, true);
if (!user) {
return false;
}
// 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.warn(`Skipped user that was not ready for deletion: id=${id}`);
return false;
}
this.logger.log(`Deleting user: ${user.id}`);
try {
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),
];
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 });
}
this.logger.warn(`Removing user from database: ${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 });
for (const folder of folders) {
this.logger.warn(`Removing user from filesystem: ${folder}`);
await this.storageRepository.unlinkDir(folder, { recursive: true, force: true });
}
this.logger.warn(`Removing user from database: ${user.id}`);
await this.albumRepository.deleteAll(user.id);
await this.assetRepository.deleteAll(user.id);
await this.userRepository.delete(user, true);
return true;
}
private isReadyForDeletion(user: UserEntity): boolean {