Skip to content

Commit

Permalink
chore(web): quota enhancement (#6371)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
alextran1502 and jrasm91 authored Jan 15, 2024
1 parent 2a8cb70 commit d096cac
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 128 deletions.
96 changes: 88 additions & 8 deletions server/src/domain/asset/asset.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand All @@ -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 });

Expand All @@ -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 });

Expand All @@ -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 });

Expand All @@ -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([
Expand All @@ -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);
Expand Down Expand Up @@ -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, {
Expand All @@ -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, {
Expand All @@ -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',
},
);
});
});
Expand Down
18 changes: 16 additions & 2 deletions server/src/domain/asset/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion server/src/domain/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
7 changes: 6 additions & 1 deletion server/src/domain/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
10 changes: 0 additions & 10 deletions server/src/infra/repositories/asset.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,16 +363,6 @@ export class AssetRepository implements IAssetRepository {

@GenerateSql({ params: [DummyValue.UUID] })
getById(id: string, relations: FindOptionsRelations<AssetEntity>): Promise<AssetEntity | null> {
if (!relations) {
relations = {
faces: {
person: true,
},
library: true,
stack: true,
};
}

return this.repository.findOne({
where: { id },
relations,
Expand Down
14 changes: 10 additions & 4 deletions server/src/infra/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,26 @@ export class UserRepository implements IUserRepository {
await this.userRepository.increment({ id }, 'quotaUsageInBytes', delta);
}

async syncUsage() {
@GenerateSql({ params: [DummyValue.UUID] })
async syncUsage(id?: string) {
const subQuery = this.assetRepository
.createQueryBuilder('assets')
.select('COALESCE(SUM(exif."fileSizeInByte"), 0)')
.leftJoin('assets.exifInfo', 'exif')
.where('assets.ownerId = users.id')
.withDeleted();

await this.userRepository
const query = this.userRepository
.createQueryBuilder('users')
.leftJoin('users.assets', 'assets')
.update()
.set({ quotaUsageInBytes: () => `(${subQuery.getQuery()})` })
.execute();
.set({ quotaUsageInBytes: () => `(${subQuery.getQuery()})` });

if (id) {
query.where('users.id = :id', { id });
}

await query.execute();
}

private async save(user: Partial<UserEntity>) {
Expand Down
Loading

0 comments on commit d096cac

Please sign in to comment.