mirror of
https://github.com/immich-app/immich.git
synced 2025-01-06 03:46:47 +01:00
refactor: move asset stacks to their own entity (#6353)
* feat: auto-stack burst photos * feat: move stacks to asset stack entity * chore: pin node version with volta in server * chore: update e2e cases * chore: cleanup * feat: migrate existing stacks --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
c4b8c853bc
commit
25cad79657
29 changed files with 538 additions and 243 deletions
|
@ -10,7 +10,7 @@ import {
|
||||||
usePagination,
|
usePagination,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { AssetController } from '@app/immich';
|
import { AssetController } from '@app/immich';
|
||||||
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
|
import { AssetEntity, AssetStackEntity, AssetType, SharedLinkType } from '@app/infra/entities';
|
||||||
import { AssetRepository } from '@app/infra/repositories';
|
import { AssetRepository } from '@app/infra/repositories';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||||
|
@ -94,32 +94,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testApp.reset({ entities: [AssetEntity] });
|
await testApp.reset({ entities: [AssetEntity, AssetStackEntity] });
|
||||||
|
|
||||||
[asset1, asset2, asset3, asset4, asset5] = await Promise.all([
|
|
||||||
createAsset(user1, new Date('1970-01-01')),
|
|
||||||
createAsset(user1, new Date('1970-02-10')),
|
|
||||||
createAsset(user1, new Date('1970-02-11'), {
|
|
||||||
isFavorite: true,
|
|
||||||
isArchived: true,
|
|
||||||
isExternal: true,
|
|
||||||
isReadOnly: true,
|
|
||||||
type: AssetType.VIDEO,
|
|
||||||
fileCreatedAt: yesterday.toJSDate(),
|
|
||||||
fileModifiedAt: yesterday.toJSDate(),
|
|
||||||
createdAt: yesterday.toJSDate(),
|
|
||||||
updatedAt: yesterday.toJSDate(),
|
|
||||||
localDateTime: yesterday.toJSDate(),
|
|
||||||
}),
|
|
||||||
createAsset(user2, new Date('1970-01-01')),
|
|
||||||
createAsset(user1, new Date('1970-01-01'), {
|
|
||||||
deletedAt: yesterday.toJSDate(),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await testApp.reset({ entities: [AssetEntity] });
|
|
||||||
|
|
||||||
[asset1, asset2, asset3, asset4, asset5] = await Promise.all([
|
[asset1, asset2, asset3, asset4, asset5] = await Promise.all([
|
||||||
createAsset(user1, new Date('1970-01-01')),
|
createAsset(user1, new Date('1970-01-01')),
|
||||||
|
@ -571,11 +546,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
.get(`/asset/assetById/${asset1.id}`)
|
.get(`/asset/assetById/${asset1.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
id: asset1.id,
|
|
||||||
stack: [],
|
|
||||||
stackCount: 0,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
it('should work with a shared link', async () => {
|
||||||
|
@ -586,11 +557,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
|
|
||||||
const { status, body } = await request(server).get(`/asset/assetById/${asset1.id}?key=${sharedLink.key}`);
|
const { status, body } = await request(server).get(`/asset/assetById/${asset1.id}?key=${sharedLink.key}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
id: asset1.id,
|
|
||||||
stack: [],
|
|
||||||
stackCount: 0,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -622,11 +589,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
.get(`/asset/${asset1.id}`)
|
.get(`/asset/${asset1.id}`)
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
id: asset1.id,
|
|
||||||
stack: [],
|
|
||||||
stackCount: 0,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with a shared link', async () => {
|
it('should work with a shared link', async () => {
|
||||||
|
@ -637,11 +600,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
|
|
||||||
const { status, body } = await request(server).get(`/asset/${asset1.id}?key=${sharedLink.key}`);
|
const { status, body } = await request(server).get(`/asset/${asset1.id}?key=${sharedLink.key}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({ id: asset1.id });
|
||||||
id: asset1.id,
|
|
||||||
stack: [],
|
|
||||||
stackCount: 0,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1371,7 +1330,7 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
expect(status).toBe(204);
|
expect(status).toBe(204);
|
||||||
|
|
||||||
const asset = await api.assetApi.get(server, user1.accessToken, asset1.id);
|
const asset = await api.assetApi.get(server, user1.accessToken, asset1.id);
|
||||||
expect(asset.stack).toHaveLength(0);
|
expect(asset.stack).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should merge stack children', async () => {
|
it('should merge stack children', async () => {
|
||||||
|
|
|
@ -30,6 +30,9 @@ export const db = {
|
||||||
.map((entity) => entity.tableName)
|
.map((entity) => entity.tableName)
|
||||||
.filter((tableName) => !tableName.startsWith('geodata'));
|
.filter((tableName) => !tableName.startsWith('geodata'));
|
||||||
|
|
||||||
|
if (tableNames.includes('asset_stack')) {
|
||||||
|
await em.query(`DELETE FROM "asset_stack" CASCADE;`);
|
||||||
|
}
|
||||||
let deleteUsers = false;
|
let deleteUsers = false;
|
||||||
for (const tableName of tableNames) {
|
for (const tableName of tableNames) {
|
||||||
if (tableName === 'users') {
|
if (tableName === 'users') {
|
||||||
|
|
|
@ -156,5 +156,8 @@
|
||||||
"^@app/domain(|/.*)$": "<rootDir>/src/domain/$1"
|
"^@app/domain(|/.*)$": "<rootDir>/src/domain/$1"
|
||||||
},
|
},
|
||||||
"globalSetup": "<rootDir>/test/global-setup.js"
|
"globalSetup": "<rootDir>/test/global-setup.js"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"node": "20.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
IAccessRepositoryMock,
|
IAccessRepositoryMock,
|
||||||
|
assetStackStub,
|
||||||
assetStub,
|
assetStub,
|
||||||
authStub,
|
authStub,
|
||||||
faceStub,
|
faceStub,
|
||||||
newAccessRepositoryMock,
|
newAccessRepositoryMock,
|
||||||
newAssetRepositoryMock,
|
newAssetRepositoryMock,
|
||||||
|
newAssetStackRepositoryMock,
|
||||||
newCommunicationRepositoryMock,
|
newCommunicationRepositoryMock,
|
||||||
newJobRepositoryMock,
|
newJobRepositoryMock,
|
||||||
newPartnerRepositoryMock,
|
newPartnerRepositoryMock,
|
||||||
|
@ -20,6 +22,7 @@ import {
|
||||||
AssetStats,
|
AssetStats,
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
|
IAssetStackRepository,
|
||||||
ICommunicationRepository,
|
ICommunicationRepository,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
IPartnerRepository,
|
IPartnerRepository,
|
||||||
|
@ -160,6 +163,7 @@ describe(AssetService.name, () => {
|
||||||
let communicationMock: jest.Mocked<ICommunicationRepository>;
|
let communicationMock: jest.Mocked<ICommunicationRepository>;
|
||||||
let configMock: jest.Mocked<ISystemConfigRepository>;
|
let configMock: jest.Mocked<ISystemConfigRepository>;
|
||||||
let partnerMock: jest.Mocked<IPartnerRepository>;
|
let partnerMock: jest.Mocked<IPartnerRepository>;
|
||||||
|
let assetStackMock: jest.Mocked<IAssetStackRepository>;
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
expect(sut).toBeDefined();
|
expect(sut).toBeDefined();
|
||||||
|
@ -174,6 +178,7 @@ describe(AssetService.name, () => {
|
||||||
userMock = newUserRepositoryMock();
|
userMock = newUserRepositoryMock();
|
||||||
configMock = newSystemConfigRepositoryMock();
|
configMock = newSystemConfigRepositoryMock();
|
||||||
partnerMock = newPartnerRepositoryMock();
|
partnerMock = newPartnerRepositoryMock();
|
||||||
|
assetStackMock = newAssetStackRepositoryMock();
|
||||||
|
|
||||||
sut = new AssetService(
|
sut = new AssetService(
|
||||||
accessMock,
|
accessMock,
|
||||||
|
@ -184,6 +189,7 @@ describe(AssetService.name, () => {
|
||||||
userMock,
|
userMock,
|
||||||
communicationMock,
|
communicationMock,
|
||||||
partnerMock,
|
partnerMock,
|
||||||
|
assetStackMock,
|
||||||
);
|
);
|
||||||
|
|
||||||
when(assetMock.getById)
|
when(assetMock.getById)
|
||||||
|
@ -578,65 +584,121 @@ describe(AssetService.name, () => {
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update parent asset when children are added', async () => {
|
it('should update parent asset updatedAt when children are added', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent']));
|
||||||
|
when(assetMock.getById)
|
||||||
|
.calledWith('parent', { stack: { assets: true } })
|
||||||
|
.mockResolvedValue(assetStub.image);
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: [],
|
ids: [],
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
}),
|
}),
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['parent'], { stackParentId: null });
|
expect(assetMock.updateAll).toHaveBeenCalledWith(['parent'], { updatedAt: expect.any(Date) });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update parent asset when children are removed', async () => {
|
it('should update parent asset when children are removed', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1']));
|
||||||
assetMock.getByIds.mockResolvedValue([{ id: 'child-1', stackParentId: 'parent' } as AssetEntity]);
|
assetMock.getByIds.mockResolvedValue([
|
||||||
|
{
|
||||||
|
id: 'child-1',
|
||||||
|
stackId: 'stack-1',
|
||||||
|
stack: assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity, { id: 'child-1' } as AssetEntity]),
|
||||||
|
} as AssetEntity,
|
||||||
|
]);
|
||||||
|
when(assetStackMock.getById)
|
||||||
|
.calledWith('stack-1')
|
||||||
|
.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: ['child-1'],
|
ids: ['child-1'],
|
||||||
removeParent: true,
|
removeParent: true,
|
||||||
}),
|
});
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith(expect.arrayContaining(['parent']), { stackParentId: null });
|
expect(assetMock.updateAll).toHaveBeenCalledWith(expect.arrayContaining(['child-1']), { stack: null });
|
||||||
|
expect(assetMock.updateAll).toHaveBeenCalledWith(expect.arrayContaining(['parent']), {
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
});
|
||||||
|
expect(assetStackMock.delete).toHaveBeenCalledWith('stack-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('update parentId for new children', async () => {
|
it('update parentId for new children', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1', 'child-2']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1', 'child-2']));
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
|
const stack = assetStackStub('stack-1', [
|
||||||
|
{ id: 'parent' } as AssetEntity,
|
||||||
|
{ id: 'child-1' } as AssetEntity,
|
||||||
|
{ id: 'child-2' } as AssetEntity,
|
||||||
|
]);
|
||||||
|
when(assetMock.getById)
|
||||||
|
.calledWith('parent', { stack: { assets: true } })
|
||||||
|
.mockResolvedValue({
|
||||||
|
id: 'child-1',
|
||||||
|
stack,
|
||||||
|
} as AssetEntity);
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
ids: ['child-1', 'child-2'],
|
ids: ['child-1', 'child-2'],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2'], { stackParentId: 'parent' });
|
expect(assetStackMock.update).toHaveBeenCalledWith({
|
||||||
|
...assetStackStub('stack-1', [
|
||||||
|
{ id: 'child-1' } as AssetEntity,
|
||||||
|
{ id: 'child-2' } as AssetEntity,
|
||||||
|
{ id: 'parent' } as AssetEntity,
|
||||||
|
]),
|
||||||
|
primaryAsset: undefined,
|
||||||
|
});
|
||||||
|
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2', 'parent'], { updatedAt: expect.any(Date) });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nullify parentId for remove children', async () => {
|
it('remove stack for removed children', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1', 'child-2']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1', 'child-2']));
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
removeParent: true,
|
removeParent: true,
|
||||||
ids: ['child-1', 'child-2'],
|
ids: ['child-1', 'child-2'],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2'], { stackParentId: null });
|
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2'], { stack: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merge stacks if new child has children', async () => {
|
it('merge stacks if new child has children', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1']));
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
|
when(assetMock.getById)
|
||||||
|
.calledWith('parent', { stack: { assets: true } })
|
||||||
|
.mockResolvedValue({ ...assetStub.image, id: 'parent' });
|
||||||
assetMock.getByIds.mockResolvedValue([
|
assetMock.getByIds.mockResolvedValue([
|
||||||
{ id: 'child-1', stack: [{ id: 'child-2' } as AssetEntity] } as AssetEntity,
|
{
|
||||||
|
id: 'child-1',
|
||||||
|
stackId: 'stack-1',
|
||||||
|
stack: assetStackStub('stack-1', [{ id: 'child-1' } as AssetEntity, { id: 'child-2' } as AssetEntity]),
|
||||||
|
} as AssetEntity,
|
||||||
]);
|
]);
|
||||||
|
when(assetStackMock.getById)
|
||||||
|
.calledWith('stack-1')
|
||||||
|
.mockResolvedValue(assetStackStub('stack-1', [{ id: 'parent' } as AssetEntity]));
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: ['child-1'],
|
ids: ['child-1'],
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'child-2'], { stackParentId: 'parent' });
|
expect(assetStackMock.delete).toHaveBeenCalledWith('stack-1');
|
||||||
|
expect(assetStackMock.create).toHaveBeenCalledWith({
|
||||||
|
assets: [{ id: 'child-1' }, { id: 'parent' }, { id: 'child-1' }, { id: 'child-2' }],
|
||||||
|
primaryAssetId: 'parent',
|
||||||
|
});
|
||||||
|
expect(assetMock.updateAll).toBeCalledWith(['child-1', 'parent', 'child-1', 'child-2'], {
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send ws asset update event', async () => {
|
it('should send ws asset update event', async () => {
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1']));
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
|
when(assetMock.getById)
|
||||||
|
.calledWith('parent', { stack: { assets: true } })
|
||||||
|
.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
|
@ -645,6 +707,7 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
expect(communicationMock.send).toHaveBeenCalledWith(ClientEvent.ASSET_UPDATE, authStub.user1.user.id, [
|
expect(communicationMock.send).toHaveBeenCalledWith(ClientEvent.ASSET_UPDATE, authStub.user1.user.id, [
|
||||||
'asset-1',
|
'asset-1',
|
||||||
|
'parent',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -702,7 +765,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetWithFace);
|
.mockResolvedValue(assetWithFace);
|
||||||
|
@ -729,25 +792,23 @@ describe(AssetService.name, () => {
|
||||||
expect(assetMock.remove).toHaveBeenCalledWith(assetWithFace);
|
expect(assetMock.remove).toHaveBeenCalledWith(assetWithFace);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update stack parent if asset has stack children', async () => {
|
it('should update stack primary asset if deleted asset was primary asset in a stack', async () => {
|
||||||
when(assetMock.getById)
|
when(assetMock.getById)
|
||||||
.calledWith(assetStub.primaryImage.id, {
|
.calledWith(assetStub.primaryImage.id, {
|
||||||
faces: {
|
faces: {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.primaryImage);
|
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
||||||
|
|
||||||
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id });
|
await sut.handleAssetDeletion({ id: assetStub.primaryImage.id });
|
||||||
|
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['stack-child-asset-2'], {
|
expect(assetStackMock.update).toHaveBeenCalledWith({
|
||||||
stackParentId: 'stack-child-asset-1',
|
id: 'stack-1',
|
||||||
});
|
primaryAssetId: 'stack-child-asset-1',
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['stack-child-asset-1'], {
|
|
||||||
stackParentId: null,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -758,7 +819,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.readOnly);
|
.mockResolvedValue(assetStub.readOnly);
|
||||||
|
@ -787,7 +848,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.external);
|
.mockResolvedValue(assetStub.external);
|
||||||
|
@ -819,7 +880,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.livePhotoStillAsset);
|
.mockResolvedValue(assetStub.livePhotoStillAsset);
|
||||||
|
@ -829,7 +890,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
||||||
|
@ -864,7 +925,7 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.image);
|
.mockResolvedValue(assetStub.image);
|
||||||
|
@ -927,54 +988,21 @@ describe(AssetService.name, () => {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: {
|
||||||
|
assets: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.mockResolvedValue(assetStub.image as AssetEntity);
|
.mockResolvedValue({ ...assetStub.image, stackId: 'stack-1' });
|
||||||
|
|
||||||
await sut.updateStackParent(authStub.user1, {
|
await sut.updateStackParent(authStub.user1, {
|
||||||
oldParentId: assetStub.image.id,
|
oldParentId: assetStub.image.id,
|
||||||
newParentId: 'new',
|
newParentId: 'new',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith([assetStub.image.id], { stackParentId: 'new' });
|
expect(assetStackMock.update).toBeCalledWith({ id: 'stack-1', primaryAssetId: 'new' });
|
||||||
|
expect(assetMock.updateAll).toBeCalledWith([assetStub.image.id, 'new', assetStub.image.id], {
|
||||||
|
updatedAt: expect.any(Date),
|
||||||
});
|
});
|
||||||
|
|
||||||
it('remove stackParentId of new parent', async () => {
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.primaryImage.id]));
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
|
|
||||||
|
|
||||||
await sut.updateStackParent(authStub.user1, {
|
|
||||||
oldParentId: assetStub.primaryImage.id,
|
|
||||||
newParentId: 'new',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith(['new'], { stackParentId: null });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('update stackParentId of old parents children to new parent', async () => {
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.primaryImage.id]));
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
|
|
||||||
when(assetMock.getById)
|
|
||||||
.calledWith(assetStub.primaryImage.id, {
|
|
||||||
faces: {
|
|
||||||
person: true,
|
|
||||||
},
|
|
||||||
library: true,
|
|
||||||
stack: true,
|
|
||||||
})
|
|
||||||
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
|
||||||
|
|
||||||
await sut.updateStackParent(authStub.user1, {
|
|
||||||
oldParentId: assetStub.primaryImage.id,
|
|
||||||
newParentId: 'new',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(assetMock.updateAll).toBeCalledWith(
|
|
||||||
[assetStub.primaryImage.id, 'stack-child-asset-1', 'stack-child-asset-2'],
|
|
||||||
{
|
|
||||||
stackParentId: 'new',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
IAccessRepository,
|
IAccessRepository,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
|
IAssetStackRepository,
|
||||||
ICommunicationRepository,
|
ICommunicationRepository,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
IPartnerRepository,
|
IPartnerRepository,
|
||||||
|
@ -85,6 +86,7 @@ export class AssetService {
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
||||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||||
|
@Inject(IAssetStackRepository) private assetStackRepository: IAssetStackRepository,
|
||||||
) {
|
) {
|
||||||
this.access = AccessCore.create(accessRepository);
|
this.access = AccessCore.create(accessRepository);
|
||||||
this.configCore = SystemConfigCore.create(configRepository);
|
this.configCore = SystemConfigCore.create(configRepository);
|
||||||
|
@ -299,8 +301,10 @@ export class AssetService {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
stack: {
|
stack: {
|
||||||
|
assets: {
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
|
@ -338,25 +342,51 @@ export class AssetService {
|
||||||
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
||||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, ids);
|
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, ids);
|
||||||
|
|
||||||
|
// TODO: refactor this logic into separate API calls POST /stack, PUT /stack, etc.
|
||||||
|
const stackIdsToCheckForDelete: string[] = [];
|
||||||
if (removeParent) {
|
if (removeParent) {
|
||||||
(options as Partial<AssetEntity>).stackParentId = null;
|
(options as Partial<AssetEntity>).stack = null;
|
||||||
const assets = await this.assetRepository.getByIds(ids);
|
const assets = await this.assetRepository.getByIds(ids);
|
||||||
|
stackIdsToCheckForDelete.push(...new Set(assets.filter((a) => !!a.stackId).map((a) => a.stackId!)));
|
||||||
// This updates the updatedAt column of the parents to indicate that one of its children is removed
|
// This updates the updatedAt column of the parents to indicate that one of its children is removed
|
||||||
// All the unique parent's -> parent is set to null
|
// All the unique parent's -> parent is set to null
|
||||||
ids.push(...new Set(assets.filter((a) => !!a.stackParentId).map((a) => a.stackParentId!)));
|
await this.assetRepository.updateAll(
|
||||||
|
assets.filter((a) => !!a.stack?.primaryAssetId).map((a) => a.stack!.primaryAssetId!),
|
||||||
|
{ updatedAt: new Date() },
|
||||||
|
);
|
||||||
} else if (options.stackParentId) {
|
} else if (options.stackParentId) {
|
||||||
|
//Creating new stack if parent doesn't have one already. If it does, then we add to the existing stack
|
||||||
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, options.stackParentId);
|
await this.access.requirePermission(auth, Permission.ASSET_UPDATE, options.stackParentId);
|
||||||
// Merge stacks
|
const primaryAsset = await this.assetRepository.getById(options.stackParentId, { stack: { assets: true } });
|
||||||
const assets = await this.assetRepository.getByIds(ids);
|
if (!primaryAsset) {
|
||||||
const assetsWithChildren = assets.filter((a) => a.stack && a.stack.length > 0);
|
throw new BadRequestException('Asset not found for given stackParentId');
|
||||||
ids.push(...assetsWithChildren.flatMap((child) => child.stack!.map((gChild) => gChild.id)));
|
}
|
||||||
|
let stack = primaryAsset.stack;
|
||||||
|
|
||||||
// This updates the updatedAt column of the parent to indicate that a new child has been added
|
ids.push(options.stackParentId);
|
||||||
await this.assetRepository.updateAll([options.stackParentId], { stackParentId: null });
|
const assets = await this.assetRepository.getByIds(ids, { stack: { assets: true } });
|
||||||
|
stackIdsToCheckForDelete.push(
|
||||||
|
...new Set(assets.filter((a) => !!a.stackId && stack?.id !== a.stackId).map((a) => a.stackId!)),
|
||||||
|
);
|
||||||
|
const assetsWithChildren = assets.filter((a) => a.stack && a.stack.assets.length > 0);
|
||||||
|
ids.push(...assetsWithChildren.flatMap((child) => child.stack!.assets.map((gChild) => gChild.id)));
|
||||||
|
|
||||||
|
if (!stack) {
|
||||||
|
stack = await this.assetStackRepository.create({
|
||||||
|
primaryAssetId: primaryAsset.id,
|
||||||
|
assets: ids.map((id) => ({ id }) as AssetEntity),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.assetStackRepository.update({
|
||||||
|
id: stack.id,
|
||||||
|
primaryAssetId: primaryAsset.id,
|
||||||
|
assets: ids.map((id) => ({ id }) as AssetEntity),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of ids) {
|
// Merge stacks
|
||||||
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
|
options.stackParentId = undefined;
|
||||||
|
(options as Partial<AssetEntity>).updatedAt = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
|
@ -364,6 +394,12 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.assetRepository.updateAll(ids, options);
|
await this.assetRepository.updateAll(ids, options);
|
||||||
|
const stacksToDelete = (
|
||||||
|
await Promise.all(stackIdsToCheckForDelete.map((id) => this.assetStackRepository.getById(id)))
|
||||||
|
)
|
||||||
|
.flatMap((stack) => (stack ? [stack] : []))
|
||||||
|
.filter((stack) => stack.assets.length < 2);
|
||||||
|
await Promise.all(stacksToDelete.map((as) => this.assetStackRepository.delete(as.id)));
|
||||||
this.communicationRepository.send(ClientEvent.ASSET_UPDATE, auth.user.id, ids);
|
this.communicationRepository.send(ClientEvent.ASSET_UPDATE, auth.user.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,7 +430,7 @@ export class AssetService {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -408,11 +444,17 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the parent of the stack children with a new asset
|
// Replace the parent of the stack children with a new asset
|
||||||
if (asset.stack && asset.stack.length != 0) {
|
if (asset.stack?.primaryAssetId === id) {
|
||||||
const stackIds = asset.stack.map((a) => a.id);
|
const stackAssetIds = asset.stack.assets.map((a) => a.id);
|
||||||
const newParentId = stackIds[0];
|
if (stackAssetIds.length > 2) {
|
||||||
await this.assetRepository.updateAll(stackIds.slice(1), { stackParentId: newParentId });
|
const newPrimaryAssetId = stackAssetIds.find((a) => a !== id)!;
|
||||||
await this.assetRepository.updateAll([newParentId], { stackParentId: null });
|
await this.assetStackRepository.update({
|
||||||
|
id: asset.stack.id,
|
||||||
|
primaryAssetId: newPrimaryAssetId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.assetStackRepository.delete(asset.stack.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.assetRepository.remove(asset);
|
await this.assetRepository.remove(asset);
|
||||||
|
@ -460,18 +502,25 @@ export class AssetService {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
library: true,
|
library: true,
|
||||||
stack: true,
|
stack: {
|
||||||
|
assets: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
if (!oldParent?.stackId) {
|
||||||
|
throw new Error('Asset not found or not in a stack');
|
||||||
|
}
|
||||||
if (oldParent != null) {
|
if (oldParent != null) {
|
||||||
childIds.push(oldParent.id);
|
childIds.push(oldParent.id);
|
||||||
// Get all children of old parent
|
// Get all children of old parent
|
||||||
childIds.push(...(oldParent.stack?.map((a) => a.id) ?? []));
|
childIds.push(...(oldParent.stack?.assets.map((a) => a.id) ?? []));
|
||||||
}
|
}
|
||||||
|
await this.assetStackRepository.update({
|
||||||
|
id: oldParent.stackId,
|
||||||
|
primaryAssetId: newParentId,
|
||||||
|
});
|
||||||
|
|
||||||
this.communicationRepository.send(ClientEvent.ASSET_UPDATE, auth.user.id, [...childIds, newParentId]);
|
this.communicationRepository.send(ClientEvent.ASSET_UPDATE, auth.user.id, [...childIds, newParentId, oldParentId]);
|
||||||
await this.assetRepository.updateAll(childIds, { stackParentId: newParentId });
|
await this.assetRepository.updateAll([oldParentId, newParentId, ...childIds], { updatedAt: new Date() });
|
||||||
// Remove ParentId of new parent if this was previously a child of some other asset
|
|
||||||
return this.assetRepository.updateAll([newParentId], { stackParentId: null });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(auth: AuthDto, dto: AssetJobsDto) {
|
async run(auth: AuthDto, dto: AssetJobsDto) {
|
||||||
|
|
|
@ -116,9 +116,13 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
people: peopleWithFaces(entity.faces),
|
people: peopleWithFaces(entity.faces),
|
||||||
checksum: entity.checksum.toString('base64'),
|
checksum: entity.checksum.toString('base64'),
|
||||||
stackParentId: entity.stackParentId,
|
stackParentId: withStack ? entity.stack?.primaryAssetId : undefined,
|
||||||
stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined,
|
stack: withStack
|
||||||
stackCount: entity.stack?.length ?? null,
|
? entity.stack?.assets
|
||||||
|
.filter((a) => a.id !== entity.stack?.primaryAssetId)
|
||||||
|
.map((a) => mapAsset(a, { stripMetadata }))
|
||||||
|
: undefined,
|
||||||
|
stackCount: entity.stack?.assets?.length ?? null,
|
||||||
isExternal: entity.isExternal,
|
isExternal: entity.isExternal,
|
||||||
isOffline: entity.isOffline,
|
isOffline: entity.isOffline,
|
||||||
isReadOnly: entity.isReadOnly,
|
isReadOnly: entity.isReadOnly,
|
||||||
|
|
|
@ -499,6 +499,7 @@ describe(MetadataService.name, () => {
|
||||||
expect(assetMock.upsertExif).toHaveBeenCalledWith({
|
expect(assetMock.upsertExif).toHaveBeenCalledWith({
|
||||||
assetId: assetStub.image.id,
|
assetId: assetStub.image.id,
|
||||||
bitsPerSample: expect.any(Number),
|
bitsPerSample: expect.any(Number),
|
||||||
|
autoStackId: null,
|
||||||
colorspace: tags.ColorSpace,
|
colorspace: tags.ColorSpace,
|
||||||
dateTimeOriginal: new Date('1970-01-01'),
|
dateTimeOriginal: new Date('1970-01-01'),
|
||||||
description: tags.ImageDescription,
|
description: tags.ImageDescription,
|
||||||
|
|
|
@ -499,6 +499,7 @@ export class MetadataService {
|
||||||
latitude: validate(tags.GPSLatitude),
|
latitude: validate(tags.GPSLatitude),
|
||||||
lensModel: tags.LensModel ?? null,
|
lensModel: tags.LensModel ?? null,
|
||||||
livePhotoCID: (tags.ContentIdentifier || tags.MediaGroupUUID) ?? null,
|
livePhotoCID: (tags.ContentIdentifier || tags.MediaGroupUUID) ?? null,
|
||||||
|
autoStackId: this.getAutoStackId(tags),
|
||||||
longitude: validate(tags.GPSLongitude),
|
longitude: validate(tags.GPSLongitude),
|
||||||
make: tags.Make ?? null,
|
make: tags.Make ?? null,
|
||||||
model: tags.Model ?? null,
|
model: tags.Model ?? null,
|
||||||
|
@ -518,6 +519,13 @@ export class MetadataService {
|
||||||
return { exifData, tags };
|
return { exifData, tags };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAutoStackId(tags: ImmichTags | null): string | null {
|
||||||
|
if (!tags) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return tags.BurstID ?? tags.BurstUUID ?? tags.CameraBurstID ?? tags.MediaUniqueID ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
private getDateTimeOriginal(tags: ImmichTags | Tags | null) {
|
private getDateTimeOriginal(tags: ImmichTags | Tags | null) {
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
return null;
|
return null;
|
||||||
|
|
10
server/src/domain/repositories/asset-stack.repository.ts
Normal file
10
server/src/domain/repositories/asset-stack.repository.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { AssetStackEntity } from '@app/infra/entities/asset-stack.entity';
|
||||||
|
|
||||||
|
export const IAssetStackRepository = 'IAssetStackRepository';
|
||||||
|
|
||||||
|
export interface IAssetStackRepository {
|
||||||
|
create(assetStack: Partial<AssetStackEntity>): Promise<AssetStackEntity>;
|
||||||
|
update(asset: Pick<AssetStackEntity, 'id'> & Partial<AssetStackEntity>): Promise<AssetStackEntity>;
|
||||||
|
delete(id: string): Promise<void>;
|
||||||
|
getById(id: string): Promise<AssetStackEntity | null>;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ export * from './access.repository';
|
||||||
export * from './activity.repository';
|
export * from './activity.repository';
|
||||||
export * from './album.repository';
|
export * from './album.repository';
|
||||||
export * from './api-key.repository';
|
export * from './api-key.repository';
|
||||||
|
export * from './asset-stack.repository';
|
||||||
export * from './asset.repository';
|
export * from './asset.repository';
|
||||||
export * from './audit.repository';
|
export * from './audit.repository';
|
||||||
export * from './communication.repository';
|
export * from './communication.repository';
|
||||||
|
|
|
@ -108,7 +108,7 @@ export class AssetRepositoryV1 implements IAssetRepositoryV1 {
|
||||||
relations: {
|
relations: {
|
||||||
exifInfo: true,
|
exifInfo: true,
|
||||||
tags: true,
|
tags: true,
|
||||||
stack: true,
|
stack: { assets: true },
|
||||||
},
|
},
|
||||||
skip: dto.skip || 0,
|
skip: dto.skip || 0,
|
||||||
take: dto.take,
|
take: dto.take,
|
||||||
|
|
19
server/src/infra/entities/asset-stack.entity.ts
Normal file
19
server/src/infra/entities/asset-stack.entity.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
import { AssetEntity } from './asset.entity';
|
||||||
|
|
||||||
|
@Entity('asset_stack')
|
||||||
|
export class AssetStackEntity {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@OneToMany(() => AssetEntity, (asset) => asset.stack)
|
||||||
|
assets!: AssetEntity[];
|
||||||
|
|
||||||
|
@OneToOne(() => AssetEntity)
|
||||||
|
@JoinColumn()
|
||||||
|
//TODO: Add constraint to ensure primary asset exists in the assets array
|
||||||
|
primaryAsset!: AssetEntity;
|
||||||
|
|
||||||
|
@Column({ nullable: false })
|
||||||
|
primaryAssetId!: string;
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { AlbumEntity } from './album.entity';
|
import { AlbumEntity } from './album.entity';
|
||||||
import { AssetFaceEntity } from './asset-face.entity';
|
import { AssetFaceEntity } from './asset-face.entity';
|
||||||
import { AssetJobStatusEntity } from './asset-job-status.entity';
|
import { AssetJobStatusEntity } from './asset-job-status.entity';
|
||||||
|
import { AssetStackEntity } from './asset-stack.entity';
|
||||||
import { ExifEntity } from './exif.entity';
|
import { ExifEntity } from './exif.entity';
|
||||||
import { LibraryEntity } from './library.entity';
|
import { LibraryEntity } from './library.entity';
|
||||||
import { SharedLinkEntity } from './shared-link.entity';
|
import { SharedLinkEntity } from './shared-link.entity';
|
||||||
|
@ -34,7 +35,6 @@ export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum';
|
||||||
@Index('IDX_day_of_month', { synchronize: false })
|
@Index('IDX_day_of_month', { synchronize: false })
|
||||||
@Index('IDX_month', { synchronize: false })
|
@Index('IDX_month', { synchronize: false })
|
||||||
@Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId'])
|
@Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId'])
|
||||||
@Index(['stackParentId'])
|
|
||||||
// For all assets, each originalpath must be unique per user and library
|
// For all assets, each originalpath must be unique per user and library
|
||||||
export class AssetEntity {
|
export class AssetEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
@ -157,14 +157,11 @@ export class AssetEntity {
|
||||||
faces!: AssetFaceEntity[];
|
faces!: AssetFaceEntity[];
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
stackParentId?: string | null;
|
stackId?: string | null;
|
||||||
|
|
||||||
@ManyToOne(() => AssetEntity, (asset) => asset.stack, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
|
@ManyToOne(() => AssetStackEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'stackParentId' })
|
@JoinColumn()
|
||||||
stackParent?: AssetEntity | null;
|
stack?: AssetStackEntity | null;
|
||||||
|
|
||||||
@OneToMany(() => AssetEntity, (asset) => asset.stackParent)
|
|
||||||
stack?: AssetEntity[];
|
|
||||||
|
|
||||||
@OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true })
|
@OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true })
|
||||||
jobStatus?: AssetJobStatusEntity;
|
jobStatus?: AssetJobStatusEntity;
|
||||||
|
|
|
@ -54,6 +54,10 @@ export class ExifEntity {
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'varchar', nullable: true })
|
||||||
livePhotoCID!: string | null;
|
livePhotoCID!: string | null;
|
||||||
|
|
||||||
|
@Index('IDX_auto_stack_id')
|
||||||
|
@Column({ type: 'varchar', nullable: true })
|
||||||
|
autoStackId!: string | null;
|
||||||
|
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'varchar', nullable: true })
|
||||||
state!: string | null;
|
state!: string | null;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { GeodataAdmin2Entity } from '@app/infra/entities/geodata-admin2.entity';
|
|
||||||
import { ActivityEntity } from './activity.entity';
|
import { ActivityEntity } from './activity.entity';
|
||||||
import { AlbumEntity } from './album.entity';
|
import { AlbumEntity } from './album.entity';
|
||||||
import { APIKeyEntity } from './api-key.entity';
|
import { APIKeyEntity } from './api-key.entity';
|
||||||
import { AssetFaceEntity } from './asset-face.entity';
|
import { AssetFaceEntity } from './asset-face.entity';
|
||||||
import { AssetJobStatusEntity } from './asset-job-status.entity';
|
import { AssetJobStatusEntity } from './asset-job-status.entity';
|
||||||
|
import { AssetStackEntity } from './asset-stack.entity';
|
||||||
import { AssetEntity } from './asset.entity';
|
import { AssetEntity } from './asset.entity';
|
||||||
import { AuditEntity } from './audit.entity';
|
import { AuditEntity } from './audit.entity';
|
||||||
import { ExifEntity } from './exif.entity';
|
import { ExifEntity } from './exif.entity';
|
||||||
import { GeodataAdmin1Entity } from './geodata-admin1.entity';
|
import { GeodataAdmin1Entity } from './geodata-admin1.entity';
|
||||||
|
import { GeodataAdmin2Entity } from './geodata-admin2.entity';
|
||||||
import { GeodataPlacesEntity } from './geodata-places.entity';
|
import { GeodataPlacesEntity } from './geodata-places.entity';
|
||||||
import { LibraryEntity } from './library.entity';
|
import { LibraryEntity } from './library.entity';
|
||||||
import { MoveEntity } from './move.entity';
|
import { MoveEntity } from './move.entity';
|
||||||
|
@ -27,6 +28,7 @@ export * from './album.entity';
|
||||||
export * from './api-key.entity';
|
export * from './api-key.entity';
|
||||||
export * from './asset-face.entity';
|
export * from './asset-face.entity';
|
||||||
export * from './asset-job-status.entity';
|
export * from './asset-job-status.entity';
|
||||||
|
export * from './asset-stack.entity';
|
||||||
export * from './asset.entity';
|
export * from './asset.entity';
|
||||||
export * from './audit.entity';
|
export * from './audit.entity';
|
||||||
export * from './exif.entity';
|
export * from './exif.entity';
|
||||||
|
@ -51,6 +53,7 @@ export const databaseEntities = [
|
||||||
AlbumEntity,
|
AlbumEntity,
|
||||||
APIKeyEntity,
|
APIKeyEntity,
|
||||||
AssetEntity,
|
AssetEntity,
|
||||||
|
AssetStackEntity,
|
||||||
AssetFaceEntity,
|
AssetFaceEntity,
|
||||||
AssetJobStatusEntity,
|
AssetJobStatusEntity,
|
||||||
AuditEntity,
|
AuditEntity,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
IActivityRepository,
|
IActivityRepository,
|
||||||
IAlbumRepository,
|
IAlbumRepository,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
|
IAssetStackRepository,
|
||||||
IAuditRepository,
|
IAuditRepository,
|
||||||
ICommunicationRepository,
|
ICommunicationRepository,
|
||||||
ICryptoRepository,
|
ICryptoRepository,
|
||||||
|
@ -41,6 +42,7 @@ import {
|
||||||
AlbumRepository,
|
AlbumRepository,
|
||||||
ApiKeyRepository,
|
ApiKeyRepository,
|
||||||
AssetRepository,
|
AssetRepository,
|
||||||
|
AssetStackRepository,
|
||||||
AuditRepository,
|
AuditRepository,
|
||||||
CommunicationRepository,
|
CommunicationRepository,
|
||||||
CryptoRepository,
|
CryptoRepository,
|
||||||
|
@ -69,6 +71,7 @@ const providers: Provider[] = [
|
||||||
{ provide: IAccessRepository, useClass: AccessRepository },
|
{ provide: IAccessRepository, useClass: AccessRepository },
|
||||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||||
|
{ provide: IAssetStackRepository, useClass: AssetStackRepository },
|
||||||
{ provide: IAuditRepository, useClass: AuditRepository },
|
{ provide: IAuditRepository, useClass: AuditRepository },
|
||||||
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
|
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
|
||||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||||
|
|
16
server/src/infra/migrations/1703035138085-AddAutoStackId.ts
Normal file
16
server/src/infra/migrations/1703035138085-AddAutoStackId.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class AddAutoStackId1703035138085 implements MigrationInterface {
|
||||||
|
name = 'AddAutoStackId1703035138085'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" ADD "autoStackId" character varying`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_auto_stack_id" ON "exif" ("autoStackId") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_auto_stack_id"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "autoStackId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAssetStackTable1705197515600 implements MigrationInterface {
|
||||||
|
name = 'CreateAssetStackTable1705197515600';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// create table
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "asset_stack" (
|
||||||
|
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
"primaryAssetId" uuid NOT NULL,
|
||||||
|
CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId"),
|
||||||
|
CONSTRAINT "PK_74a27e7fcbd5852463d0af3034b" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// create stacks
|
||||||
|
await queryRunner.query(
|
||||||
|
`INSERT INTO "asset_stack" ("primaryAssetId")
|
||||||
|
SELECT DISTINCT("stackParentId" )
|
||||||
|
FROM "assets"
|
||||||
|
WHERE "stackParentId" IS NOT NULL;`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add "stackId"
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" ADD COLUMN "stackId" uuid`);
|
||||||
|
|
||||||
|
// set "stackId" for parents
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE "assets"
|
||||||
|
SET "stackId" = "asset_stack"."id"
|
||||||
|
FROM "asset_stack"
|
||||||
|
WHERE "assets"."id" = "asset_stack"."primaryAssetId"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// set "stackId" for children
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE "assets"
|
||||||
|
SET "stackId" = "asset_stack"."id"
|
||||||
|
FROM "asset_stack"
|
||||||
|
WHERE "assets"."stackParentId" = "asset_stack"."primaryAssetId"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// update constraints
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_b463c8edb01364bf2beba08ef1"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_b463c8edb01364bf2beba08ef19"`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack"("id") ON DELETE SET NULL ON UPDATE CASCADE`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// drop "stackParentId"
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "stackParentId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// add "stackParentId"
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" ADD COLUMN "stackParentId" uuid`);
|
||||||
|
|
||||||
|
// set "stackParentId" for parents
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE "assets"
|
||||||
|
SET "stackParentId" = "asset_stack"."primaryAssetId"
|
||||||
|
FROM "asset_stack"
|
||||||
|
WHERE "assets"."stackId" = "asset_stack"."id" and "assets"."id" != "asset_stack"."primaryAssetId"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// update constraints
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "assets" ADD CONSTRAINT "FK_b463c8edb01364bf2beba08ef19" FOREIGN KEY ("stackParentId") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_b463c8edb01364bf2beba08ef1" ON "assets" ("stackParentId") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_stack" DROP CONSTRAINT "FK_91704e101438fd0653f582426dc"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207"`);
|
||||||
|
|
||||||
|
// drop table
|
||||||
|
await queryRunner.query(`DROP TABLE "asset_stack"`);
|
||||||
|
|
||||||
|
// drop "stackId"
|
||||||
|
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "stackId"`);
|
||||||
|
}
|
||||||
|
}
|
47
server/src/infra/repositories/asset-stack.repository.ts
Normal file
47
server/src/infra/repositories/asset-stack.repository.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { IAssetStackRepository } from '@app/domain';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { AssetStackEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AssetStackRepository implements IAssetStackRepository {
|
||||||
|
constructor(@InjectRepository(AssetStackEntity) private repository: Repository<AssetStackEntity>) {}
|
||||||
|
|
||||||
|
create(entity: Partial<AssetStackEntity>) {
|
||||||
|
return this.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string): Promise<void> {
|
||||||
|
await this.repository.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(entity: Partial<AssetStackEntity>) {
|
||||||
|
return this.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string): Promise<AssetStackEntity | null> {
|
||||||
|
return this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
primaryAsset: true,
|
||||||
|
assets: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async save(entity: Partial<AssetStackEntity>) {
|
||||||
|
const { id } = await this.repository.save(entity);
|
||||||
|
return this.repository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
primaryAsset: true,
|
||||||
|
assets: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -138,7 +138,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
|
|
||||||
const withExif = Object.keys(exifWhere).length > 0 || _withExif;
|
const withExif = Object.keys(exifWhere).length > 0 || _withExif;
|
||||||
|
|
||||||
const where = _.omitBy(
|
const where: FindOptionsWhere<AssetEntity> = _.omitBy(
|
||||||
{
|
{
|
||||||
ownerId,
|
ownerId,
|
||||||
id,
|
id,
|
||||||
|
@ -182,10 +182,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
builder.leftJoinAndSelect('faces.person', 'person');
|
builder.leftJoinAndSelect('faces.person', 'person');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withStacked) {
|
|
||||||
builder.leftJoinAndSelect('asset.stack', 'stack');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withSmartInfo) {
|
if (withSmartInfo) {
|
||||||
builder.leftJoinAndSelect('asset.smartInfo', 'smartInfo');
|
builder.leftJoinAndSelect('asset.smartInfo', 'smartInfo');
|
||||||
}
|
}
|
||||||
|
@ -194,13 +190,20 @@ export class AssetRepository implements IAssetRepository {
|
||||||
builder.withDeleted();
|
builder.withDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.where(where);
|
||||||
|
|
||||||
|
if (withStacked) {
|
||||||
builder
|
builder
|
||||||
.where(where)
|
.leftJoinAndSelect('asset.stack', 'stack')
|
||||||
|
.leftJoinAndSelect('stack.assets', 'stackedAssets')
|
||||||
|
.andWhere(new Brackets((qb) => qb.where('stack.primaryAssetId = asset.id').orWhere('asset.stackId IS NULL')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
.skip(size * (page - 1))
|
.skip(size * (page - 1))
|
||||||
.take(size)
|
.take(size)
|
||||||
.orderBy('asset.fileCreatedAt', order ?? 'DESC');
|
.orderBy('asset.fileCreatedAt', order ?? 'DESC')
|
||||||
|
.getMany();
|
||||||
return builder.getMany();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(asset: AssetCreate): Promise<AssetEntity> {
|
create(asset: AssetCreate): Promise<AssetEntity> {
|
||||||
|
@ -279,7 +282,9 @@ export class AssetRepository implements IAssetRepository {
|
||||||
faces: {
|
faces: {
|
||||||
person: true,
|
person: true,
|
||||||
},
|
},
|
||||||
stack: true,
|
stack: {
|
||||||
|
assets: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,8 +802,14 @@ export class AssetRepository implements IAssetRepository {
|
||||||
builder = builder.andWhere('asset.type = :assetType', { assetType });
|
builder = builder.andWhere('asset.type = :assetType', { assetType });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stackJoined = false;
|
||||||
|
|
||||||
if (exifInfo !== false) {
|
if (exifInfo !== false) {
|
||||||
builder = builder.leftJoinAndSelect('asset.exifInfo', 'exifInfo').leftJoinAndSelect('asset.stack', 'stack');
|
stackJoined = true;
|
||||||
|
builder = builder
|
||||||
|
.leftJoinAndSelect('asset.exifInfo', 'exifInfo')
|
||||||
|
.leftJoinAndSelect('asset.stack', 'stack')
|
||||||
|
.leftJoinAndSelect('stack.assets', 'stackedAssets');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumId) {
|
if (albumId) {
|
||||||
|
@ -829,7 +840,12 @@ export class AssetRepository implements IAssetRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withStacked) {
|
if (withStacked) {
|
||||||
builder = builder.andWhere('asset.stackParentId IS NULL');
|
if (!stackJoined) {
|
||||||
|
builder = builder.leftJoinAndSelect('asset.stack', 'stack').leftJoinAndSelect('stack.assets', 'stackedAssets');
|
||||||
|
}
|
||||||
|
builder = builder.andWhere(
|
||||||
|
new Brackets((qb) => qb.where('stack.primaryAssetId = asset.id').orWhere('asset.stackId IS NULL')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * from './access.repository';
|
||||||
export * from './activity.repository';
|
export * from './activity.repository';
|
||||||
export * from './album.repository';
|
export * from './album.repository';
|
||||||
export * from './api-key.repository';
|
export * from './api-key.repository';
|
||||||
|
export * from './asset-stack.repository';
|
||||||
export * from './asset.repository';
|
export * from './asset.repository';
|
||||||
export * from './audit.repository';
|
export * from './audit.repository';
|
||||||
export * from './communication.repository';
|
export * from './communication.repository';
|
||||||
|
|
|
@ -30,7 +30,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
||||||
|
@ -45,6 +45,7 @@ SELECT
|
||||||
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."autoStackId" AS "AssetEntity__AssetEntity_exifInfo_autoStackId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
@ -105,7 +106,7 @@ SELECT
|
||||||
"entity"."livePhotoVideoId" AS "entity_livePhotoVideoId",
|
"entity"."livePhotoVideoId" AS "entity_livePhotoVideoId",
|
||||||
"entity"."originalFileName" AS "entity_originalFileName",
|
"entity"."originalFileName" AS "entity_originalFileName",
|
||||||
"entity"."sidecarPath" AS "entity_sidecarPath",
|
"entity"."sidecarPath" AS "entity_sidecarPath",
|
||||||
"entity"."stackParentId" AS "entity_stackParentId",
|
"entity"."stackId" AS "entity_stackId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||||
|
@ -120,6 +121,7 @@ SELECT
|
||||||
"exifInfo"."projectionType" AS "exifInfo_projectionType",
|
"exifInfo"."projectionType" AS "exifInfo_projectionType",
|
||||||
"exifInfo"."city" AS "exifInfo_city",
|
"exifInfo"."city" AS "exifInfo_city",
|
||||||
"exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
|
"exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
|
||||||
|
"exifInfo"."autoStackId" AS "exifInfo_autoStackId",
|
||||||
"exifInfo"."state" AS "exifInfo_state",
|
"exifInfo"."state" AS "exifInfo_state",
|
||||||
"exifInfo"."country" AS "exifInfo_country",
|
"exifInfo"."country" AS "exifInfo_country",
|
||||||
"exifInfo"."make" AS "exifInfo_make",
|
"exifInfo"."make" AS "exifInfo_make",
|
||||||
|
@ -187,7 +189,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
||||||
|
@ -202,6 +204,7 @@ SELECT
|
||||||
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."autoStackId" AS "AssetEntity__AssetEntity_exifInfo_autoStackId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
@ -242,34 +245,36 @@ SELECT
|
||||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
|
||||||
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
|
||||||
"AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id",
|
"AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id",
|
||||||
"AssetEntity__AssetEntity_stack"."deviceAssetId" AS "AssetEntity__AssetEntity_stack_deviceAssetId",
|
"AssetEntity__AssetEntity_stack"."primaryAssetId" AS "AssetEntity__AssetEntity_stack_primaryAssetId",
|
||||||
"AssetEntity__AssetEntity_stack"."ownerId" AS "AssetEntity__AssetEntity_stack_ownerId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."id" AS "bd93d5747511a4dad4923546c51365bf1a803774_id",
|
||||||
"AssetEntity__AssetEntity_stack"."libraryId" AS "AssetEntity__AssetEntity_stack_libraryId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."deviceAssetId" AS "bd93d5747511a4dad4923546c51365bf1a803774_deviceAssetId",
|
||||||
"AssetEntity__AssetEntity_stack"."deviceId" AS "AssetEntity__AssetEntity_stack_deviceId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."ownerId" AS "bd93d5747511a4dad4923546c51365bf1a803774_ownerId",
|
||||||
"AssetEntity__AssetEntity_stack"."type" AS "AssetEntity__AssetEntity_stack_type",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."libraryId" AS "bd93d5747511a4dad4923546c51365bf1a803774_libraryId",
|
||||||
"AssetEntity__AssetEntity_stack"."originalPath" AS "AssetEntity__AssetEntity_stack_originalPath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."deviceId" AS "bd93d5747511a4dad4923546c51365bf1a803774_deviceId",
|
||||||
"AssetEntity__AssetEntity_stack"."resizePath" AS "AssetEntity__AssetEntity_stack_resizePath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."type" AS "bd93d5747511a4dad4923546c51365bf1a803774_type",
|
||||||
"AssetEntity__AssetEntity_stack"."webpPath" AS "AssetEntity__AssetEntity_stack_webpPath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."originalPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalPath",
|
||||||
"AssetEntity__AssetEntity_stack"."thumbhash" AS "AssetEntity__AssetEntity_stack_thumbhash",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."resizePath" AS "bd93d5747511a4dad4923546c51365bf1a803774_resizePath",
|
||||||
"AssetEntity__AssetEntity_stack"."encodedVideoPath" AS "AssetEntity__AssetEntity_stack_encodedVideoPath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."webpPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_webpPath",
|
||||||
"AssetEntity__AssetEntity_stack"."createdAt" AS "AssetEntity__AssetEntity_stack_createdAt",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."thumbhash" AS "bd93d5747511a4dad4923546c51365bf1a803774_thumbhash",
|
||||||
"AssetEntity__AssetEntity_stack"."updatedAt" AS "AssetEntity__AssetEntity_stack_updatedAt",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."encodedVideoPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_encodedVideoPath",
|
||||||
"AssetEntity__AssetEntity_stack"."deletedAt" AS "AssetEntity__AssetEntity_stack_deletedAt",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."createdAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_createdAt",
|
||||||
"AssetEntity__AssetEntity_stack"."fileCreatedAt" AS "AssetEntity__AssetEntity_stack_fileCreatedAt",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."updatedAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_updatedAt",
|
||||||
"AssetEntity__AssetEntity_stack"."localDateTime" AS "AssetEntity__AssetEntity_stack_localDateTime",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."deletedAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_deletedAt",
|
||||||
"AssetEntity__AssetEntity_stack"."fileModifiedAt" AS "AssetEntity__AssetEntity_stack_fileModifiedAt",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."fileCreatedAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_fileCreatedAt",
|
||||||
"AssetEntity__AssetEntity_stack"."isFavorite" AS "AssetEntity__AssetEntity_stack_isFavorite",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."localDateTime" AS "bd93d5747511a4dad4923546c51365bf1a803774_localDateTime",
|
||||||
"AssetEntity__AssetEntity_stack"."isArchived" AS "AssetEntity__AssetEntity_stack_isArchived",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."fileModifiedAt" AS "bd93d5747511a4dad4923546c51365bf1a803774_fileModifiedAt",
|
||||||
"AssetEntity__AssetEntity_stack"."isExternal" AS "AssetEntity__AssetEntity_stack_isExternal",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite",
|
||||||
"AssetEntity__AssetEntity_stack"."isReadOnly" AS "AssetEntity__AssetEntity_stack_isReadOnly",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived",
|
||||||
"AssetEntity__AssetEntity_stack"."isOffline" AS "AssetEntity__AssetEntity_stack_isOffline",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal",
|
||||||
"AssetEntity__AssetEntity_stack"."checksum" AS "AssetEntity__AssetEntity_stack_checksum",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isReadOnly" AS "bd93d5747511a4dad4923546c51365bf1a803774_isReadOnly",
|
||||||
"AssetEntity__AssetEntity_stack"."duration" AS "AssetEntity__AssetEntity_stack_duration",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
|
||||||
"AssetEntity__AssetEntity_stack"."isVisible" AS "AssetEntity__AssetEntity_stack_isVisible",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum",
|
||||||
"AssetEntity__AssetEntity_stack"."livePhotoVideoId" AS "AssetEntity__AssetEntity_stack_livePhotoVideoId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration",
|
||||||
"AssetEntity__AssetEntity_stack"."originalFileName" AS "AssetEntity__AssetEntity_stack_originalFileName",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isVisible" AS "bd93d5747511a4dad4923546c51365bf1a803774_isVisible",
|
||||||
"AssetEntity__AssetEntity_stack"."sidecarPath" AS "AssetEntity__AssetEntity_stack_sidecarPath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."livePhotoVideoId" AS "bd93d5747511a4dad4923546c51365bf1a803774_livePhotoVideoId",
|
||||||
"AssetEntity__AssetEntity_stack"."stackParentId" AS "AssetEntity__AssetEntity_stack_stackParentId"
|
"bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName",
|
||||||
|
"bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath",
|
||||||
|
"bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||||
|
@ -278,7 +283,8 @@ FROM
|
||||||
LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId"
|
LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId"
|
||||||
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
||||||
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
||||||
LEFT JOIN "assets" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."stackParentId" = "AssetEntity"."id"
|
LEFT JOIN "asset_stack" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."id" = "AssetEntity"."stackId"
|
||||||
|
LEFT JOIN "assets" "bd93d5747511a4dad4923546c51365bf1a803774" ON "bd93d5747511a4dad4923546c51365bf1a803774"."stackId" = "AssetEntity__AssetEntity_stack"."id"
|
||||||
WHERE
|
WHERE
|
||||||
("AssetEntity"."id" IN ($1))
|
("AssetEntity"."id" IN ($1))
|
||||||
|
|
||||||
|
@ -317,7 +323,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
"AssetEntity"."stackId" AS "AssetEntity_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
|
@ -362,7 +368,7 @@ FROM
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
"AssetEntity"."stackId" AS "AssetEntity_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
|
@ -426,7 +432,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
"AssetEntity"."stackId" AS "AssetEntity_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -472,7 +478,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
"AssetEntity"."stackId" AS "AssetEntity_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -516,7 +522,7 @@ SELECT
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
"AssetEntity"."stackId" AS "AssetEntity_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -550,8 +556,9 @@ SELECT
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||||
LEFT JOIN "assets" "stack" ON "stack"."stackParentId" = "asset"."id"
|
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||||
AND ("stack"."deletedAt" IS NULL)
|
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||||
|
AND ("stackedAssets"."deletedAt" IS NULL)
|
||||||
WHERE
|
WHERE
|
||||||
("asset"."isVisible" = true)
|
("asset"."isVisible" = true)
|
||||||
AND ("asset"."deletedAt" IS NULL)
|
AND ("asset"."deletedAt" IS NULL)
|
||||||
|
@ -600,7 +607,7 @@ SELECT
|
||||||
"asset"."livePhotoVideoId" AS "asset_livePhotoVideoId",
|
"asset"."livePhotoVideoId" AS "asset_livePhotoVideoId",
|
||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackParentId" AS "asset_stackParentId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||||
|
@ -615,6 +622,7 @@ SELECT
|
||||||
"exifInfo"."projectionType" AS "exifInfo_projectionType",
|
"exifInfo"."projectionType" AS "exifInfo_projectionType",
|
||||||
"exifInfo"."city" AS "exifInfo_city",
|
"exifInfo"."city" AS "exifInfo_city",
|
||||||
"exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
|
"exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
|
||||||
|
"exifInfo"."autoStackId" AS "exifInfo_autoStackId",
|
||||||
"exifInfo"."state" AS "exifInfo_state",
|
"exifInfo"."state" AS "exifInfo_state",
|
||||||
"exifInfo"."country" AS "exifInfo_country",
|
"exifInfo"."country" AS "exifInfo_country",
|
||||||
"exifInfo"."make" AS "exifInfo_make",
|
"exifInfo"."make" AS "exifInfo_make",
|
||||||
|
@ -629,39 +637,42 @@ SELECT
|
||||||
"exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
|
"exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
|
||||||
"exifInfo"."fps" AS "exifInfo_fps",
|
"exifInfo"."fps" AS "exifInfo_fps",
|
||||||
"stack"."id" AS "stack_id",
|
"stack"."id" AS "stack_id",
|
||||||
"stack"."deviceAssetId" AS "stack_deviceAssetId",
|
"stack"."primaryAssetId" AS "stack_primaryAssetId",
|
||||||
"stack"."ownerId" AS "stack_ownerId",
|
"stackedAssets"."id" AS "stackedAssets_id",
|
||||||
"stack"."libraryId" AS "stack_libraryId",
|
"stackedAssets"."deviceAssetId" AS "stackedAssets_deviceAssetId",
|
||||||
"stack"."deviceId" AS "stack_deviceId",
|
"stackedAssets"."ownerId" AS "stackedAssets_ownerId",
|
||||||
"stack"."type" AS "stack_type",
|
"stackedAssets"."libraryId" AS "stackedAssets_libraryId",
|
||||||
"stack"."originalPath" AS "stack_originalPath",
|
"stackedAssets"."deviceId" AS "stackedAssets_deviceId",
|
||||||
"stack"."resizePath" AS "stack_resizePath",
|
"stackedAssets"."type" AS "stackedAssets_type",
|
||||||
"stack"."webpPath" AS "stack_webpPath",
|
"stackedAssets"."originalPath" AS "stackedAssets_originalPath",
|
||||||
"stack"."thumbhash" AS "stack_thumbhash",
|
"stackedAssets"."resizePath" AS "stackedAssets_resizePath",
|
||||||
"stack"."encodedVideoPath" AS "stack_encodedVideoPath",
|
"stackedAssets"."webpPath" AS "stackedAssets_webpPath",
|
||||||
"stack"."createdAt" AS "stack_createdAt",
|
"stackedAssets"."thumbhash" AS "stackedAssets_thumbhash",
|
||||||
"stack"."updatedAt" AS "stack_updatedAt",
|
"stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath",
|
||||||
"stack"."deletedAt" AS "stack_deletedAt",
|
"stackedAssets"."createdAt" AS "stackedAssets_createdAt",
|
||||||
"stack"."fileCreatedAt" AS "stack_fileCreatedAt",
|
"stackedAssets"."updatedAt" AS "stackedAssets_updatedAt",
|
||||||
"stack"."localDateTime" AS "stack_localDateTime",
|
"stackedAssets"."deletedAt" AS "stackedAssets_deletedAt",
|
||||||
"stack"."fileModifiedAt" AS "stack_fileModifiedAt",
|
"stackedAssets"."fileCreatedAt" AS "stackedAssets_fileCreatedAt",
|
||||||
"stack"."isFavorite" AS "stack_isFavorite",
|
"stackedAssets"."localDateTime" AS "stackedAssets_localDateTime",
|
||||||
"stack"."isArchived" AS "stack_isArchived",
|
"stackedAssets"."fileModifiedAt" AS "stackedAssets_fileModifiedAt",
|
||||||
"stack"."isExternal" AS "stack_isExternal",
|
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||||
"stack"."isReadOnly" AS "stack_isReadOnly",
|
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||||
"stack"."isOffline" AS "stack_isOffline",
|
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||||
"stack"."checksum" AS "stack_checksum",
|
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||||
"stack"."duration" AS "stack_duration",
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stack"."isVisible" AS "stack_isVisible",
|
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||||
"stack"."livePhotoVideoId" AS "stack_livePhotoVideoId",
|
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||||
"stack"."originalFileName" AS "stack_originalFileName",
|
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
||||||
"stack"."sidecarPath" AS "stack_sidecarPath",
|
"stackedAssets"."livePhotoVideoId" AS "stackedAssets_livePhotoVideoId",
|
||||||
"stack"."stackParentId" AS "stack_stackParentId"
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
|
"stackedAssets"."stackId" AS "stackedAssets_stackId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||||
LEFT JOIN "assets" "stack" ON "stack"."stackParentId" = "asset"."id"
|
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||||
AND ("stack"."deletedAt" IS NULL)
|
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||||
|
AND ("stackedAssets"."deletedAt" IS NULL)
|
||||||
WHERE
|
WHERE
|
||||||
(
|
(
|
||||||
"asset"."isVisible" = true
|
"asset"."isVisible" = true
|
||||||
|
|
|
@ -172,7 +172,7 @@ FROM
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."stackParentId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackParentId"
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId"
|
||||||
FROM
|
FROM
|
||||||
"asset_faces" "AssetFaceEntity"
|
"asset_faces" "AssetFaceEntity"
|
||||||
LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
|
LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
|
||||||
|
@ -270,7 +270,7 @@ FROM
|
||||||
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
||||||
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
||||||
"AssetEntity__AssetEntity_faces"."personId" AS "AssetEntity__AssetEntity_faces_personId",
|
"AssetEntity__AssetEntity_faces"."personId" AS "AssetEntity__AssetEntity_faces_personId",
|
||||||
|
@ -303,6 +303,7 @@ FROM
|
||||||
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."autoStackId" AS "AssetEntity__AssetEntity_exifInfo_autoStackId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
@ -376,7 +377,7 @@ SELECT
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."stackParentId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackParentId"
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId"
|
||||||
FROM
|
FROM
|
||||||
"asset_faces" "AssetFaceEntity"
|
"asset_faces" "AssetFaceEntity"
|
||||||
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
||||||
|
|
|
@ -49,7 +49,7 @@ FROM
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."stackParentId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackParentId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageWidth" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageWidth",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageWidth" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageWidth",
|
||||||
|
@ -64,6 +64,7 @@ FROM
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."projectionType" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_projectionType",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."projectionType" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_projectionType",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."city" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_city",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."city" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_city",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."livePhotoCID" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_livePhotoCID",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."livePhotoCID" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_livePhotoCID",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."autoStackId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_autoStackId",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."state" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_state",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."state" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_state",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."country" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_country",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."country" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_country",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."make" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_make",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."make" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_make",
|
||||||
|
@ -114,7 +115,7 @@ FROM
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_livePhotoVideoId",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_livePhotoVideoId",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackParentId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackParentId",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageWidth" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageWidth",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageWidth" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageWidth",
|
||||||
|
@ -129,6 +130,7 @@ FROM
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."projectionType" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_projectionType",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."projectionType" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_projectionType",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."city" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_city",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."city" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_city",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."livePhotoCID" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_livePhotoCID",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."livePhotoCID" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_livePhotoCID",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."autoStackId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_autoStackId",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."state" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_state",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."state" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_state",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."country" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_country",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."country" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_country",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."make" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_make",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."make" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_make",
|
||||||
|
@ -236,7 +238,7 @@ SELECT
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."stackParentId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackParentId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
||||||
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
||||||
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
||||||
"SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName",
|
"SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName",
|
||||||
|
|
|
@ -35,7 +35,7 @@ SELECT
|
||||||
"a"."livePhotoVideoId" AS "a_livePhotoVideoId",
|
"a"."livePhotoVideoId" AS "a_livePhotoVideoId",
|
||||||
"a"."originalFileName" AS "a_originalFileName",
|
"a"."originalFileName" AS "a_originalFileName",
|
||||||
"a"."sidecarPath" AS "a_sidecarPath",
|
"a"."sidecarPath" AS "a_sidecarPath",
|
||||||
"a"."stackParentId" AS "a_stackParentId",
|
"a"."stackId" AS "a_stackId",
|
||||||
"e"."assetId" AS "e_assetId",
|
"e"."assetId" AS "e_assetId",
|
||||||
"e"."description" AS "e_description",
|
"e"."description" AS "e_description",
|
||||||
"e"."exifImageWidth" AS "e_exifImageWidth",
|
"e"."exifImageWidth" AS "e_exifImageWidth",
|
||||||
|
@ -50,6 +50,7 @@ SELECT
|
||||||
"e"."projectionType" AS "e_projectionType",
|
"e"."projectionType" AS "e_projectionType",
|
||||||
"e"."city" AS "e_city",
|
"e"."city" AS "e_city",
|
||||||
"e"."livePhotoCID" AS "e_livePhotoCID",
|
"e"."livePhotoCID" AS "e_livePhotoCID",
|
||||||
|
"e"."autoStackId" AS "e_autoStackId",
|
||||||
"e"."state" AS "e_state",
|
"e"."state" AS "e_state",
|
||||||
"e"."country" AS "e_country",
|
"e"."country" AS "e_country",
|
||||||
"e"."make" AS "e_make",
|
"e"."make" AS "e_make",
|
||||||
|
|
19
server/test/fixtures/asset.stub.ts
vendored
19
server/test/fixtures/asset.stub.ts
vendored
|
@ -1,9 +1,18 @@
|
||||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetStackEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||||
import { authStub } from './auth.stub';
|
import { authStub } from './auth.stub';
|
||||||
import { fileStub } from './file.stub';
|
import { fileStub } from './file.stub';
|
||||||
import { libraryStub } from './library.stub';
|
import { libraryStub } from './library.stub';
|
||||||
import { userStub } from './user.stub';
|
import { userStub } from './user.stub';
|
||||||
|
|
||||||
|
export const assetStackStub = (stackId: string, assets: AssetEntity[]): AssetStackEntity => {
|
||||||
|
return {
|
||||||
|
id: stackId,
|
||||||
|
assets: assets,
|
||||||
|
primaryAsset: assets[0],
|
||||||
|
primaryAssetId: assets[0].id,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const assetStub = {
|
export const assetStub = {
|
||||||
noResizePath: Object.freeze<AssetEntity>({
|
noResizePath: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
|
@ -120,7 +129,7 @@ export const assetStub = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
primaryImage: Object.freeze<AssetEntity>({
|
primaryImage: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'primary-asset-id',
|
||||||
deviceAssetId: 'device-asset-id',
|
deviceAssetId: 'device-asset-id',
|
||||||
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
fileModifiedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
fileCreatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
|
@ -157,7 +166,11 @@ export const assetStub = {
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
fileSizeInByte: 5_000,
|
fileSizeInByte: 5_000,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
stack: [{ id: 'stack-child-asset-1' } as AssetEntity, { id: 'stack-child-asset-2' } as AssetEntity],
|
stack: assetStackStub('stack-1', [
|
||||||
|
{ id: 'primary-asset-id' } as AssetEntity,
|
||||||
|
{ id: 'stack-child-asset-1' } as AssetEntity,
|
||||||
|
{ id: 'stack-child-asset-2' } as AssetEntity,
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
image: Object.freeze<AssetEntity>({
|
image: Object.freeze<AssetEntity>({
|
||||||
|
|
1
server/test/fixtures/shared-link.stub.ts
vendored
1
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -248,6 +248,7 @@ export const sharedLinkStub = {
|
||||||
profileDescription: 'sRGB',
|
profileDescription: 'sRGB',
|
||||||
bitsPerSample: 8,
|
bitsPerSample: 8,
|
||||||
colorspace: 'sRGB',
|
colorspace: 'sRGB',
|
||||||
|
autoStackId: null,
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
sharedLinks: [],
|
sharedLinks: [],
|
||||||
|
|
10
server/test/repositories/asset-stack.repository.mock.ts
Normal file
10
server/test/repositories/asset-stack.repository.mock.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { IAssetStackRepository } from '@app/domain';
|
||||||
|
|
||||||
|
export const newAssetStackRepositoryMock = (): jest.Mocked<IAssetStackRepository> => {
|
||||||
|
return {
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
getById: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
export * from './access.repository.mock';
|
export * from './access.repository.mock';
|
||||||
export * from './album.repository.mock';
|
export * from './album.repository.mock';
|
||||||
export * from './api-key.repository.mock';
|
export * from './api-key.repository.mock';
|
||||||
|
export * from './asset-stack.repository.mock';
|
||||||
export * from './asset.repository.mock';
|
export * from './asset.repository.mock';
|
||||||
export * from './audit.repository.mock';
|
export * from './audit.repository.mock';
|
||||||
export * from './communication.repository.mock';
|
export * from './communication.repository.mock';
|
||||||
|
|
Loading…
Reference in a new issue