feat(web,server): offline/untracked files admin tool (#4447)

* feat: admin repair orphans tool

* chore: open api

* fix: include upload folder

* fix: bugs

* feat: empty placeholder

* fix: checks

* feat: move buttons to top of page

* feat: styling and clipboard

* styling

* better clicking hitbox

* fix: show title on hover

* feat: download report

* restrict file access to immich related files

* Add description

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
Jason Rasmussen
2023-10-14 13:12:59 -04:00
committed by GitHub
parent ed386dd12a
commit d2807b8d6a
53 changed files with 3104 additions and 87 deletions
@@ -0,0 +1,89 @@
import { DatabaseAction, EntityType } from '@app/infra/entities';
import {
IAccessRepositoryMock,
auditStub,
authStub,
newAccessRepositoryMock,
newAssetRepositoryMock,
newAuditRepositoryMock,
newCryptoRepositoryMock,
newPersonRepositoryMock,
newStorageRepositoryMock,
newUserRepositoryMock,
} from '@test';
import {
IAssetRepository,
IAuditRepository,
ICryptoRepository,
IPersonRepository,
IStorageRepository,
IUserRepository,
} from '../repositories';
import { AuditService } from './audit.service';
describe(AuditService.name, () => {
let sut: AuditService;
let accessMock: IAccessRepositoryMock;
let assetMock: jest.Mocked<IAssetRepository>;
let auditMock: jest.Mocked<IAuditRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
let userMock: jest.Mocked<IUserRepository>;
beforeEach(async () => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
cryptoMock = newCryptoRepositoryMock();
auditMock = newAuditRepositoryMock();
personMock = newPersonRepositoryMock();
storageMock = newStorageRepositoryMock();
userMock = newUserRepositoryMock();
sut = new AuditService(accessMock, assetMock, cryptoMock, personMock, auditMock, storageMock, userMock);
});
it('should work', () => {
expect(sut).toBeDefined();
});
describe('handleCleanup', () => {
it('should delete old audit entries', async () => {
await expect(sut.handleCleanup()).resolves.toBe(true);
expect(auditMock.removeBefore).toBeCalledWith(expect.any(Date));
});
});
describe('getDeletes', () => {
it('should require full sync if the request is older than 100 days', async () => {
auditMock.getAfter.mockResolvedValue([]);
const date = new Date(2022, 0, 1);
await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({
needsFullSync: true,
ids: [],
});
expect(auditMock.getAfter).toHaveBeenCalledWith(date, {
action: DatabaseAction.DELETE,
ownerId: authStub.admin.id,
entityType: EntityType.ASSET,
});
});
it('should get any new or updated assets and deleted ids', async () => {
auditMock.getAfter.mockResolvedValue([auditStub.delete]);
const date = new Date();
await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({
needsFullSync: false,
ids: ['asset-deleted'],
});
expect(auditMock.getAfter).toHaveBeenCalledWith(date, {
action: DatabaseAction.DELETE,
ownerId: authStub.admin.id,
entityType: EntityType.ASSET,
});
});
});
});