Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(web): quota enhancement #6371

Merged
merged 12 commits into from
Jan 15, 2024
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, {
alextran1502 marked this conversation as resolved.
Show resolved Hide resolved
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 (user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
alextran1502 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading