chore(web): quota enhancement (#6371)

* chore(web): quota enhancement

* show quota in user table

* update quota for single user ioption

* Add a note how to set unlimited storage

* fixed deletion doesn't update quota

* refactor relation

* fixed test

* re-refactor

* update sql

* fix e2e test

* Update server/src/domain/user/user.service.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* revert e2e test

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Alex
2024-01-15 09:04:29 -06:00
committed by GitHub
parent 2a8cb70c98
commit d096caccac
10 changed files with 183 additions and 128 deletions
+88 -8
View File
@@ -845,7 +845,16 @@ describe(AssetService.name, () => {
it('should remove faces', async () => {
const assetWithFace = { ...assetStub.image, faces: [faceStub.face1, faceStub.mergeFace1] };
when(assetMock.getById).calledWith(assetWithFace.id).mockResolvedValue(assetWithFace);
when(assetMock.getById)
.calledWith(assetWithFace.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetWithFace);
await sut.handleAssetDeletion({ id: assetWithFace.id });
@@ -870,7 +879,16 @@ describe(AssetService.name, () => {
});
it('should update stack parent if asset has stack children', async () => {
when(assetMock.getById).calledWith(assetStub.primaryImage.id).mockResolvedValue(assetStub.primaryImage);
when(assetMock.getById)
.calledWith(assetStub.primaryImage.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.primaryImage);
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id });
@@ -883,7 +901,16 @@ describe(AssetService.name, () => {
});
it('should not schedule delete-files job for readonly assets', async () => {
when(assetMock.getById).calledWith(assetStub.readOnly.id).mockResolvedValue(assetStub.readOnly);
when(assetMock.getById)
.calledWith(assetStub.readOnly.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.readOnly);
await sut.handleAssetDeletion({ id: assetStub.readOnly.id });
@@ -903,7 +930,16 @@ describe(AssetService.name, () => {
});
it('should process assets from external library with fromExternal flag', async () => {
when(assetMock.getById).calledWith(assetStub.external.id).mockResolvedValue(assetStub.external);
when(assetMock.getById)
.calledWith(assetStub.external.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.external);
await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true });
@@ -926,6 +962,27 @@ describe(AssetService.name, () => {
});
it('should delete a live photo', async () => {
when(assetMock.getById)
.calledWith(assetStub.livePhotoStillAsset.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.livePhotoStillAsset);
when(assetMock.getById)
.calledWith(assetStub.livePhotoMotionAsset.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.livePhotoMotionAsset);
await sut.handleAssetDeletion({ id: assetStub.livePhotoStillAsset.id });
expect(jobMock.queue.mock.calls).toEqual([
@@ -950,7 +1007,16 @@ describe(AssetService.name, () => {
});
it('should update usage', async () => {
when(assetMock.getById).calledWith(assetStub.image.id).mockResolvedValue(assetStub.image);
when(assetMock.getById)
.calledWith(assetStub.image.id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
})
.mockResolvedValue(assetStub.image);
await sut.handleAssetDeletion({ id: assetStub.image.id });
expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000);
@@ -1005,7 +1071,13 @@ describe(AssetService.name, () => {
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
when(assetMock.getById)
.calledWith(assetStub.image.id)
.calledWith(assetStub.image.id, {
faces: {
person: true,
},
library: true,
stack: true,
})
.mockResolvedValue(assetStub.image as AssetEntity);
await sut.updateStackParent(authStub.user1, {
@@ -1032,7 +1104,13 @@ describe(AssetService.name, () => {
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.primaryImage.id]));
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
when(assetMock.getById)
.calledWith(assetStub.primaryImage.id)
.calledWith(assetStub.primaryImage.id, {
faces: {
person: true,
},
library: true,
stack: true,
})
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
await sut.updateStackParent(authStub.user1, {
@@ -1042,7 +1120,9 @@ describe(AssetService.name, () => {
expect(assetMock.updateAll).toBeCalledWith(
[assetStub.primaryImage.id, 'stack-child-asset-1', 'stack-child-asset-2'],
{ stackParentId: 'new' },
{
stackParentId: 'new',
},
);
});
});
+16 -2
View File
@@ -465,7 +465,15 @@ export class AssetService {
async handleAssetDeletion(job: IAssetDeletionJob) {
const { id, fromExternal } = job;
const asset = await this.assetRepository.getById(id);
const asset = await this.assetRepository.getById(id, {
faces: {
person: true,
},
library: true,
stack: true,
exifInfo: true,
});
if (!asset) {
return false;
}
@@ -554,7 +562,13 @@ export class AssetService {
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, newParentId);
const childIds: string[] = [];
const oldParent = await this.assetRepository.getById(oldParentId);
const oldParent = await this.assetRepository.getById(oldParentId, {
faces: {
person: true,
},
library: true,
stack: true,
});
if (oldParent != null) {
childIds.push(oldParent.id);
// Get all children of old parent
@@ -34,5 +34,5 @@ export interface IUserRepository {
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
restore(user: UserEntity): Promise<UserEntity>;
updateUsage(id: string, delta: number): Promise<void>;
syncUsage(): Promise<void>;
syncUsage(id?: string): Promise<void>;
}
+6 -1
View File
@@ -60,7 +60,12 @@ export class UserService {
}
async update(auth: AuthDto, dto: UpdateUserDto): Promise<UserResponseDto> {
await this.findOrFail(dto.id, {});
const user = await this.findOrFail(dto.id, {});
if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
await this.userRepository.syncUsage(dto.id);
}
return this.userCore.updateUser(auth.user, dto.id, dto).then(mapUser);
}