feat(server, web): quotas (#4471)
* feat: quotas * chore: open api * chore: update status box and upload error message --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
newPartnerRepositoryMock,
|
||||
newStorageRepositoryMock,
|
||||
newSystemConfigRepositoryMock,
|
||||
newUserRepositoryMock,
|
||||
} from '@test';
|
||||
import { when } from 'jest-when';
|
||||
import { Readable } from 'stream';
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
IPartnerRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
IUserRepository,
|
||||
JobItem,
|
||||
TimeBucketSize,
|
||||
} from '../repositories';
|
||||
@@ -67,6 +69,7 @@ const uploadFile = {
|
||||
checksum: Buffer.from('checksum', 'utf8'),
|
||||
originalPath: 'upload/admin/image.jpeg',
|
||||
originalName: 'image.jpeg',
|
||||
size: 1000,
|
||||
},
|
||||
},
|
||||
filename: (fieldName: UploadFieldName, filename: string) => {
|
||||
@@ -79,6 +82,7 @@ const uploadFile = {
|
||||
checksum: Buffer.from('checksum', 'utf8'),
|
||||
originalPath: `upload/admin/${filename}`,
|
||||
originalName: filename,
|
||||
size: 1000,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -167,6 +171,7 @@ describe(AssetService.name, () => {
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
let jobMock: jest.Mocked<IJobRepository>;
|
||||
let storageMock: jest.Mocked<IStorageRepository>;
|
||||
let userMock: jest.Mocked<IUserRepository>;
|
||||
let communicationMock: jest.Mocked<ICommunicationRepository>;
|
||||
let configMock: jest.Mocked<ISystemConfigRepository>;
|
||||
let partnerMock: jest.Mocked<IPartnerRepository>;
|
||||
@@ -182,6 +187,7 @@ describe(AssetService.name, () => {
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
jobMock = newJobRepositoryMock();
|
||||
storageMock = newStorageRepositoryMock();
|
||||
userMock = newUserRepositoryMock();
|
||||
configMock = newSystemConfigRepositoryMock();
|
||||
partnerMock = newPartnerRepositoryMock();
|
||||
|
||||
@@ -192,6 +198,7 @@ describe(AssetService.name, () => {
|
||||
jobMock,
|
||||
configMock,
|
||||
storageMock,
|
||||
userMock,
|
||||
communicationMock,
|
||||
partnerMock,
|
||||
);
|
||||
@@ -836,7 +843,7 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
|
||||
it('should remove faces', async () => {
|
||||
const assetWithFace = { ...(assetStub.image as AssetEntity), faces: [faceStub.face1, faceStub.mergeFace1] };
|
||||
const assetWithFace = { ...assetStub.image, faces: [faceStub.face1, faceStub.mergeFace1] };
|
||||
|
||||
when(assetMock.getById).calledWith(assetWithFace.id).mockResolvedValue(assetWithFace);
|
||||
|
||||
@@ -863,9 +870,7 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
|
||||
it('should update stack parent if asset has stack children', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.primaryImage.id)
|
||||
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
||||
when(assetMock.getById).calledWith(assetStub.primaryImage.id).mockResolvedValue(assetStub.primaryImage);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id });
|
||||
|
||||
@@ -878,9 +883,7 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
|
||||
it('should not schedule delete-files job for readonly assets', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.readOnly.id)
|
||||
.mockResolvedValue(assetStub.readOnly as AssetEntity);
|
||||
when(assetMock.getById).calledWith(assetStub.readOnly.id).mockResolvedValue(assetStub.readOnly);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.readOnly.id });
|
||||
|
||||
@@ -890,21 +893,17 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
|
||||
it('should not process assets from external library without fromExternal flag', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.external.id)
|
||||
.mockResolvedValue(assetStub.external as AssetEntity);
|
||||
when(assetMock.getById).calledWith(assetStub.external.id).mockResolvedValue(assetStub.external);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id });
|
||||
|
||||
expect(jobMock.queue).not.toBeCalled();
|
||||
expect(jobMock.queueAll).not.toBeCalled();
|
||||
expect(assetMock.remove).not.toBeCalled();
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||
expect(assetMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should process assets from external library with fromExternal flag', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.external.id)
|
||||
.mockResolvedValue(assetStub.external as AssetEntity);
|
||||
when(assetMock.getById).calledWith(assetStub.external.id).mockResolvedValue(assetStub.external);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true });
|
||||
|
||||
@@ -949,6 +948,13 @@ describe(AssetService.name, () => {
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update usage', async () => {
|
||||
when(assetMock.getById).calledWith(assetStub.image.id).mockResolvedValue(assetStub.image);
|
||||
await sut.handleAssetDeletion({ id: assetStub.image.id });
|
||||
|
||||
expect(userMock.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
IPartnerRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
IUserRepository,
|
||||
ImmichReadStream,
|
||||
JobItem,
|
||||
TimeBucketOptions,
|
||||
@@ -75,6 +76,7 @@ export interface UploadFile {
|
||||
checksum: Buffer;
|
||||
originalPath: string;
|
||||
originalName: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export class AssetService {
|
||||
@@ -89,6 +91,7 @@ export class AssetService {
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||
) {
|
||||
@@ -481,6 +484,7 @@ export class AssetService {
|
||||
}
|
||||
|
||||
await this.assetRepository.remove(asset);
|
||||
await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0));
|
||||
this.communicationRepository.send(ClientEvent.ASSET_DELETE, asset.ownerId, id);
|
||||
|
||||
// TODO refactor this to use cascades
|
||||
|
||||
Reference in New Issue
Block a user