1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00:00

feat(server): asset search endpoint (#4931)

* feat(server): GET /assets endpoint

* chore: open api

* chore: use dumb name

* feat: search by make, model, lens, city, state, country

* chore: open api

* chore: pagination validation and tests

* chore: pr feedback
This commit is contained in:
Jason Rasmussen 2023-11-14 17:47:15 -05:00 committed by GitHub
parent 7a8f8e5472
commit 753dab8b3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2557 additions and 94 deletions

View file

@ -670,6 +670,20 @@ export interface AssetJobsDto {
}
/**
*
* @export
* @enum {string}
*/
export const AssetOrder = {
Asc: 'asc',
Desc: 'desc'
} as const;
export type AssetOrder = typeof AssetOrder[keyof typeof AssetOrder];
/**
*
* @export
@ -7822,6 +7836,260 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions,
};
},
/**
*
* @param {string} [id]
* @param {string} [libraryId]
* @param {AssetTypeEnum} [type]
* @param {AssetOrder} [order]
* @param {string} [deviceAssetId]
* @param {string} [deviceId]
* @param {string} [checksum]
* @param {boolean} [isArchived]
* @param {boolean} [isEncoded]
* @param {boolean} [isExternal]
* @param {boolean} [isFavorite]
* @param {boolean} [isMotion]
* @param {boolean} [isOffline]
* @param {boolean} [isReadOnly]
* @param {boolean} [isVisible]
* @param {boolean} [withDeleted]
* @param {boolean} [withStacked]
* @param {boolean} [withExif]
* @param {boolean} [withPeople]
* @param {string} [createdBefore]
* @param {string} [createdAfter]
* @param {string} [updatedBefore]
* @param {string} [updatedAfter]
* @param {string} [trashedBefore]
* @param {string} [trashedAfter]
* @param {string} [takenBefore]
* @param {string} [takenAfter]
* @param {string} [originalFileName]
* @param {string} [originalPath]
* @param {string} [resizePath]
* @param {string} [webpPath]
* @param {string} [encodedVideoPath]
* @param {string} [city]
* @param {string} [state]
* @param {string} [country]
* @param {string} [make]
* @param {string} [model]
* @param {string} [lensModel]
* @param {number} [page]
* @param {number} [size]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
searchAssets: async (id?: string, libraryId?: string, type?: AssetTypeEnum, order?: AssetOrder, deviceAssetId?: string, deviceId?: string, checksum?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, withDeleted?: boolean, withStacked?: boolean, withExif?: boolean, withPeople?: boolean, createdBefore?: string, createdAfter?: string, updatedBefore?: string, updatedAfter?: string, trashedBefore?: string, trashedAfter?: string, takenBefore?: string, takenAfter?: string, originalFileName?: string, originalPath?: string, resizePath?: string, webpPath?: string, encodedVideoPath?: string, city?: string, state?: string, country?: string, make?: string, model?: string, lensModel?: string, page?: number, size?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/assets`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
if (libraryId !== undefined) {
localVarQueryParameter['libraryId'] = libraryId;
}
if (type !== undefined) {
localVarQueryParameter['type'] = type;
}
if (order !== undefined) {
localVarQueryParameter['order'] = order;
}
if (deviceAssetId !== undefined) {
localVarQueryParameter['deviceAssetId'] = deviceAssetId;
}
if (deviceId !== undefined) {
localVarQueryParameter['deviceId'] = deviceId;
}
if (checksum !== undefined) {
localVarQueryParameter['checksum'] = checksum;
}
if (isArchived !== undefined) {
localVarQueryParameter['isArchived'] = isArchived;
}
if (isEncoded !== undefined) {
localVarQueryParameter['isEncoded'] = isEncoded;
}
if (isExternal !== undefined) {
localVarQueryParameter['isExternal'] = isExternal;
}
if (isFavorite !== undefined) {
localVarQueryParameter['isFavorite'] = isFavorite;
}
if (isMotion !== undefined) {
localVarQueryParameter['isMotion'] = isMotion;
}
if (isOffline !== undefined) {
localVarQueryParameter['isOffline'] = isOffline;
}
if (isReadOnly !== undefined) {
localVarQueryParameter['isReadOnly'] = isReadOnly;
}
if (isVisible !== undefined) {
localVarQueryParameter['isVisible'] = isVisible;
}
if (withDeleted !== undefined) {
localVarQueryParameter['withDeleted'] = withDeleted;
}
if (withStacked !== undefined) {
localVarQueryParameter['withStacked'] = withStacked;
}
if (withExif !== undefined) {
localVarQueryParameter['withExif'] = withExif;
}
if (withPeople !== undefined) {
localVarQueryParameter['withPeople'] = withPeople;
}
if (createdBefore !== undefined) {
localVarQueryParameter['createdBefore'] = (createdBefore as any instanceof Date) ?
(createdBefore as any).toISOString() :
createdBefore;
}
if (createdAfter !== undefined) {
localVarQueryParameter['createdAfter'] = (createdAfter as any instanceof Date) ?
(createdAfter as any).toISOString() :
createdAfter;
}
if (updatedBefore !== undefined) {
localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ?
(updatedBefore as any).toISOString() :
updatedBefore;
}
if (updatedAfter !== undefined) {
localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ?
(updatedAfter as any).toISOString() :
updatedAfter;
}
if (trashedBefore !== undefined) {
localVarQueryParameter['trashedBefore'] = (trashedBefore as any instanceof Date) ?
(trashedBefore as any).toISOString() :
trashedBefore;
}
if (trashedAfter !== undefined) {
localVarQueryParameter['trashedAfter'] = (trashedAfter as any instanceof Date) ?
(trashedAfter as any).toISOString() :
trashedAfter;
}
if (takenBefore !== undefined) {
localVarQueryParameter['takenBefore'] = (takenBefore as any instanceof Date) ?
(takenBefore as any).toISOString() :
takenBefore;
}
if (takenAfter !== undefined) {
localVarQueryParameter['takenAfter'] = (takenAfter as any instanceof Date) ?
(takenAfter as any).toISOString() :
takenAfter;
}
if (originalFileName !== undefined) {
localVarQueryParameter['originalFileName'] = originalFileName;
}
if (originalPath !== undefined) {
localVarQueryParameter['originalPath'] = originalPath;
}
if (resizePath !== undefined) {
localVarQueryParameter['resizePath'] = resizePath;
}
if (webpPath !== undefined) {
localVarQueryParameter['webpPath'] = webpPath;
}
if (encodedVideoPath !== undefined) {
localVarQueryParameter['encodedVideoPath'] = encodedVideoPath;
}
if (city !== undefined) {
localVarQueryParameter['city'] = city;
}
if (state !== undefined) {
localVarQueryParameter['state'] = state;
}
if (country !== undefined) {
localVarQueryParameter['country'] = country;
}
if (make !== undefined) {
localVarQueryParameter['make'] = make;
}
if (model !== undefined) {
localVarQueryParameter['model'] = model;
}
if (lensModel !== undefined) {
localVarQueryParameter['lensModel'] = lensModel;
}
if (page !== undefined) {
localVarQueryParameter['page'] = page;
}
if (size !== undefined) {
localVarQueryParameter['size'] = size;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} id
@ -8440,6 +8708,55 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.searchAsset(searchAssetDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {string} [id]
* @param {string} [libraryId]
* @param {AssetTypeEnum} [type]
* @param {AssetOrder} [order]
* @param {string} [deviceAssetId]
* @param {string} [deviceId]
* @param {string} [checksum]
* @param {boolean} [isArchived]
* @param {boolean} [isEncoded]
* @param {boolean} [isExternal]
* @param {boolean} [isFavorite]
* @param {boolean} [isMotion]
* @param {boolean} [isOffline]
* @param {boolean} [isReadOnly]
* @param {boolean} [isVisible]
* @param {boolean} [withDeleted]
* @param {boolean} [withStacked]
* @param {boolean} [withExif]
* @param {boolean} [withPeople]
* @param {string} [createdBefore]
* @param {string} [createdAfter]
* @param {string} [updatedBefore]
* @param {string} [updatedAfter]
* @param {string} [trashedBefore]
* @param {string} [trashedAfter]
* @param {string} [takenBefore]
* @param {string} [takenAfter]
* @param {string} [originalFileName]
* @param {string} [originalPath]
* @param {string} [resizePath]
* @param {string} [webpPath]
* @param {string} [encodedVideoPath]
* @param {string} [city]
* @param {string} [state]
* @param {string} [country]
* @param {string} [make]
* @param {string} [model]
* @param {string} [lensModel]
* @param {number} [page]
* @param {number} [size]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async searchAssets(id?: string, libraryId?: string, type?: AssetTypeEnum, order?: AssetOrder, deviceAssetId?: string, deviceId?: string, checksum?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, withDeleted?: boolean, withStacked?: boolean, withExif?: boolean, withPeople?: boolean, createdBefore?: string, createdAfter?: string, updatedBefore?: string, updatedAfter?: string, trashedBefore?: string, trashedAfter?: string, takenBefore?: string, takenAfter?: string, originalFileName?: string, originalPath?: string, resizePath?: string, webpPath?: string, encodedVideoPath?: string, city?: string, state?: string, country?: string, make?: string, model?: string, lensModel?: string, page?: number, size?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.searchAssets(id, libraryId, type, order, deviceAssetId, deviceId, checksum, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, withDeleted, withStacked, withExif, withPeople, createdBefore, createdAfter, updatedBefore, updatedAfter, trashedBefore, trashedAfter, takenBefore, takenAfter, originalFileName, originalPath, resizePath, webpPath, encodedVideoPath, city, state, country, make, model, lensModel, page, size, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {string} id
@ -8739,6 +9056,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
searchAsset(requestParameters: AssetApiSearchAssetRequest, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.searchAsset(requestParameters.searchAssetDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiSearchAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.searchAssets(requestParameters.id, requestParameters.libraryId, requestParameters.type, requestParameters.order, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.checksum, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.withDeleted, requestParameters.withStacked, requestParameters.withExif, requestParameters.withPeople, requestParameters.createdBefore, requestParameters.createdAfter, requestParameters.updatedBefore, requestParameters.updatedAfter, requestParameters.trashedBefore, requestParameters.trashedAfter, requestParameters.takenBefore, requestParameters.takenAfter, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.resizePath, requestParameters.webpPath, requestParameters.encodedVideoPath, requestParameters.city, requestParameters.state, requestParameters.country, requestParameters.make, requestParameters.model, requestParameters.lensModel, requestParameters.page, requestParameters.size, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiServeFileRequest} requestParameters Request parameters.
@ -9333,6 +9659,293 @@ export interface AssetApiSearchAssetRequest {
readonly searchAssetDto: SearchAssetDto
}
/**
* Request parameters for searchAssets operation in AssetApi.
* @export
* @interface AssetApiSearchAssetsRequest
*/
export interface AssetApiSearchAssetsRequest {
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly id?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly libraryId?: string
/**
*
* @type {AssetTypeEnum}
* @memberof AssetApiSearchAssets
*/
readonly type?: AssetTypeEnum
/**
*
* @type {AssetOrder}
* @memberof AssetApiSearchAssets
*/
readonly order?: AssetOrder
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly deviceAssetId?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly deviceId?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly checksum?: string
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isArchived?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isEncoded?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isExternal?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isFavorite?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isMotion?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isOffline?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isReadOnly?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isVisible?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withDeleted?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withStacked?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withExif?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withPeople?: boolean
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly createdBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly createdAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly updatedBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly updatedAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly trashedBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly trashedAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly takenBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly takenAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly originalFileName?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly originalPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly resizePath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly webpPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly encodedVideoPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly city?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly state?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly country?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly make?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly model?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly lensModel?: string
/**
*
* @type {number}
* @memberof AssetApiSearchAssets
*/
readonly page?: number
/**
*
* @type {number}
* @memberof AssetApiSearchAssets
*/
readonly size?: number
}
/**
* Request parameters for serveFile operation in AssetApi.
* @export
@ -9813,6 +10426,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).searchAsset(requestParameters.searchAssetDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiSearchAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).searchAssets(requestParameters.id, requestParameters.libraryId, requestParameters.type, requestParameters.order, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.checksum, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.withDeleted, requestParameters.withStacked, requestParameters.withExif, requestParameters.withPeople, requestParameters.createdBefore, requestParameters.createdAfter, requestParameters.updatedBefore, requestParameters.updatedAfter, requestParameters.trashedBefore, requestParameters.trashedAfter, requestParameters.takenBefore, requestParameters.takenAfter, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.resizePath, requestParameters.webpPath, requestParameters.encodedVideoPath, requestParameters.city, requestParameters.state, requestParameters.country, requestParameters.make, requestParameters.model, requestParameters.lensModel, requestParameters.page, requestParameters.size, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiServeFileRequest} requestParameters Request parameters.

View file

@ -29,6 +29,7 @@ doc/AssetIdsDto.md
doc/AssetIdsResponseDto.md
doc/AssetJobName.md
doc/AssetJobsDto.md
doc/AssetOrder.md
doc/AssetResponseDto.md
doc/AssetStatsResponseDto.md
doc/AssetTypeEnum.md
@ -220,6 +221,7 @@ lib/model/asset_ids_dto.dart
lib/model/asset_ids_response_dto.dart
lib/model/asset_job_name.dart
lib/model/asset_jobs_dto.dart
lib/model/asset_order.dart
lib/model/asset_response_dto.dart
lib/model/asset_stats_response_dto.dart
lib/model/asset_type_enum.dart
@ -376,6 +378,7 @@ test/asset_ids_dto_test.dart
test/asset_ids_response_dto_test.dart
test/asset_job_name_test.dart
test/asset_jobs_dto_test.dart
test/asset_order_test.dart
test/asset_response_dto_test.dart
test/asset_stats_response_dto_test.dart
test/asset_type_enum_test.dart

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/doc/AssetOrder.md generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/lib/model/asset_order.dart generated Normal file

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/test/asset_order_test.dart generated Normal file

Binary file not shown.

View file

@ -2464,6 +2464,372 @@
]
}
},
"/assets": {
"get": {
"operationId": "searchAssets",
"parameters": [
{
"name": "id",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "libraryId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "type",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetTypeEnum"
}
},
{
"name": "order",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/AssetOrder"
}
},
{
"name": "deviceAssetId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "deviceId",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "checksum",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "isArchived",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isEncoded",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isExternal",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isFavorite",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isMotion",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isOffline",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isReadOnly",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "isVisible",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "withDeleted",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "withStacked",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "withExif",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "withPeople",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "createdBefore",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "createdAfter",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "updatedBefore",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "updatedAfter",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "trashedBefore",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "trashedAfter",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "takenBefore",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "takenAfter",
"required": false,
"in": "query",
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"name": "originalFileName",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "originalPath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "resizePath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "webpPath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "encodedVideoPath",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "city",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "state",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "country",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "make",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "model",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "lensModel",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
},
{
"name": "size",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AssetResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Asset"
]
}
},
"/audit/deletes": {
"get": {
"operationId": "getAuditDeletes",
@ -6304,6 +6670,13 @@
],
"type": "object"
},
"AssetOrder": {
"enum": [
"asc",
"desc"
],
"type": "string"
},
"AssetResponseDto": {
"properties": {
"checksum": {

View file

@ -30,6 +30,8 @@ import {
AssetIdsDto,
AssetJobName,
AssetJobsDto,
AssetOrder,
AssetSearchDto,
AssetStatsDto,
DownloadArchiveInfo,
DownloadInfoDto,
@ -91,6 +93,34 @@ export class AssetService {
this.configCore = SystemConfigCore.create(configRepository);
}
search(authUser: AuthUserDto, dto: AssetSearchDto) {
let checksum: Buffer | undefined = undefined;
if (dto.checksum) {
const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
checksum = Buffer.from(dto.checksum, encoding);
}
const enumToOrder = { [AssetOrder.ASC]: 'ASC', [AssetOrder.DESC]: 'DESC' } as const;
const order = dto.order ? enumToOrder[dto.order] : undefined;
return this.assetRepository
.search({
...dto,
order,
checksum,
ownerId: authUser.id,
})
.then((assets) =>
assets.map((asset) =>
mapAsset(asset, {
stripMetadata: false,
withStack: true,
}),
),
);
}
canUploadFile({ authUser, fieldName, file }: UploadRequest): true {
this.access.requireUploadAccess(authUser);

View file

@ -1,8 +1,161 @@
import { AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsInt, IsPositive, IsString } from 'class-validator';
import { Optional, ValidateUUID } from '../../domain.util';
import { IsBoolean, IsEnum, IsInt, IsPositive, IsString, Min } from 'class-validator';
import { Optional, QueryBoolean, QueryDate, ValidateUUID } from '../../domain.util';
import { BulkIdsDto } from '../response-dto';
export enum AssetOrder {
ASC = 'asc',
DESC = 'desc',
}
export class AssetSearchDto {
@ValidateUUID({ optional: true })
id?: string;
@ValidateUUID({ optional: true })
libraryId?: string;
@IsString()
@Optional()
deviceAssetId?: string;
@IsString()
@Optional()
deviceId?: string;
@IsEnum(AssetType)
@Optional()
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type?: AssetType;
@IsString()
@Optional()
checksum?: string;
@QueryBoolean({ optional: true })
isArchived?: boolean;
@QueryBoolean({ optional: true })
isEncoded?: boolean;
@QueryBoolean({ optional: true })
isExternal?: boolean;
@QueryBoolean({ optional: true })
isFavorite?: boolean;
@QueryBoolean({ optional: true })
isMotion?: boolean;
@QueryBoolean({ optional: true })
isOffline?: boolean;
@QueryBoolean({ optional: true })
isReadOnly?: boolean;
@QueryBoolean({ optional: true })
isVisible?: boolean;
@QueryBoolean({ optional: true })
withDeleted?: boolean;
@QueryBoolean({ optional: true })
withStacked?: boolean;
@QueryBoolean({ optional: true })
withExif?: boolean;
@QueryBoolean({ optional: true })
withPeople?: boolean;
@QueryDate({ optional: true })
createdBefore?: Date;
@QueryDate({ optional: true })
createdAfter?: Date;
@QueryDate({ optional: true })
updatedBefore?: Date;
@QueryDate({ optional: true })
updatedAfter?: Date;
@QueryDate({ optional: true })
trashedBefore?: Date;
@QueryDate({ optional: true })
trashedAfter?: Date;
@QueryDate({ optional: true })
takenBefore?: Date;
@QueryDate({ optional: true })
takenAfter?: Date;
@IsString()
@Optional()
originalFileName?: string;
@IsString()
@Optional()
originalPath?: string;
@IsString()
@Optional()
resizePath?: string;
@IsString()
@Optional()
webpPath?: string;
@IsString()
@Optional()
encodedVideoPath?: string;
@IsString()
@Optional()
city?: string;
@IsString()
@Optional()
state?: string;
@IsString()
@Optional()
country?: string;
@IsString()
@Optional()
make?: string;
@IsString()
@Optional()
model?: string;
@IsString()
@Optional()
lensModel?: string;
@IsEnum(AssetOrder)
@Optional()
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
order?: AssetOrder;
@IsInt()
@Min(1)
@Type(() => Number)
@Optional()
page?: number;
@IsInt()
@Min(1)
@Type(() => Number)
@Optional()
size?: number;
}
export class AssetBulkUpdateDto extends BulkIdsDto {
@Optional()
@IsBoolean()

View file

@ -1,6 +1,17 @@
import { applyDecorators } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsNotEmpty, IsOptional, IsString, IsUUID, ValidateIf, ValidationOptions } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDate,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
ValidateIf,
ValidationOptions,
} from 'class-validator';
import { CronJob } from 'cron';
import { basename, extname } from 'node:path';
import sanitize from 'sanitize-filename';
@ -33,6 +44,22 @@ interface IValue {
value?: string;
}
export const QueryBoolean = ({ optional }: { optional?: boolean }) => {
const decorators = [IsBoolean(), Transform(toBoolean)];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
export const QueryDate = ({ optional }: { optional?: boolean }) => {
const decorators = [IsDate(), Type(() => Date)];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
export const toBoolean = ({ value }: IValue) => {
if (value == 'true') {
return true;

View file

@ -11,11 +11,58 @@ export interface AssetStatsOptions {
}
export interface AssetSearchOptions {
isVisible?: boolean;
trashedBefore?: Date;
id?: string;
libraryId?: string;
deviceAssetId?: string;
deviceId?: string;
ownerId?: string;
type?: AssetType;
order?: 'ASC' | 'DESC';
checksum?: Buffer;
isArchived?: boolean;
isEncoded?: boolean;
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
withDeleted?: boolean;
withStacked?: boolean;
withExif?: boolean;
withPeople?: boolean;
createdBefore?: Date;
createdAfter?: Date;
updatedBefore?: Date;
updatedAfter?: Date;
trashedBefore?: Date;
trashedAfter?: Date;
takenBefore?: Date;
takenAfter?: Date;
originalFileName?: string;
originalPath?: string;
resizePath?: string;
webpPath?: string;
encodedVideoPath?: string;
city?: string;
state?: string;
country?: string;
make?: string;
model?: string;
lensModel?: string;
/** defaults to 'DESC' */
order?: 'ASC' | 'DESC';
/** defaults to 1 */
page?: number;
/** defaults to 250 */
size?: number;
}
export interface LivePhotoSearchOptions {
@ -127,4 +174,5 @@ export interface IAssetRepository {
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
upsertJobStatus(jobStatus: Partial<AssetJobStatusEntity>): Promise<void>;
search(options: AssetSearchOptions): Promise<AssetEntity[]>;
}

View file

@ -16,6 +16,7 @@ import {
AlbumController,
AppController,
AssetController,
AssetsController,
AuditController,
AuthController,
JobController,
@ -41,6 +42,7 @@ import { ErrorInterceptor, FileUploadInterceptor } from './interceptors';
],
controllers: [
ActivityController,
AssetsController,
AssetController,
AssetControllerV1,
AppController,

View file

@ -4,6 +4,7 @@ import {
AssetIdsDto,
AssetJobsDto,
AssetResponseDto,
AssetSearchDto,
AssetService,
AssetStatsDto,
AssetStatsResponseDto,
@ -42,6 +43,19 @@ import { UseValidation, asStreamableFile } from '../app.utils';
import { Route } from '../interceptors';
import { UUIDParamDto } from './dto/uuid-param.dto';
@ApiTags('Asset')
@Controller('assets')
@Authenticated()
@UseValidation()
export class AssetsController {
constructor(private service: AssetService) {}
@Get()
searchAssets(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> {
return this.service.search(authUser, dto);
}
}
@ApiTags('Asset')
@Controller(Route.ASSET)
@Authenticated()

View file

@ -18,12 +18,15 @@ import {
} from '@app/domain';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import _ from 'lodash';
import { DateTime } from 'luxon';
import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm';
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities';
import OptionalBetween from '../utils/optional-between.util';
import { paginate } from '../utils/pagination.util';
const DEFAULT_SEARCH_SIZE = 250;
const truncateMap: Record<TimeBucketSize, string> = {
[TimeBucketSize.DAY]: 'day',
[TimeBucketSize.MONTH]: 'month',
@ -50,6 +53,134 @@ export class AssetRepository implements IAssetRepository {
await this.jobStatusRepository.upsert(jobStatus, { conflictPaths: ['assetId'] });
}
search(options: AssetSearchOptions): Promise<AssetEntity[]> {
const {
id,
libraryId,
deviceAssetId,
type,
checksum,
ownerId,
isVisible,
isFavorite,
isExternal,
isReadOnly,
isOffline,
isArchived,
isMotion,
isEncoded,
createdBefore,
createdAfter,
updatedBefore,
updatedAfter,
trashedBefore,
trashedAfter,
takenBefore,
takenAfter,
originalFileName,
originalPath,
resizePath,
webpPath,
encodedVideoPath,
city,
state,
country,
make,
model,
lensModel,
withDeleted: _withDeleted,
withExif: _withExif,
withStacked,
withPeople,
order,
} = options;
const withDeleted = _withDeleted ?? (trashedAfter !== undefined || trashedBefore !== undefined);
const page = Math.max(options.page || 1, 1);
const size = Math.min(options.size || DEFAULT_SEARCH_SIZE, DEFAULT_SEARCH_SIZE);
const exifWhere = _.omitBy(
{
city,
state,
country,
make,
model,
lensModel,
},
_.isUndefined,
);
const withExif = Object.keys(exifWhere).length > 0 || _withExif;
const where = _.omitBy(
{
ownerId,
id,
libraryId,
deviceAssetId,
type,
checksum,
isVisible,
isFavorite,
isExternal,
isReadOnly,
isOffline,
isArchived,
livePhotoVideoId: isMotion && Not(IsNull()),
originalFileName,
originalPath,
resizePath,
webpPath,
encodedVideoPath: encodedVideoPath ?? (isEncoded && Not(IsNull())),
createdAt: OptionalBetween(createdAfter, createdBefore),
updatedAt: OptionalBetween(updatedAfter, updatedBefore),
deletedAt: OptionalBetween(trashedAfter, trashedBefore),
fileCreatedAt: OptionalBetween(takenAfter, takenBefore),
exifInfo: Object.keys(exifWhere).length > 0 ? exifWhere : undefined,
},
_.isUndefined,
);
const builder = this.repository.createQueryBuilder('asset');
if (withExif) {
if (_withExif) {
builder.leftJoinAndSelect('asset.exifInfo', 'exifInfo');
} else {
builder.leftJoin('asset.exifInfo', 'exifInfo');
}
}
if (withPeople) {
builder.leftJoinAndSelect('asset.faces', 'faces');
builder.leftJoinAndSelect('faces.person', 'person');
}
if (withStacked) {
builder.leftJoinAndSelect('asset.stack', 'stack');
}
if (withDeleted) {
builder.withDeleted();
}
builder
.where(where)
.skip(size * (page - 1))
.take(size)
.orderBy('asset.fileCreatedAt', order ?? 'DESC');
return builder.getMany();
}
create(asset: AssetCreate): Promise<AssetEntity> {
return this.repository.save(asset);
}

View file

@ -4,19 +4,20 @@ import {
IPersonRepository,
LibraryResponseDto,
LoginResponseDto,
SharedLinkResponseDto,
TimeBucketSize,
WithoutProperty,
mapAsset,
usePagination,
} from '@app/domain';
import { AssetController } from '@app/immich';
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra/entities';
import { AssetEntity, AssetType, LibraryType, SharedLinkType } from '@app/infra/entities';
import { AssetRepository } from '@app/infra/repositories';
import { INestApplication } from '@nestjs/common';
import { api } from '@test/api';
import { errorStub, uuidStub } from '@test/fixtures';
import { db, testApp } from '@test/test-utils';
import { randomBytes } from 'crypto';
import { DateTime } from 'luxon';
import request from 'supertest';
const user1Dto = {
@ -31,6 +32,9 @@ const user2Dto = {
name: 'User 2',
};
const today = DateTime.fromObject({ year: 2023, month: 11, day: 3 });
const yesterday = today.minus({ days: 1 });
const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
const dto: Record<string, any> = {
deviceAssetId: 'example-image',
@ -49,83 +53,498 @@ const makeUploadDto = (options?: { omit: string }): Record<string, any> => {
return dto;
};
let assetCount = 0;
const createAsset = (
repository: IAssetRepository,
loginResponse: LoginResponseDto,
libraryId: string,
createdAt: Date,
): Promise<AssetEntity> => {
const id = assetCount++;
return repository.create({
ownerId: loginResponse.userId,
checksum: randomBytes(20),
originalPath: `/tests/test_${id}`,
deviceAssetId: `test_${id}`,
deviceId: 'e2e-test',
libraryId,
isVisible: true,
fileCreatedAt: createdAt,
fileModifiedAt: new Date(),
localDateTime: createdAt,
type: AssetType.IMAGE,
originalFileName: `test_${id}`,
});
};
describe(`${AssetController.name} (e2e)`, () => {
let app: INestApplication;
let server: any;
let assetRepository: IAssetRepository;
let defaultLibrary: LibraryResponseDto;
let sharedLink: SharedLinkResponseDto;
let user1: LoginResponseDto;
let user2: LoginResponseDto;
let asset1: AssetEntity;
let asset2: AssetEntity;
let asset3: AssetEntity;
let asset4: AssetEntity;
let libraries: LibraryResponseDto[];
let asset1: AssetResponseDto;
let asset2: AssetResponseDto;
let asset3: AssetResponseDto;
let asset4: AssetResponseDto;
let asset5: AssetResponseDto;
let assetCount = 0;
const createAsset = async (loginResponse: LoginResponseDto, createdAt: Date, other: Partial<AssetEntity> = {}) => {
const id = assetCount++;
const asset = await assetRepository.create({
createdAt: today.toJSDate(),
updatedAt: today.toJSDate(),
ownerId: loginResponse.userId,
checksum: randomBytes(20),
originalPath: `/tests/test_${id}`,
deviceAssetId: `test_${id}`,
deviceId: 'e2e-test',
libraryId: (
libraries.find(
({ ownerId, type }) => ownerId === loginResponse.userId && type === LibraryType.UPLOAD,
) as LibraryResponseDto
).id,
isVisible: true,
fileCreatedAt: createdAt,
fileModifiedAt: new Date(),
localDateTime: createdAt,
type: AssetType.IMAGE,
originalFileName: `test_${id}`,
...other,
});
return mapAsset(asset);
};
beforeAll(async () => {
[server, app] = await testApp.create();
assetRepository = app.get<IAssetRepository>(IAssetRepository);
});
afterAll(async () => {
await testApp.teardown();
});
beforeEach(async () => {
await db.reset();
await api.authApi.adminSignUp(server);
const admin = await api.authApi.adminLogin(server);
const [libraries] = await Promise.all([
api.libraryApi.getAll(server, admin.accessToken),
await Promise.all([
api.userApi.create(server, admin.accessToken, user1Dto),
api.userApi.create(server, admin.accessToken, user2Dto),
]);
defaultLibrary = libraries[0];
[user1, user2] = await Promise.all([
api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password }),
api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password }),
]);
[asset1, asset2, asset3, asset4] = await Promise.all([
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-02')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user2, defaultLibrary.id, new Date('1970-01-01')),
const [user1Libraries, user2Libraries] = await Promise.all([
api.libraryApi.getAll(server, user1.accessToken),
api.libraryApi.getAll(server, user2.accessToken),
]);
sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
type: SharedLinkType.INDIVIDUAL,
assetIds: [asset1.id, asset2.id],
libraries = [...user1Libraries, ...user2Libraries];
});
beforeEach(async () => {
await db.reset({ entities: [AssetEntity] });
[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(),
encodedVideoPath: '/path/to/encoded-video.mp4',
webpPath: '/path/to/thumb.webp',
resizePath: '/path/to/thumb.jpg',
}),
createAsset(user2, new Date('1970-01-01')),
createAsset(user1, new Date('1970-01-01'), {
deletedAt: yesterday.toJSDate(),
}),
]);
await assetRepository.upsertExif({
assetId: asset3.id,
latitude: 90,
longitude: 90,
city: 'Immich',
state: 'Nebraska',
country: 'United States',
make: 'Cannon',
model: 'EOS Rebel T7',
lensModel: 'Fancy lens',
});
});
afterAll(async () => {
await testApp.teardown();
});
describe('GET /assets', () => {
it('should require authentication', async () => {
const { status, body } = await request(server).get('/assets');
expect(body).toEqual(errorStub.unauthorized);
expect(status).toBe(401);
});
const badTests = [
//
{
should: 'should reject page as a string',
query: { page: 'abc' },
expected: ['page must not be less than 1', 'page must be an integer number'],
},
{
should: 'should reject page as a decimal',
query: { page: 1.5 },
expected: ['page must be an integer number'],
},
{
should: 'should reject page as a negative number',
query: { page: -10 },
expected: ['page must not be less than 1'],
},
{
should: 'should reject page as 0',
query: { page: 0 },
expected: ['page must not be less than 1'],
},
{
should: 'should reject size as a string',
query: { size: 'abc' },
expected: ['size must not be less than 1', 'size must be an integer number'],
},
{
should: 'should reject an invalid size',
query: { size: -1.5 },
expected: ['size must not be less than 1', 'size must be an integer number'],
},
...[
'isArchived',
'isFavorite',
'isReadOnly',
'isExternal',
'isEncoded',
'isMotion',
'isOffline',
'isVisible',
].map((value) => ({
should: `should reject ${value} not a boolean`,
query: { [value]: 'immich' },
expected: [`${value} must be a boolean value`],
})),
];
for (const { should, query, expected } of badTests) {
it(should, async () => {
const { status, body } = await request(server)
.get('/assets')
.set('Authorization', `Bearer ${user1.accessToken}`)
.query(query);
expect(status).toBe(400);
expect(body).toEqual(errorStub.badRequest(expected));
});
}
const searchTests = [
{
should: 'should only return my own assets',
deferred: () => ({
query: {},
assets: [asset3, asset2, asset1],
}),
},
{
should: 'should sort my assets in reverse',
deferred: () => ({
query: { order: 'asc' },
assets: [asset1, asset2, asset3],
}),
},
{
should: 'should support custom page sizes',
deferred: () => ({
query: { size: 1 },
assets: [asset3],
}),
},
{
should: 'should support pagination',
deferred: () => ({
query: { size: 1, page: 2 },
assets: [asset2],
}),
},
{
should: 'should search by checksum (base64)',
deferred: () => ({
query: { checksum: asset1.checksum },
assets: [asset1],
}),
},
{
should: 'should search by checksum (hex)',
deferred: () => ({
query: { checksum: Buffer.from(asset1.checksum, 'base64').toString('hex') },
assets: [asset1],
}),
},
{
should: 'should search by id',
deferred: () => ({
query: { id: asset1.id },
assets: [asset1],
}),
},
{
should: 'should search by isFavorite (true)',
deferred: () => ({
query: { isFavorite: true },
assets: [asset3],
}),
},
{
should: 'should search by isFavorite (false)',
deferred: () => ({
query: { isFavorite: false },
assets: [asset2, asset1],
}),
},
{
should: 'should search by isArchived (true)',
deferred: () => ({
query: { isArchived: true },
assets: [asset3],
}),
},
{
should: 'should search by isArchived (false)',
deferred: () => ({
query: { isArchived: false },
assets: [asset2, asset1],
}),
},
{
should: 'should search by isReadOnly (true)',
deferred: () => ({
query: { isReadOnly: true },
assets: [asset3],
}),
},
{
should: 'should search by isReadOnly (false)',
deferred: () => ({
query: { isReadOnly: false },
assets: [asset2, asset1],
}),
},
{
should: 'should search by type (image)',
deferred: () => ({
query: { type: 'IMAGE' },
assets: [asset2, asset1],
}),
},
{
should: 'should search by type (video)',
deferred: () => ({
query: { type: 'VIDEO' },
assets: [asset3],
}),
},
{
should: 'should search by createdBefore',
deferred: () => ({
query: { createdBefore: yesterday.plus({ hour: 1 }).toJSDate() },
assets: [asset3],
}),
},
{
should: 'should search by createdBefore (no results)',
deferred: () => ({
query: { createdBefore: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by createdAfter',
deferred: () => ({
query: { createdAfter: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [asset3, asset2, asset1],
}),
},
{
should: 'should search by createdAfter (no results)',
deferred: () => ({
query: { createdAfter: today.plus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by updatedBefore',
deferred: () => ({
query: { updatedBefore: yesterday.plus({ hour: 1 }).toJSDate() },
assets: [asset3],
}),
},
{
should: 'should search by updatedBefore (no results)',
deferred: () => ({
query: { updatedBefore: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by updatedAfter',
deferred: () => ({
query: { updatedAfter: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [asset3, asset2, asset1],
}),
},
{
should: 'should search by updatedAfter (no results)',
deferred: () => ({
query: { updatedAfter: today.plus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by trashedBefore',
deferred: () => ({
query: { trashedBefore: yesterday.plus({ hour: 1 }).toJSDate() },
assets: [asset5],
}),
},
{
should: 'should search by trashedBefore (no results)',
deferred: () => ({
query: { trashedBefore: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by trashedAfter',
deferred: () => ({
query: { trashedAfter: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [asset5],
}),
},
{
should: 'should search by trashedAfter (no results)',
deferred: () => ({
query: { trashedAfter: today.plus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by takenBefore',
deferred: () => ({
query: { takenBefore: yesterday.plus({ hour: 1 }).toJSDate() },
assets: [asset3, asset2, asset1],
}),
},
{
should: 'should search by takenBefore (no results)',
deferred: () => ({
query: { takenBefore: yesterday.minus({ years: 100 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by takenAfter',
deferred: () => ({
query: { takenAfter: yesterday.minus({ hour: 1 }).toJSDate() },
assets: [asset3],
}),
},
{
should: 'should search by takenAfter (no results)',
deferred: () => ({
query: { takenAfter: today.plus({ hour: 1 }).toJSDate() },
assets: [],
}),
},
{
should: 'should search by originalPath',
deferred: () => ({
query: { originalPath: asset1.originalPath },
assets: [asset1],
}),
},
{
should: 'should search by originalFilename',
deferred: () => ({
query: { originalFileName: asset1.originalFileName },
assets: [asset1],
}),
},
{
should: 'should search by encodedVideoPath',
deferred: () => ({
query: { encodedVideoPath: '/path/to/encoded-video.mp4' },
assets: [asset3],
}),
},
{
should: 'should search by resizePath',
deferred: () => ({
query: { resizePath: '/path/to/thumb.jpg' },
assets: [asset3],
}),
},
{
should: 'should search by webpPath',
deferred: () => ({
query: { webpPath: '/path/to/thumb.webp' },
assets: [asset3],
}),
},
{
should: 'should search by city',
deferred: () => ({
query: { city: 'Immich' },
assets: [asset3],
}),
},
{
should: 'should search by state',
deferred: () => ({
query: { state: 'Nebraska' },
assets: [asset3],
}),
},
{
should: 'should search by country',
deferred: () => ({
query: { country: 'United States' },
assets: [asset3],
}),
},
{
should: 'sohuld search by make',
deferred: () => ({
query: { make: 'Cannon' },
assets: [asset3],
}),
},
{
should: 'should search by country',
deferred: () => ({
query: { model: 'EOS Rebel T7' },
assets: [asset3],
}),
},
{
should: 'should search by lensModel',
deferred: () => ({
query: { lensModel: 'Fancy lens' },
assets: [asset3],
}),
},
];
for (const { should, deferred } of searchTests) {
it(should, async () => {
const { assets, query } = deferred();
const { status, body } = await request(server)
.get('/assets')
.query(query)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body.length).toBe(assets.length);
for (let i = 0; i < assets.length; i++) {
expect(body[i]).toEqual(expect.objectContaining({ id: assets[i].id }));
}
});
}
});
describe('POST /asset/upload', () => {
it('should require authentication', async () => {
const { status, body } = await request(server)
@ -369,8 +788,8 @@ describe(`${AssetController.name} (e2e)`, () => {
.get('/asset/statistics')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual({ images: 5, videos: 1, total: 6 });
expect(status).toBe(200);
expect(body).toEqual({ images: 6, videos: 0, total: 6 });
});
it('should return stats of all favored assets', async () => {
@ -380,7 +799,7 @@ describe(`${AssetController.name} (e2e)`, () => {
.query({ isFavorite: true });
expect(status).toBe(200);
expect(body).toEqual({ images: 2, videos: 0, total: 2 });
expect(body).toEqual({ images: 2, videos: 1, total: 3 });
});
it('should return stats of all archived assets', async () => {
@ -390,7 +809,7 @@ describe(`${AssetController.name} (e2e)`, () => {
.query({ isArchived: true });
expect(status).toBe(200);
expect(body).toEqual({ images: 2, videos: 0, total: 2 });
expect(body).toEqual({ images: 2, videos: 1, total: 3 });
});
it('should return stats of all favored and archived assets', async () => {
@ -400,7 +819,7 @@ describe(`${AssetController.name} (e2e)`, () => {
.query({ isFavorite: true, isArchived: true });
expect(status).toBe(200);
expect(body).toEqual({ images: 1, videos: 0, total: 1 });
expect(body).toEqual({ images: 1, videos: 1, total: 2 });
});
it('should return stats of all assets neither favored nor archived', async () => {
@ -410,19 +829,19 @@ describe(`${AssetController.name} (e2e)`, () => {
.query({ isFavorite: false, isArchived: false });
expect(status).toBe(200);
expect(body).toEqual({ images: 3, videos: 0, total: 3 });
expect(body).toEqual({ images: 2, videos: 0, total: 2 });
});
});
describe('GET /asset/random', () => {
beforeAll(async () => {
await Promise.all([
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
createAsset(user1, new Date('1970-02-01')),
]);
});
it('should require authentication', async () => {
@ -432,7 +851,7 @@ describe(`${AssetController.name} (e2e)`, () => {
expect(body).toEqual(errorStub.unauthorized);
});
it('should return 1 random assets', async () => {
it.each(Array(10))('should return 1 random assets', async () => {
const { status, body } = await request(server)
.get('/asset/random')
.set('Authorization', `Bearer ${user1.accessToken}`);
@ -442,13 +861,14 @@ describe(`${AssetController.name} (e2e)`, () => {
const assets: AssetResponseDto[] = body;
expect(assets.length).toBe(1);
expect(assets[0].ownerId).toBe(user1.userId);
// assets owned by user1
expect([asset1.id, asset2.id, asset3.id]).toContain(assets[0].id);
//
// assets owned by user2
expect(assets[0].id).not.toBe(asset4.id);
// assets owned by user1
expect([asset1.id, asset2.id, asset3.id]).toContain(assets[0].id);
});
it('should return 2 random assets', async () => {
it.each(Array(10))('should return 2 random assets', async () => {
const { status, body } = await request(server)
.get('/asset/random?count=2')
.set('Authorization', `Bearer ${user1.accessToken}`);
@ -505,13 +925,19 @@ describe(`${AssetController.name} (e2e)`, () => {
expect(status).toBe(200);
expect(body).toEqual(
expect.arrayContaining([
{ count: 1, timeBucket: asset3.fileCreatedAt.toISOString() },
{ count: 2, timeBucket: asset1.fileCreatedAt.toISOString() },
{ count: 1, timeBucket: '2023-11-01T00:00:00.000Z' },
{ count: 1, timeBucket: '1970-01-01T00:00:00.000Z' },
{ count: 1, timeBucket: '1970-02-01T00:00:00.000Z' },
]),
);
});
it('should not allow access for unrelated shared links', async () => {
const sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
type: SharedLinkType.INDIVIDUAL,
assetIds: [asset1.id, asset2.id],
});
const { status, body } = await request(server)
.get('/asset/time-buckets')
.query({ key: sharedLink.key, size: TimeBucketSize.MONTH });
@ -575,12 +1001,7 @@ describe(`${AssetController.name} (e2e)`, () => {
.query({ size: TimeBucketSize.MONTH, timeBucket });
expect(status).toBe(200);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: asset1.id }),
expect.objectContaining({ id: asset2.id }),
]),
);
expect(body).toEqual(expect.arrayContaining([expect.objectContaining({ id: asset2.id })]));
});
it('should return error if time bucket is requested with partners asset and archived', async () => {
@ -649,16 +1070,12 @@ describe(`${AssetController.name} (e2e)`, () => {
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(server)
.get('/asset/map-marker')
.query({ isArchived: false })
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(2);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: asset1.id }),
expect.objectContaining({ id: asset2.id }),
]),
);
expect(body).toHaveLength(1);
expect(body).toEqual(expect.arrayContaining([expect.objectContaining({ id: asset2.id })]));
});
it('should get all map markers', async () => {
@ -711,8 +1128,10 @@ describe(`${AssetController.name} (e2e)`, () => {
});
it('should add stack children', async () => {
const parent = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-01'));
const child = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-01'));
const [parent, child] = await Promise.all([
createAsset(user1, new Date('1970-01-01')),
createAsset(user1, new Date('1970-01-01')),
]);
const { status } = await request(server)
.put('/asset')
@ -752,7 +1171,7 @@ describe(`${AssetController.name} (e2e)`, () => {
});
it('should merge stack children', async () => {
const newParent = await createAsset(assetRepository, user1, defaultLibrary.id, new Date('1970-01-01'));
const newParent = await createAsset(user1, new Date('1970-01-01'));
const { status } = await request(server)
.put('/asset')
.set('Authorization', `Bearer ${user1.accessToken}`)

View file

@ -31,5 +31,6 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
getTimeBuckets: jest.fn(),
restoreAll: jest.fn(),
softDeleteAll: jest.fn(),
search: jest.fn(),
};
};

View file

@ -5,25 +5,39 @@ import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as fs from 'fs';
import path from 'path';
import { EntityTarget, ObjectLiteral } from 'typeorm';
import { AppService } from '../src/microservices/app.service';
export const IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH;
export const IMMICH_TEST_ASSET_TEMP_PATH = path.normalize(`${IMMICH_TEST_ASSET_PATH}/temp/`);
export interface ResetOptions {
entities?: EntityTarget<ObjectLiteral>[];
}
export const db = {
reset: async () => {
reset: async (options?: ResetOptions) => {
if (!dataSource.isInitialized) {
await dataSource.initialize();
}
await dataSource.transaction(async (em) => {
for (const entity of dataSource.entityMetadatas) {
if (entity.tableName === 'users') {
const entities = options?.entities || [];
const tableNames =
entities.length > 0
? entities.map((entity) => em.getRepository(entity).metadata.tableName)
: dataSource.entityMetadatas.map((entity) => entity.tableName);
let deleteUsers = false;
for (const tableName of tableNames) {
if (tableName === 'users') {
deleteUsers = true;
continue;
}
await em.query(`DELETE FROM ${entity.tableName} CASCADE;`);
await em.query(`DELETE FROM ${tableName} CASCADE;`);
}
if (deleteUsers) {
await em.query(`DELETE FROM "users" CASCADE;`);
}
await em.query(`DELETE FROM "users" CASCADE;`);
});
},
disconnect: async () => {

View file

@ -670,6 +670,20 @@ export interface AssetJobsDto {
}
/**
*
* @export
* @enum {string}
*/
export const AssetOrder = {
Asc: 'asc',
Desc: 'desc'
} as const;
export type AssetOrder = typeof AssetOrder[keyof typeof AssetOrder];
/**
*
* @export
@ -7822,6 +7836,260 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions,
};
},
/**
*
* @param {string} [id]
* @param {string} [libraryId]
* @param {AssetTypeEnum} [type]
* @param {AssetOrder} [order]
* @param {string} [deviceAssetId]
* @param {string} [deviceId]
* @param {string} [checksum]
* @param {boolean} [isArchived]
* @param {boolean} [isEncoded]
* @param {boolean} [isExternal]
* @param {boolean} [isFavorite]
* @param {boolean} [isMotion]
* @param {boolean} [isOffline]
* @param {boolean} [isReadOnly]
* @param {boolean} [isVisible]
* @param {boolean} [withDeleted]
* @param {boolean} [withStacked]
* @param {boolean} [withExif]
* @param {boolean} [withPeople]
* @param {string} [createdBefore]
* @param {string} [createdAfter]
* @param {string} [updatedBefore]
* @param {string} [updatedAfter]
* @param {string} [trashedBefore]
* @param {string} [trashedAfter]
* @param {string} [takenBefore]
* @param {string} [takenAfter]
* @param {string} [originalFileName]
* @param {string} [originalPath]
* @param {string} [resizePath]
* @param {string} [webpPath]
* @param {string} [encodedVideoPath]
* @param {string} [city]
* @param {string} [state]
* @param {string} [country]
* @param {string} [make]
* @param {string} [model]
* @param {string} [lensModel]
* @param {number} [page]
* @param {number} [size]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
searchAssets: async (id?: string, libraryId?: string, type?: AssetTypeEnum, order?: AssetOrder, deviceAssetId?: string, deviceId?: string, checksum?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, withDeleted?: boolean, withStacked?: boolean, withExif?: boolean, withPeople?: boolean, createdBefore?: string, createdAfter?: string, updatedBefore?: string, updatedAfter?: string, trashedBefore?: string, trashedAfter?: string, takenBefore?: string, takenAfter?: string, originalFileName?: string, originalPath?: string, resizePath?: string, webpPath?: string, encodedVideoPath?: string, city?: string, state?: string, country?: string, make?: string, model?: string, lensModel?: string, page?: number, size?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/assets`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
if (libraryId !== undefined) {
localVarQueryParameter['libraryId'] = libraryId;
}
if (type !== undefined) {
localVarQueryParameter['type'] = type;
}
if (order !== undefined) {
localVarQueryParameter['order'] = order;
}
if (deviceAssetId !== undefined) {
localVarQueryParameter['deviceAssetId'] = deviceAssetId;
}
if (deviceId !== undefined) {
localVarQueryParameter['deviceId'] = deviceId;
}
if (checksum !== undefined) {
localVarQueryParameter['checksum'] = checksum;
}
if (isArchived !== undefined) {
localVarQueryParameter['isArchived'] = isArchived;
}
if (isEncoded !== undefined) {
localVarQueryParameter['isEncoded'] = isEncoded;
}
if (isExternal !== undefined) {
localVarQueryParameter['isExternal'] = isExternal;
}
if (isFavorite !== undefined) {
localVarQueryParameter['isFavorite'] = isFavorite;
}
if (isMotion !== undefined) {
localVarQueryParameter['isMotion'] = isMotion;
}
if (isOffline !== undefined) {
localVarQueryParameter['isOffline'] = isOffline;
}
if (isReadOnly !== undefined) {
localVarQueryParameter['isReadOnly'] = isReadOnly;
}
if (isVisible !== undefined) {
localVarQueryParameter['isVisible'] = isVisible;
}
if (withDeleted !== undefined) {
localVarQueryParameter['withDeleted'] = withDeleted;
}
if (withStacked !== undefined) {
localVarQueryParameter['withStacked'] = withStacked;
}
if (withExif !== undefined) {
localVarQueryParameter['withExif'] = withExif;
}
if (withPeople !== undefined) {
localVarQueryParameter['withPeople'] = withPeople;
}
if (createdBefore !== undefined) {
localVarQueryParameter['createdBefore'] = (createdBefore as any instanceof Date) ?
(createdBefore as any).toISOString() :
createdBefore;
}
if (createdAfter !== undefined) {
localVarQueryParameter['createdAfter'] = (createdAfter as any instanceof Date) ?
(createdAfter as any).toISOString() :
createdAfter;
}
if (updatedBefore !== undefined) {
localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ?
(updatedBefore as any).toISOString() :
updatedBefore;
}
if (updatedAfter !== undefined) {
localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ?
(updatedAfter as any).toISOString() :
updatedAfter;
}
if (trashedBefore !== undefined) {
localVarQueryParameter['trashedBefore'] = (trashedBefore as any instanceof Date) ?
(trashedBefore as any).toISOString() :
trashedBefore;
}
if (trashedAfter !== undefined) {
localVarQueryParameter['trashedAfter'] = (trashedAfter as any instanceof Date) ?
(trashedAfter as any).toISOString() :
trashedAfter;
}
if (takenBefore !== undefined) {
localVarQueryParameter['takenBefore'] = (takenBefore as any instanceof Date) ?
(takenBefore as any).toISOString() :
takenBefore;
}
if (takenAfter !== undefined) {
localVarQueryParameter['takenAfter'] = (takenAfter as any instanceof Date) ?
(takenAfter as any).toISOString() :
takenAfter;
}
if (originalFileName !== undefined) {
localVarQueryParameter['originalFileName'] = originalFileName;
}
if (originalPath !== undefined) {
localVarQueryParameter['originalPath'] = originalPath;
}
if (resizePath !== undefined) {
localVarQueryParameter['resizePath'] = resizePath;
}
if (webpPath !== undefined) {
localVarQueryParameter['webpPath'] = webpPath;
}
if (encodedVideoPath !== undefined) {
localVarQueryParameter['encodedVideoPath'] = encodedVideoPath;
}
if (city !== undefined) {
localVarQueryParameter['city'] = city;
}
if (state !== undefined) {
localVarQueryParameter['state'] = state;
}
if (country !== undefined) {
localVarQueryParameter['country'] = country;
}
if (make !== undefined) {
localVarQueryParameter['make'] = make;
}
if (model !== undefined) {
localVarQueryParameter['model'] = model;
}
if (lensModel !== undefined) {
localVarQueryParameter['lensModel'] = lensModel;
}
if (page !== undefined) {
localVarQueryParameter['page'] = page;
}
if (size !== undefined) {
localVarQueryParameter['size'] = size;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} id
@ -8440,6 +8708,55 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.searchAsset(searchAssetDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {string} [id]
* @param {string} [libraryId]
* @param {AssetTypeEnum} [type]
* @param {AssetOrder} [order]
* @param {string} [deviceAssetId]
* @param {string} [deviceId]
* @param {string} [checksum]
* @param {boolean} [isArchived]
* @param {boolean} [isEncoded]
* @param {boolean} [isExternal]
* @param {boolean} [isFavorite]
* @param {boolean} [isMotion]
* @param {boolean} [isOffline]
* @param {boolean} [isReadOnly]
* @param {boolean} [isVisible]
* @param {boolean} [withDeleted]
* @param {boolean} [withStacked]
* @param {boolean} [withExif]
* @param {boolean} [withPeople]
* @param {string} [createdBefore]
* @param {string} [createdAfter]
* @param {string} [updatedBefore]
* @param {string} [updatedAfter]
* @param {string} [trashedBefore]
* @param {string} [trashedAfter]
* @param {string} [takenBefore]
* @param {string} [takenAfter]
* @param {string} [originalFileName]
* @param {string} [originalPath]
* @param {string} [resizePath]
* @param {string} [webpPath]
* @param {string} [encodedVideoPath]
* @param {string} [city]
* @param {string} [state]
* @param {string} [country]
* @param {string} [make]
* @param {string} [model]
* @param {string} [lensModel]
* @param {number} [page]
* @param {number} [size]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async searchAssets(id?: string, libraryId?: string, type?: AssetTypeEnum, order?: AssetOrder, deviceAssetId?: string, deviceId?: string, checksum?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, withDeleted?: boolean, withStacked?: boolean, withExif?: boolean, withPeople?: boolean, createdBefore?: string, createdAfter?: string, updatedBefore?: string, updatedAfter?: string, trashedBefore?: string, trashedAfter?: string, takenBefore?: string, takenAfter?: string, originalFileName?: string, originalPath?: string, resizePath?: string, webpPath?: string, encodedVideoPath?: string, city?: string, state?: string, country?: string, make?: string, model?: string, lensModel?: string, page?: number, size?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.searchAssets(id, libraryId, type, order, deviceAssetId, deviceId, checksum, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, withDeleted, withStacked, withExif, withPeople, createdBefore, createdAfter, updatedBefore, updatedAfter, trashedBefore, trashedAfter, takenBefore, takenAfter, originalFileName, originalPath, resizePath, webpPath, encodedVideoPath, city, state, country, make, model, lensModel, page, size, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {string} id
@ -8739,6 +9056,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
searchAsset(requestParameters: AssetApiSearchAssetRequest, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.searchAsset(requestParameters.searchAssetDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiSearchAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.searchAssets(requestParameters.id, requestParameters.libraryId, requestParameters.type, requestParameters.order, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.checksum, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.withDeleted, requestParameters.withStacked, requestParameters.withExif, requestParameters.withPeople, requestParameters.createdBefore, requestParameters.createdAfter, requestParameters.updatedBefore, requestParameters.updatedAfter, requestParameters.trashedBefore, requestParameters.trashedAfter, requestParameters.takenBefore, requestParameters.takenAfter, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.resizePath, requestParameters.webpPath, requestParameters.encodedVideoPath, requestParameters.city, requestParameters.state, requestParameters.country, requestParameters.make, requestParameters.model, requestParameters.lensModel, requestParameters.page, requestParameters.size, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiServeFileRequest} requestParameters Request parameters.
@ -9333,6 +9659,293 @@ export interface AssetApiSearchAssetRequest {
readonly searchAssetDto: SearchAssetDto
}
/**
* Request parameters for searchAssets operation in AssetApi.
* @export
* @interface AssetApiSearchAssetsRequest
*/
export interface AssetApiSearchAssetsRequest {
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly id?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly libraryId?: string
/**
*
* @type {AssetTypeEnum}
* @memberof AssetApiSearchAssets
*/
readonly type?: AssetTypeEnum
/**
*
* @type {AssetOrder}
* @memberof AssetApiSearchAssets
*/
readonly order?: AssetOrder
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly deviceAssetId?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly deviceId?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly checksum?: string
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isArchived?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isEncoded?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isExternal?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isFavorite?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isMotion?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isOffline?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isReadOnly?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly isVisible?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withDeleted?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withStacked?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withExif?: boolean
/**
*
* @type {boolean}
* @memberof AssetApiSearchAssets
*/
readonly withPeople?: boolean
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly createdBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly createdAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly updatedBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly updatedAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly trashedBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly trashedAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly takenBefore?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly takenAfter?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly originalFileName?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly originalPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly resizePath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly webpPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly encodedVideoPath?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly city?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly state?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly country?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly make?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly model?: string
/**
*
* @type {string}
* @memberof AssetApiSearchAssets
*/
readonly lensModel?: string
/**
*
* @type {number}
* @memberof AssetApiSearchAssets
*/
readonly page?: number
/**
*
* @type {number}
* @memberof AssetApiSearchAssets
*/
readonly size?: number
}
/**
* Request parameters for serveFile operation in AssetApi.
* @export
@ -9813,6 +10426,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).searchAsset(requestParameters.searchAssetDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiSearchAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).searchAssets(requestParameters.id, requestParameters.libraryId, requestParameters.type, requestParameters.order, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.checksum, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.withDeleted, requestParameters.withStacked, requestParameters.withExif, requestParameters.withPeople, requestParameters.createdBefore, requestParameters.createdAfter, requestParameters.updatedBefore, requestParameters.updatedAfter, requestParameters.trashedBefore, requestParameters.trashedAfter, requestParameters.takenBefore, requestParameters.takenAfter, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.resizePath, requestParameters.webpPath, requestParameters.encodedVideoPath, requestParameters.city, requestParameters.state, requestParameters.country, requestParameters.make, requestParameters.model, requestParameters.lensModel, requestParameters.page, requestParameters.size, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiServeFileRequest} requestParameters Request parameters.