mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
fix(server): getAllAssets doesn't return all assets (#7752)
* fix(server): getAllAssets doesn't return all assets * try reverting * fix: archive and remove unused method * update sql * remove unused code * linting
This commit is contained in:
parent
7a4ae7d142
commit
e8fb529026
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { AssetSearchOptions, ReverseGeocodeResult, SearchExploreItem } from '@app/domain';
|
||||||
AssetSearchOneToOneRelationOptions,
|
|
||||||
AssetSearchOptions,
|
|
||||||
ReverseGeocodeResult,
|
|
||||||
SearchExploreItem,
|
|
||||||
} from '@app/domain';
|
|
||||||
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||||
import { FindOptionsRelations, FindOptionsSelect } from 'typeorm';
|
import { FindOptionsRelations, FindOptionsSelect } from 'typeorm';
|
||||||
import { Paginated, PaginationOptions } from '../domain.util';
|
import { Paginated, PaginationOptions } from '../domain.util';
|
||||||
@ -140,10 +135,6 @@ export interface IAssetRepository {
|
|||||||
updateOfflineLibraryAssets(libraryId: string, originalPaths: string[]): Promise<void>;
|
updateOfflineLibraryAssets(libraryId: string, originalPaths: string[]): Promise<void>;
|
||||||
deleteAll(ownerId: string): Promise<void>;
|
deleteAll(ownerId: string): Promise<void>;
|
||||||
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||||
getAllByFileCreationDate(
|
|
||||||
pagination: PaginationOptions,
|
|
||||||
options?: AssetSearchOneToOneRelationOptions,
|
|
||||||
): Paginated<AssetEntity>;
|
|
||||||
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
||||||
updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void>;
|
updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void>;
|
||||||
save(asset: Pick<AssetEntity, 'id'> & Partial<AssetEntity>): Promise<AssetEntity>;
|
save(asset: Pick<AssetEntity, 'id'> & Partial<AssetEntity>): Promise<AssetEntity>;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { AssetEntity } from '@app/infra/entities';
|
import { AssetEntity, ExifEntity } from '@app/infra/entities';
|
||||||
|
import { OptionalBetween } from '@app/infra/infra.utils';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { In } from 'typeorm/find-options/operator/In.js';
|
import { In } from 'typeorm/find-options/operator/In.js';
|
||||||
import { Repository } from 'typeorm/repository/Repository.js';
|
import { Repository } from 'typeorm/repository/Repository.js';
|
||||||
|
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||||
import { SearchPropertiesDto } from './dto/search-properties.dto';
|
import { SearchPropertiesDto } from './dto/search-properties.dto';
|
||||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
|
|
||||||
export interface AssetCheck {
|
export interface AssetCheck {
|
||||||
id: string;
|
id: string;
|
||||||
checksum: Buffer;
|
checksum: Buffer;
|
||||||
@ -21,6 +22,7 @@ export interface IAssetRepositoryV1 {
|
|||||||
get(id: string): Promise<AssetEntity | null>;
|
get(id: string): Promise<AssetEntity | null>;
|
||||||
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
|
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
|
||||||
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
||||||
|
getAllByUserId(userId: string, dto: AssetSearchDto): Promise<AssetEntity[]>;
|
||||||
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
||||||
getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise<AssetCheck[]>;
|
getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise<AssetCheck[]>;
|
||||||
getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<string[]>;
|
getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<string[]>;
|
||||||
@ -31,7 +33,40 @@ export const IAssetRepositoryV1 = 'IAssetRepositoryV1';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetRepositoryV1 implements IAssetRepositoryV1 {
|
export class AssetRepositoryV1 implements IAssetRepositoryV1 {
|
||||||
constructor(@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>) {}
|
constructor(
|
||||||
|
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||||
|
@InjectRepository(ExifEntity) private exifRepository: Repository<ExifEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all assets by user ID.
|
||||||
|
*
|
||||||
|
* @param ownerId - The ID of the owner.
|
||||||
|
* @param dto - The AssetSearchDto object containing search criteria.
|
||||||
|
* @returns A Promise that resolves to an array of AssetEntity objects.
|
||||||
|
*/
|
||||||
|
getAllByUserId(ownerId: string, dto: AssetSearchDto): Promise<AssetEntity[]> {
|
||||||
|
return this.assetRepository.find({
|
||||||
|
where: {
|
||||||
|
ownerId,
|
||||||
|
isVisible: true,
|
||||||
|
isFavorite: dto.isFavorite,
|
||||||
|
isArchived: dto.isArchived,
|
||||||
|
updatedAt: OptionalBetween(dto.updatedAfter, dto.updatedBefore),
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
exifInfo: true,
|
||||||
|
tags: true,
|
||||||
|
stack: { assets: true },
|
||||||
|
},
|
||||||
|
skip: dto.skip || 0,
|
||||||
|
take: dto.take,
|
||||||
|
order: {
|
||||||
|
fileCreatedAt: 'DESC',
|
||||||
|
},
|
||||||
|
withDeleted: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
|
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
|
||||||
return this.assetRepository
|
return this.assetRepository
|
||||||
|
@ -77,6 +77,7 @@ describe('AssetService', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
assetRepositoryMockV1 = {
|
assetRepositoryMockV1 = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
getAllByUserId: jest.fn(),
|
||||||
getDetectedObjectsByUserId: jest.fn(),
|
getDetectedObjectsByUserId: jest.fn(),
|
||||||
getLocationsByUserId: jest.fn(),
|
getLocationsByUserId: jest.fn(),
|
||||||
getSearchPropertiesByUserId: jest.fn(),
|
getSearchPropertiesByUserId: jest.fn(),
|
||||||
|
@ -113,19 +113,8 @@ export class AssetService {
|
|||||||
public async getAllAssets(auth: AuthDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
public async getAllAssets(auth: AuthDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||||
const userId = dto.userId || auth.user.id;
|
const userId = dto.userId || auth.user.id;
|
||||||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
||||||
const assets = await this.assetRepository.getAllByFileCreationDate(
|
const assets = await this.assetRepositoryV1.getAllByUserId(userId, dto);
|
||||||
{ take: dto.take ?? 1000, skip: dto.skip },
|
return assets.map((asset) => mapAsset(asset, { withStack: true, auth }));
|
||||||
{
|
|
||||||
...dto,
|
|
||||||
userIds: [userId],
|
|
||||||
withDeleted: true,
|
|
||||||
orderDirection: 'DESC',
|
|
||||||
withExif: true,
|
|
||||||
isVisible: true,
|
|
||||||
withStacked: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return assets.items.map((asset) => mapAsset(asset, { withStack: true }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async serveThumbnail(auth: AuthDto, assetId: string, dto: GetAssetThumbnailDto): Promise<ImmichFileResponse> {
|
async serveThumbnail(auth: AuthDto, assetId: string, dto: GetAssetThumbnailDto): Promise<ImmichFileResponse> {
|
||||||
|
@ -2,7 +2,6 @@ import {
|
|||||||
AssetBuilderOptions,
|
AssetBuilderOptions,
|
||||||
AssetCreate,
|
AssetCreate,
|
||||||
AssetExploreFieldOptions,
|
AssetExploreFieldOptions,
|
||||||
AssetSearchOneToOneRelationOptions,
|
|
||||||
AssetSearchOptions,
|
AssetSearchOptions,
|
||||||
AssetStats,
|
AssetStats,
|
||||||
AssetStatsOptions,
|
AssetStatsOptions,
|
||||||
@ -233,29 +232,6 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({
|
|
||||||
params: [
|
|
||||||
{ skip: 20_000, take: 10_000 },
|
|
||||||
{
|
|
||||||
takenBefore: DummyValue.DATE,
|
|
||||||
userIds: [DummyValue.UUID],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
getAllByFileCreationDate(
|
|
||||||
pagination: PaginationOptions,
|
|
||||||
options: AssetSearchOneToOneRelationOptions = {},
|
|
||||||
): Paginated<AssetEntity> {
|
|
||||||
let builder = this.repository.createQueryBuilder('asset');
|
|
||||||
builder = searchAssetBuilder(builder, options);
|
|
||||||
builder.orderBy('asset.fileCreatedAt', options.orderDirection ?? 'DESC');
|
|
||||||
return paginatedBuilder<AssetEntity>(builder, {
|
|
||||||
mode: PaginationMode.LIMIT_OFFSET,
|
|
||||||
skip: pagination.skip,
|
|
||||||
take: pagination.take,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get assets by device's Id on the database
|
* Get assets by device's Id on the database
|
||||||
* @param ownerId
|
* @param ownerId
|
||||||
|
@ -428,55 +428,6 @@ WHERE
|
|||||||
AND "isOffline" = $4
|
AND "isOffline" = $4
|
||||||
)
|
)
|
||||||
|
|
||||||
-- AssetRepository.getAllByFileCreationDate
|
|
||||||
SELECT
|
|
||||||
"asset"."id" AS "asset_id",
|
|
||||||
"asset"."deviceAssetId" AS "asset_deviceAssetId",
|
|
||||||
"asset"."ownerId" AS "asset_ownerId",
|
|
||||||
"asset"."libraryId" AS "asset_libraryId",
|
|
||||||
"asset"."deviceId" AS "asset_deviceId",
|
|
||||||
"asset"."type" AS "asset_type",
|
|
||||||
"asset"."originalPath" AS "asset_originalPath",
|
|
||||||
"asset"."resizePath" AS "asset_resizePath",
|
|
||||||
"asset"."webpPath" AS "asset_webpPath",
|
|
||||||
"asset"."thumbhash" AS "asset_thumbhash",
|
|
||||||
"asset"."encodedVideoPath" AS "asset_encodedVideoPath",
|
|
||||||
"asset"."createdAt" AS "asset_createdAt",
|
|
||||||
"asset"."updatedAt" AS "asset_updatedAt",
|
|
||||||
"asset"."deletedAt" AS "asset_deletedAt",
|
|
||||||
"asset"."fileCreatedAt" AS "asset_fileCreatedAt",
|
|
||||||
"asset"."localDateTime" AS "asset_localDateTime",
|
|
||||||
"asset"."fileModifiedAt" AS "asset_fileModifiedAt",
|
|
||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
|
||||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
|
||||||
"asset"."duration" AS "asset_duration",
|
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
|
||||||
"asset"."livePhotoVideoId" AS "asset_livePhotoVideoId",
|
|
||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
|
||||||
"asset"."stackId" AS "asset_stackId"
|
|
||||||
FROM
|
|
||||||
"assets" "asset"
|
|
||||||
WHERE
|
|
||||||
(
|
|
||||||
"asset"."fileCreatedAt" <= $1
|
|
||||||
AND 1 = 1
|
|
||||||
AND "asset"."ownerId" IN ($2)
|
|
||||||
AND 1 = 1
|
|
||||||
AND "asset"."isArchived" = $3
|
|
||||||
)
|
|
||||||
AND ("asset"."deletedAt" IS NULL)
|
|
||||||
ORDER BY
|
|
||||||
"asset"."fileCreatedAt" DESC
|
|
||||||
LIMIT
|
|
||||||
10001
|
|
||||||
OFFSET
|
|
||||||
20000
|
|
||||||
|
|
||||||
-- AssetRepository.getAllByDeviceId
|
-- AssetRepository.getAllByDeviceId
|
||||||
SELECT
|
SELECT
|
||||||
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
@ -18,7 +18,6 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
|
|||||||
getFirstAssetForAlbumId: jest.fn(),
|
getFirstAssetForAlbumId: jest.fn(),
|
||||||
getLastUpdatedAssetForAlbumId: jest.fn(),
|
getLastUpdatedAssetForAlbumId: jest.fn(),
|
||||||
getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
|
getAll: jest.fn().mockResolvedValue({ items: [], hasNextPage: false }),
|
||||||
getAllByFileCreationDate: jest.fn(),
|
|
||||||
getAllByDeviceId: jest.fn(),
|
getAllByDeviceId: jest.fn(),
|
||||||
updateAll: jest.fn(),
|
updateAll: jest.fn(),
|
||||||
getByLibraryId: jest.fn(),
|
getByLibraryId: jest.fn(),
|
||||||
|
Loading…
Reference in New Issue
Block a user