Merge branch 'main' of https://github.com/immich-app/immich into feat/cli-albums
This commit is contained in:
commit
a9cbd573f4
91 changed files with 4244 additions and 532 deletions
2
.github/workflows/dispatch_sdk_update.yml
vendored
2
.github/workflows/dispatch_sdk_update.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GH_TOKEN }}
|
||||
script: |
|
||||
|
|
|
@ -85,7 +85,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
|||
| Virtual scroll | Yes | Yes |
|
||||
| OAuth support | Yes | Yes |
|
||||
| API Keys | N/A | Yes |
|
||||
| LivePhoto backup and playback | iOS | Yes |
|
||||
| LivePhoto/MotionPhoto backup and playback | Yes | Yes |
|
||||
| User-defined storage structure | Yes | Yes |
|
||||
| Public Sharing | No | Yes |
|
||||
| Archive and Favorites | Yes | Yes |
|
||||
|
@ -95,6 +95,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
|||
| Memories (x years ago) | Yes | Yes |
|
||||
| Offline support | Yes | No |
|
||||
| Read-only gallery | Yes | Yes |
|
||||
| Stacked Photos | Yes | Yes |
|
||||
|
||||
## Support the project
|
||||
|
||||
|
|
12
cli/package-lock.json
generated
12
cli/package-lock.json
generated
|
@ -2416,9 +2416,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
|
||||
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
|
@ -7953,9 +7953,9 @@
|
|||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
|
||||
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ=="
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
|
|
624
cli/src/api/open-api/api.ts
generated
624
cli/src/api/open-api/api.ts
generated
|
@ -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.
|
||||
|
|
|
@ -127,7 +127,7 @@ services:
|
|||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
|
|
@ -84,7 +84,7 @@ services:
|
|||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
|
|
@ -23,7 +23,7 @@ services:
|
|||
- database
|
||||
|
||||
database:
|
||||
image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
command: -c fsync=off
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
|
|
@ -71,7 +71,7 @@ services:
|
|||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:874f566dd512d79cf74f59754833e869ae76ece96716d153b0fa3e64aec88d92
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Company",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek již je v albu.",
|
||||
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuji",
|
||||
"home_page_add_to_album_success": "Přidány položky {added} do alba {album}.",
|
||||
"home_page_album_err_partner": "Položky partnera nelze zatím přidat do alba, přeskakuji",
|
||||
"home_page_archive_err_local": "Zatím nemohu archivovat lokální média, přeskakuji",
|
||||
"home_page_archive_err_partner": "Položky partnera nelze archivovat, přeskakuji",
|
||||
"home_page_building_timeline": "Vytváření časové osy",
|
||||
"home_page_delete_err_partner": "Položky partnera nelze odstranit, přeskakuji",
|
||||
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuji",
|
||||
"home_page_favorite_err_partner": "Položky partnera nelze označit jako oblíbené, přeskakuji",
|
||||
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji",
|
||||
"image_viewer_page_state_provider_download_error": "Chyba stahování",
|
||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} již nebude mít přístup k vašim fotografiím.",
|
||||
"partner_page_stop_sharing_title": "Přestat sdílet vaše fotografie?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Přesto pokračovat",
|
||||
"permission_onboarding_get_started": "Začít",
|
||||
"permission_onboarding_go_to_settings": "Přejít do nastavení",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Tilføjede {added} elementer til album {album}. {failed} elementer er allerede i albummet.",
|
||||
"home_page_add_to_album_err_local": "Kan endnu ikke tilføje lokale elementer til album. Springer over..",
|
||||
"home_page_add_to_album_success": "Tilføjede {added} elementer til album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu.. Springer over",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Kan endnu ikke gøre lokale elementer til favoritter. Springer over..",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over",
|
||||
"image_viewer_page_state_provider_download_error": "Fejl ved download",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} vil ikke længere have adgang til dine billeder.",
|
||||
"partner_page_stop_sharing_title": "Stop med at dele dine billeder?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Fortsæt alligevel",
|
||||
"permission_onboarding_get_started": "Kom i gang",
|
||||
"permission_onboarding_go_to_settings": "Gå til indstillinger",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugefügt. {failed} Elemente sind bereits vorhanden.",
|
||||
"home_page_add_to_album_err_local": "Kann lokale Elemente noch nicht zu Alben hinzufügen, überspringe",
|
||||
"home_page_add_to_album_success": "{added} Elemente zu {album} hinzugefügt.",
|
||||
"home_page_album_err_partner": "Inhalte von Partnern können derzeit nicht zu Alben hinzugefügt werden",
|
||||
"home_page_archive_err_local": "Kann lokale Elemente nicht archvieren, überspringe",
|
||||
"home_page_archive_err_partner": "Inhalte von Partnern können nicht archiviert werden",
|
||||
"home_page_building_timeline": "Zeitachse wird erstellt.",
|
||||
"home_page_delete_err_partner": "Inhalte von Partnern können nicht gelöscht werden",
|
||||
"home_page_favorite_err_local": "Kann lokale Elemente noch nicht favorisieren, überspringe",
|
||||
"home_page_favorite_err_partner": "Inhalte von Partnern können nicht favorisiert werden",
|
||||
"home_page_first_time_notice": "Wenn dies das erste Mal ist dass Du Immich nutzt, stelle bitte sicher, dass mindestens ein Album zur Sicherung ausgewählt ist, sodass die Zeitachse mit Fotos und Videos gefüllt werden kann.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Max. 30 Elemente können gleichzeitig hochgeladen werden, überspringe",
|
||||
"image_viewer_page_state_provider_download_error": "Fehler beim Herunterladen",
|
||||
"image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} wird nicht mehr auf deine Fotos zugreifen können.",
|
||||
"partner_page_stop_sharing_title": "Deine Fotos nicht mehr teilen?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Trotzdem fortfahren",
|
||||
"permission_onboarding_get_started": "Jetzt starten",
|
||||
"permission_onboarding_go_to_settings": "Gehe zu Einstellungen",
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Backup Information",
|
||||
"backup_controller_page_none_selected": "None selected",
|
||||
"backup_controller_page_remainder": "Remainder",
|
||||
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
|
||||
|
@ -95,6 +96,7 @@
|
|||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
"backup_controller_page_turn_on": "Turn on foreground backup",
|
||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||
"backup_err_only_album": "Cannot remove the only album",
|
||||
"backup_info_card_assets": "assets",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
|
@ -168,15 +170,16 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||
|
@ -247,15 +250,16 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
"permission_onboarding_grant_permission": "Grant permission",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_log_out": "Log out",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich requires permission to access your photos and videos.",
|
||||
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||
"profile_drawer_documentation": "Documentation",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} elementos agregados al álbum {album}.{failed} elementos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden agregar recursos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "{added} elementos agregados al álbum {album}. ",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Los recursos locales no pueden ser archivados, omitiendo",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo",
|
||||
"image_viewer_page_state_provider_download_error": "Error de descarga",
|
||||
"image_viewer_page_state_provider_download_success": "Descarga exitosa",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos",
|
||||
"partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?",
|
||||
"partner_page_title": "Compañero",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continuar de todos modos",
|
||||
"permission_onboarding_get_started": "Empezar",
|
||||
"permission_onboarding_go_to_settings": "Ir a configuración",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} elementos agregados al álbum {album}.\n{failed} elementos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden agregar recursos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "{added} elementos agregados al álbum {album}. ",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Los recursos locales no pueden ser archivados, omitiendo",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo",
|
||||
"image_viewer_page_state_provider_download_error": "Error de descarga",
|
||||
"image_viewer_page_state_provider_download_success": "Descarga exitosa",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos",
|
||||
"partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?",
|
||||
"partner_page_title": "Compañero",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continuar de todos modos",
|
||||
"permission_onboarding_get_started": "Empezar",
|
||||
"permission_onboarding_go_to_settings": "Ir a configuración",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} elementos agregados al álbum {album}.\n{failed} elementos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden agregar recursos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "{added} elementos agregados al álbum {album}. ",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Los recursos locales no pueden ser archivados, omitiendo",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Aún no se pueden archivar recursos locales, omitiendo",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Si esta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo",
|
||||
"image_viewer_page_state_provider_download_error": "Error de descarga",
|
||||
"image_viewer_page_state_provider_download_success": "Descarga exitosa",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos",
|
||||
"partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?",
|
||||
"partner_page_title": "Compañero",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continuar de todos modos",
|
||||
"permission_onboarding_get_started": "Empezar",
|
||||
"permission_onboarding_go_to_settings": "Ir a configuración",
|
||||
|
|
388
mobile/assets/i18n/es-US.json
Normal file
388
mobile/assets/i18n/es-US.json
Normal file
|
@ -0,0 +1,388 @@
|
|||
{
|
||||
"add_to_album_bottom_sheet_added": "Agregado a {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas de recursos encontrados en el dispositivo. Activa esta opción para cargar imágenes remotas en su lugar.",
|
||||
"advanced_settings_prefer_remote_title": "Preferir imágenes remotas",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Omite la verificación del certificado SSL para la URL del servidor. Requerido para certificados autofirmados.",
|
||||
"advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados",
|
||||
"advanced_settings_tile_subtitle": "Configuraciones avanzadas de usuario",
|
||||
"advanced_settings_tile_title": "Avanzado",
|
||||
"advanced_settings_troubleshooting_subtitle": "Habilitar funciones adicionales para solución de problemas",
|
||||
"advanced_settings_troubleshooting_title": "Solución de problemas",
|
||||
"album_info_card_backup_album_excluded": "EXCLUIDOS",
|
||||
"album_info_card_backup_album_included": "INCLUIDOS",
|
||||
"album_thumbnail_card_item": "1 elemento",
|
||||
"album_thumbnail_card_items": "{} elementos",
|
||||
"album_thumbnail_card_shared": " · Compartido",
|
||||
"album_thumbnail_owned": "Propio",
|
||||
"album_thumbnail_shared_by": "Compartido por {}",
|
||||
"album_viewer_appbar_share_delete": "Eliminar álbum",
|
||||
"album_viewer_appbar_share_err_delete": "No se ha podido eliminar el álbum",
|
||||
"album_viewer_appbar_share_err_leave": "No se ha podido abandonar el álbum",
|
||||
"album_viewer_appbar_share_err_remove": "Hay problemas para remover los archivos del álbum",
|
||||
"album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum",
|
||||
"album_viewer_appbar_share_leave": "Abandonar álbum",
|
||||
"album_viewer_appbar_share_remove": "Remover del álbum",
|
||||
"album_viewer_appbar_share_to": "Compartir con",
|
||||
"album_viewer_page_share_add_users": "Agregar usuarios",
|
||||
"all_people_page_title": "Personas",
|
||||
"all_videos_page_title": "Videos",
|
||||
"app_bar_signout_dialog_content": "¿Estás seguro de que quieres cerrar sesión?",
|
||||
"app_bar_signout_dialog_ok": "Sí",
|
||||
"app_bar_signout_dialog_title": "Cerrar sesión",
|
||||
"archive_page_no_archived_assets": "No se encontraron recursos archivados",
|
||||
"archive_page_title": "Archivo ({})",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico",
|
||||
"asset_list_layout_settings_group_automatically": "Automático",
|
||||
"asset_list_layout_settings_group_by": "Agrupar recursos por",
|
||||
"asset_list_layout_settings_group_by_month": "Mes",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mes + día",
|
||||
"asset_list_settings_subtitle": "Configuraciones del diseño de la cuadrícula de fotos",
|
||||
"asset_list_settings_title": "Cuadrícula de fotos",
|
||||
"backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({})",
|
||||
"backup_album_selection_page_albums_tap": "Pulsar para incluir, pulsar dos veces para excluir",
|
||||
"backup_album_selection_page_assets_scatter": "Los archivos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.",
|
||||
"backup_album_selection_page_select_albums": "Seleccionar álbumes",
|
||||
"backup_album_selection_page_selection_info": "Información de la selección",
|
||||
"backup_album_selection_page_total_assets": "Total de archivos únicos",
|
||||
"backup_all": "Todos",
|
||||
"backup_background_service_backup_failed_message": "Error al copiar archivos. Reintentando…",
|
||||
"backup_background_service_connection_failed_message": "Error al conectar con el servidor. Reintentando…",
|
||||
"backup_background_service_current_upload_notification": "Subiendo {}",
|
||||
"backup_background_service_default_notification": "Verificando si hay nuevos archivos…",
|
||||
"backup_background_service_error_title": "Error de copia de seguridad",
|
||||
"backup_background_service_in_progress_notification": "Creando copia de seguridad de tus archivos…",
|
||||
"backup_background_service_upload_failure_notification": "Error al subir {}",
|
||||
"backup_controller_page_albums": "Álbumes de respaldo",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Activa la actualización en segundo plano de la aplicación en Configuración > General > Actualización en segundo plano para usar la copia de seguridad en segundo plano.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Actualización en segundo plano desactivada",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Ir a configuración",
|
||||
"backup_controller_page_background_battery_info_link": "Muéstrame cómo",
|
||||
"backup_controller_page_background_battery_info_message": "Para obtener la mejor experiencia de copia de seguridad en segundo plano, desactiva cualquier optimización de batería que restrinja la actividad en segundo plano para Immich.\n\nDado que esto es específico en cada dispositivo, busca la información necesaria del fabricante de tu dispositivo.",
|
||||
"backup_controller_page_background_battery_info_ok": "Ok",
|
||||
"backup_controller_page_background_battery_info_title": "Optimizaciones de batería",
|
||||
"backup_controller_page_background_charging": "Sólo mientras se carga",
|
||||
"backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano",
|
||||
"backup_controller_page_background_delay": "Retraso en la copia de seguridad de nuevos recursos: {}",
|
||||
"backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automáticamente cualquier nuevo recurso sin necesidad de abrir la aplicación",
|
||||
"backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automática está desactivada",
|
||||
"backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automática está activada",
|
||||
"backup_controller_page_background_turn_off": "Desactivar el servicio en segundo plano",
|
||||
"backup_controller_page_background_turn_on": "Activar el servicio en segundo plano",
|
||||
"backup_controller_page_background_wifi": "Sólo en WiFi",
|
||||
"backup_controller_page_backup": "Respaldo",
|
||||
"backup_controller_page_backup_selected": "Seleccionado: ",
|
||||
"backup_controller_page_backup_sub": "Fotos y videos respaldados",
|
||||
"backup_controller_page_cancel": "Cancelar",
|
||||
"backup_controller_page_created": "Creado el: {}",
|
||||
"backup_controller_page_desc_backup": "Activa la copia de seguridad en primer plano para subir automáticamente nuevos recursos al servidor al abrir la aplicación.",
|
||||
"backup_controller_page_excluded": "Excluído: ",
|
||||
"backup_controller_page_failed": "Fallidos ({})",
|
||||
"backup_controller_page_filename": "Nombre del archivo: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Información del respaldo",
|
||||
"backup_controller_page_none_selected": "Ninguno seleccionado",
|
||||
"backup_controller_page_remainder": "Restante",
|
||||
"backup_controller_page_remainder_sub": "Fotos y videos restantes para hacer una copia de seguridad de la selección",
|
||||
"backup_controller_page_select": "Seleccionar",
|
||||
"backup_controller_page_server_storage": "Almacenamiento del servidor",
|
||||
"backup_controller_page_start_backup": "Iniciar respaldo",
|
||||
"backup_controller_page_status_off": "La copia de seguridad automática en primer plano está desactivada",
|
||||
"backup_controller_page_status_on": "La copia de seguridad automática en primer plano está activada",
|
||||
"backup_controller_page_storage_format": "{} de {} usado",
|
||||
"backup_controller_page_to_backup": "Álbumes a respaldar",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "Todas las fotos y videos únicos de los álbumes seleccionados",
|
||||
"backup_controller_page_turn_off": "Desactivar la copia de seguridad en primer plano",
|
||||
"backup_controller_page_turn_on": "Activar la copia de seguridad en primer plano",
|
||||
"backup_controller_page_uploading_file_info": "Subiendo información del archivo",
|
||||
"backup_err_only_album": "No se puede eliminar el único álbum",
|
||||
"backup_info_card_assets": "recursos",
|
||||
"backup_manual_cancelled": "Cancelado",
|
||||
"backup_manual_failed": "Fallido",
|
||||
"backup_manual_in_progress": "Subida ya en progreso. Inténtalo después de un tiempo",
|
||||
"backup_manual_success": "Exitoso",
|
||||
"backup_manual_title": "Estado de subida",
|
||||
"cache_settings_album_thumbnails": "Miniaturas de la página de la biblioteca ({} recursos)",
|
||||
"cache_settings_clear_cache_button": "Borrar caché",
|
||||
"cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.",
|
||||
"cache_settings_image_cache_size": "Tamaño de la caché de imágenes ({} recursos)",
|
||||
"cache_settings_statistics_album": "Miniaturas de la biblioteca",
|
||||
"cache_settings_statistics_assets": "{} recursos ({})",
|
||||
"cache_settings_statistics_full": "Imágenes completas",
|
||||
"cache_settings_statistics_shared": "Miniaturas de álbumes compartidos",
|
||||
"cache_settings_statistics_thumbnail": "Miniaturas",
|
||||
"cache_settings_statistics_title": "Uso de caché",
|
||||
"cache_settings_subtitle": "Controla el comportamiento de la caché de la aplicación móvil Immich",
|
||||
"cache_settings_thumbnail_size": "Tamaño de la caché de miniaturas ({} recursos)",
|
||||
"cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local",
|
||||
"cache_settings_tile_title": "Almacenamiento local",
|
||||
"cache_settings_title": "Configuración de la caché",
|
||||
"change_password_form_confirm_password": "Confirmar Contraseña",
|
||||
"change_password_form_description": "Hola {name},\n\nÉsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
|
||||
"change_password_form_new_password": "Nueva Contraseña",
|
||||
"change_password_form_password_mismatch": "Las contraseñas no coinciden",
|
||||
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",
|
||||
"common_add_to_album": "Agregar al álbum",
|
||||
"common_change_password": "Cambiar Contraseña",
|
||||
"common_create_new_album": "Crear nuevo álbum",
|
||||
"common_server_error": "Por favor, verifica tu conexión de red, asegúrate de que el servidor esté accesible y las versiones de la aplicación y del servidor sean compatibles.",
|
||||
"common_shared": "Compartido",
|
||||
"control_bottom_app_bar_add_to_album": "Agregar al álbum",
|
||||
"control_bottom_app_bar_album_info": "{} elementos",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elementos · Compartido",
|
||||
"control_bottom_app_bar_archive": "Archivar",
|
||||
"control_bottom_app_bar_create_new_album": "Crear nuevo álbum",
|
||||
"control_bottom_app_bar_delete": "Eliminar",
|
||||
"control_bottom_app_bar_favorite": "Favorito",
|
||||
"control_bottom_app_bar_share": "Compartir",
|
||||
"control_bottom_app_bar_share_to": "Compartir con",
|
||||
"control_bottom_app_bar_stack": "Apilar",
|
||||
"control_bottom_app_bar_unarchive": "Desarchivar",
|
||||
"control_bottom_app_bar_upload": "Subir",
|
||||
"create_album_page_untitled": "Sin título",
|
||||
"create_shared_album_page_create": "Crear",
|
||||
"create_shared_album_page_share": "Compartir",
|
||||
"create_shared_album_page_share_add_assets": "AGREGAR RECURSOS",
|
||||
"create_shared_album_page_share_select_photos": "Seleccionar fotos",
|
||||
"curated_location_page_title": "Lugares",
|
||||
"curated_object_page_title": "Objetos",
|
||||
"daily_title_text_date": "E, dd MMM",
|
||||
"daily_title_text_date_year": "E, dd de MMM, yyyy",
|
||||
"date_format": "E d, LLL y • h:mm a",
|
||||
"delete_dialog_alert": "Estos elementos se eliminarán permanentemente de Immich y de tu dispositivo",
|
||||
"delete_dialog_cancel": "Cancelar",
|
||||
"delete_dialog_ok": "Eliminar",
|
||||
"delete_dialog_title": "Eliminar permanentemente",
|
||||
"delete_shared_link_dialog_content": "¿Estás seguro de que quieres eliminar este enlace compartido?",
|
||||
"delete_shared_link_dialog_title": "Eliminar enlace compartido",
|
||||
"description_input_hint_text": "Agregar descripción...",
|
||||
"description_input_submit_error": "Error al actualizar la descripción, verifica el registro para obtener más detalles",
|
||||
"exif_bottom_sheet_description": "Agregar Descripción...",
|
||||
"exif_bottom_sheet_details": "DETALLES",
|
||||
"exif_bottom_sheet_location": "UBICACIÓN",
|
||||
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
|
||||
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
|
||||
"experimental_settings_subtitle": "¡Úsalo bajo tu propio riesgo!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos",
|
||||
"favorites_page_title": "Favoritos",
|
||||
"home_page_add_to_album_conflicts": "{added} recursos agregados al álbum {album}.\n{failed} recursos ya existen en el álbum.",
|
||||
"home_page_add_to_album_err_local": "Aún no se pueden agregar recursos locales a álbumes, omitiendo",
|
||||
"home_page_add_to_album_success": "{added} recursos agregados al álbum {album}.",
|
||||
"home_page_archive_err_local": "Aún no se pueden archivar recursos locales, omitiendo",
|
||||
"home_page_building_timeline": "Construyendo la línea de tiempo",
|
||||
"home_page_favorite_err_local": "Aún no se pueden marcar recursos locales como favoritos, omitiendo",
|
||||
"home_page_first_time_notice": "Si ésta es la primera vez que usas la app, por favor, asegúrate de elegir un álbum de respaldo para que la línea de tiempo pueda cargar fotos y videos en los álbumes.",
|
||||
"home_page_upload_err_limit": "Sólo se pueden subir un máximo de 30 recursos a la vez, omitiendo",
|
||||
"home_page_favorite_err_partner": "Aún no se pueden marcar recursos de compañeros como favoritos, omitiendo",
|
||||
"home_page_album_err_partner": "Aún no se pueden agregar recursos de compañeros a un álbum, omitiendo",
|
||||
"home_page_archive_err_partner": "Aún no se pueden archivar recursos de compañeros, omitiendo",
|
||||
"home_page_delete_err_partner": "Aún no se pueden eliminar recursos de compañeros, omitiendo",
|
||||
"image_viewer_page_state_provider_download_error": "Error de descarga",
|
||||
"image_viewer_page_state_provider_download_success": "Descarga exitosa",
|
||||
"image_viewer_page_state_provider_share_error": "Error al compartir",
|
||||
"library_page_albums": "Álbumes",
|
||||
"library_page_archive": "Archivo",
|
||||
"library_page_device_albums": "Álbumes en el dispositivo",
|
||||
"library_page_favorites": "Favoritos",
|
||||
"library_page_new_album": "Nuevo álbum",
|
||||
"library_page_sharing": "Compartiendo",
|
||||
"library_page_sort_created": "Creado más recientemente",
|
||||
"library_page_sort_last_modified": "Modificado más recientemente",
|
||||
"library_page_sort_most_recent_photo": "Foto más reciente",
|
||||
"library_page_sort_title": "Título del álbum",
|
||||
"login_disabled": "El inicio de sesión ha sido deshabilitado",
|
||||
"login_form_api_exception": "Excepción de API. Por favor, verifica la URL del servidor e inténtalo de nuevo.",
|
||||
"login_form_button_text": "Iniciar sesión",
|
||||
"login_form_email_hint": "tucorreo@correo.com",
|
||||
"login_form_endpoint_hint": "http://ip-de-tu-servidor:puerto/api",
|
||||
"login_form_endpoint_url": "URL del servidor",
|
||||
"login_form_err_http": "Por favor, especifique http:// o https://",
|
||||
"login_form_err_invalid_email": "Correo electrónico inválido",
|
||||
"login_form_err_invalid_url": "URL inválido",
|
||||
"login_form_err_leading_whitespace": "Espacio en blanco inicial",
|
||||
"login_form_err_trailing_whitespace": "Espacio en blanco al final",
|
||||
"login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, verifica la URL del servidor",
|
||||
"login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor",
|
||||
"login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña",
|
||||
"login_form_handshake_exception": "Hubo una excepción de handshake con el servidor. Habilita el soporte de certificado autofirmado en la configuración si estás usando un certificado autofirmado.",
|
||||
"login_form_label_email": "Correo electrónico",
|
||||
"login_form_label_password": "Contraseña",
|
||||
"login_form_next_button": "Siguiente",
|
||||
"login_form_password_hint": "contraseña",
|
||||
"login_form_save_login": "Permanecer conectado",
|
||||
"login_form_server_empty": "Introduce la URL del servidor.",
|
||||
"login_form_server_error": "No se pudo conectar al servidor.",
|
||||
"login_password_changed_error": "Hubo un error al actualizar tu contraseña",
|
||||
"login_password_changed_success": "Contraseña actualizada exitosamente",
|
||||
"map_cannot_get_user_location": "No se puede obtener la ubicación del usuario",
|
||||
"map_location_dialog_cancel": "Cancelar",
|
||||
"map_location_dialog_yes": "Sí",
|
||||
"map_location_service_disabled_content": "El servicio de ubicación debe estar habilitado para mostrar recursos desde tu ubicación actual. ¿Quieres habilitarlo ahora?",
|
||||
"map_location_service_disabled_title": "Servicio de ubicación deshabilitado",
|
||||
"map_no_assets_in_bounds": "No hay fotos en esta área",
|
||||
"map_no_location_permission_content": "Se necesita permiso de ubicación para mostrar recursos desde tu ubicación actual. ¿Quieres permitirlo ahora?",
|
||||
"map_no_location_permission_title": "Permiso de ubicación denegado",
|
||||
"map_settings_dark_mode": "Modo oscuro",
|
||||
"map_settings_dialog_cancel": "Cancelar",
|
||||
"map_settings_dialog_save": "Guardar",
|
||||
"map_settings_dialog_title": "Configuración del mapa",
|
||||
"map_settings_include_show_archived": "Incluir archivados",
|
||||
"map_settings_only_relative_range": "Rango de fechas",
|
||||
"map_settings_only_show_favorites": "Mostrar sólo favoritos",
|
||||
"map_zoom_to_see_photos": "Aleja el mapa para ver las fotos",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Fotos en movimiento",
|
||||
"notification_permission_dialog_cancel": "Cancelar",
|
||||
"notification_permission_dialog_content": "Para activar las notificaciones, ve a Configuración y selecciona permitir.",
|
||||
"notification_permission_dialog_settings": "Configuración",
|
||||
"notification_permission_list_tile_content": "Concede permiso para activar las notificaciones.",
|
||||
"notification_permission_list_tile_enable_button": "Activar notificaciones",
|
||||
"notification_permission_list_tile_title": "Permisos de notificación",
|
||||
"partner_page_add_partner": "Agregar compañero",
|
||||
"partner_page_empty_message": "Tus fotos aún no se han compartido con ningún compañero.",
|
||||
"partner_page_no_more_users": "No hay más usuarios para agregar",
|
||||
"partner_page_partner_add_failed": "Error al agregar compañero",
|
||||
"partner_page_select_partner": "Seleccionar compañero",
|
||||
"partner_page_shared_to_title": "Compartido con",
|
||||
"partner_page_stop_sharing_content": "{} ya no podrá acceder a tus fotos",
|
||||
"partner_page_stop_sharing_title": "¿Dejar de compartir tus fotos?",
|
||||
"partner_page_title": "Compañero",
|
||||
"permission_onboarding_continue_anyway": "Continuar de todos modos",
|
||||
"permission_onboarding_get_started": "Empezar",
|
||||
"permission_onboarding_go_to_settings": "Ir a configuración",
|
||||
"permission_onboarding_grant_permission": "Conceder permiso",
|
||||
"permission_onboarding_log_out": "Cerrar sesión",
|
||||
"permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, concede permisos de fotos y videos en Configuración.",
|
||||
"permission_onboarding_permission_granted": "¡Permiso concedido! Todo listo.",
|
||||
"permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich haga copia de seguridad y gestione toda tu colección de galería, concede permisos de fotos y videos en Configuración.",
|
||||
"permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.",
|
||||
"profile_drawer_app_logs": "Registros",
|
||||
"profile_drawer_client_server_up_to_date": "El cliente y el servidor están actualizados",
|
||||
"profile_drawer_documentation": "Documentación",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_settings": "Configuración",
|
||||
"profile_drawer_sign_out": "Cerrar sesión",
|
||||
"profile_drawer_trash": "Papelera",
|
||||
"recently_added_page_title": "Recién Agregados",
|
||||
"search_bar_hint": "Busca tus fotos",
|
||||
"search_page_categories": "Categorías",
|
||||
"search_page_favorites": "Favoritos",
|
||||
"search_page_motion_photos": "Fotos en .ovimiento",
|
||||
"search_page_no_objects": "No hay información de objetos disponible",
|
||||
"search_page_no_places": "No hay información de lugares disponible",
|
||||
"search_page_people": "Personas",
|
||||
"search_page_places": "Lugares",
|
||||
"search_page_recently_added": "Recién agregados",
|
||||
"search_page_screenshots": "Capturas de pantalla",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Cosas",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "Ver todo",
|
||||
"search_page_your_activity": "Tu actividad",
|
||||
"search_result_page_new_search_hint": "Nueva búsqueda",
|
||||
"search_suggestion_list_smart_search_hint_1": "La búsqueda inteligente está habilitada por defecto, para buscar metadatos utiliza la sintaxis ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:tu-término-de-búsqueda",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Sugerencias",
|
||||
"select_user_for_sharing_page_err_album": "Error al crear álbum",
|
||||
"select_user_for_sharing_page_share_suggestions": "Sugerencias",
|
||||
"server_info_box_app_version": "Versión de la Aplicación",
|
||||
"server_info_box_server_url": "URL del Servidor",
|
||||
"server_info_box_server_version": "Versión del Servidor",
|
||||
"setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).",
|
||||
"setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).",
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
"setting_notifications_notify_hours": "{} horas",
|
||||
"setting_notifications_notify_immediately": "inmediatamente",
|
||||
"setting_notifications_notify_minutes": "{} minutos",
|
||||
"setting_notifications_notify_never": "nunca",
|
||||
"setting_notifications_notify_seconds": "{} segundos",
|
||||
"setting_notifications_single_progress_subtitle": "Información detallada del progreso de subida de cada recurso",
|
||||
"setting_notifications_single_progress_title": "Mostrar progreso detallado de copia de seguridad en segundo plano",
|
||||
"setting_notifications_subtitle": "Ajusta tus preferencias de notificación",
|
||||
"setting_notifications_title": "Notificaciones",
|
||||
"setting_notifications_total_progress_subtitle": "Progreso general de subida (recursos completados/totales)",
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Configuración",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
"share_create_album": "Crear álbum",
|
||||
"shared_album_activities_input_disable": "Los comentarios están deshabilitados",
|
||||
"shared_album_activities_input_hint": "Di algo",
|
||||
"shared_album_activity_remove_content": "¿Quieres eliminar esta actividad?",
|
||||
"shared_album_activity_remove_title": "Eliminar actividad",
|
||||
"shared_album_activity_setting_subtitle": "Permitir que otros respondan",
|
||||
"shared_album_activity_setting_title": "Comentarios y me gusta",
|
||||
"share_dialog_preparing": "Preparando...",
|
||||
"shared_link_app_bar_title": "Enlaces compartidos",
|
||||
"shared_link_create_app_bar_title": "Crear enlace para compartir",
|
||||
"shared_link_create_info": "Permitir que cualquiera con el enlace vea la(s) foto(s) seleccionada(s)",
|
||||
"shared_link_create_submit_button": "Crear enlace",
|
||||
"shared_link_edit_allow_download": "Permitir que el usuario público pueda descargar",
|
||||
"shared_link_edit_allow_upload": "Permitir que el usuario público pueda subir",
|
||||
"shared_link_edit_app_bar_title": "Editar enlace",
|
||||
"shared_link_edit_change_expiry": "Cambiar tiempo de expiración",
|
||||
"shared_link_edit_description": "Descripción",
|
||||
"shared_link_edit_description_hint": "Introduce la descripción del enlace",
|
||||
"shared_link_edit_expire_after": "Expirar después de",
|
||||
"shared_link_edit_password": "Contraseña",
|
||||
"shared_link_edit_password_hint": "Introduce la contraseña del enlace",
|
||||
"shared_link_edit_show_meta": "Mostrar metadatos",
|
||||
"shared_link_edit_submit_button": "Actualizar enlace",
|
||||
"shared_link_empty": "No tienes ningún enlace compartido",
|
||||
"shared_link_manage_links": "Administrar enlaces compartidos",
|
||||
"share_done": "Hecho",
|
||||
"share_invite": "Invitar al álbum",
|
||||
"sharing_page_album": "Álbumes compartidos",
|
||||
"sharing_page_description": "Crea álbumes compartidos para compartir fotos y videos con personas de tu red.",
|
||||
"sharing_page_empty_list": "LISTA VACÍA",
|
||||
"sharing_silver_appbar_create_shared_album": "Crear álbum compartido",
|
||||
"sharing_silver_appbar_shared_links": "Enlaces compartidos",
|
||||
"sharing_silver_appbar_share_partner": "Compartir con compañero",
|
||||
"tab_controller_nav_library": "Biblioteca",
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Buscar",
|
||||
"tab_controller_nav_sharing": "Compartidos",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamiento en las miniaturas de los recursos",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Número de recursos por fila ({})",
|
||||
"theme_setting_dark_mode_switch": "Modo oscuro",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imágenes",
|
||||
"theme_setting_image_viewer_quality_title": "Calidad del visor de imágenes",
|
||||
"theme_setting_system_theme_switch": "Automático (seguir ajuste del sistema)",
|
||||
"theme_setting_theme_subtitle": "Elige la configuración del tema de la aplicación",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor",
|
||||
"theme_setting_three_stage_loading_title": "Activar carga en tres etapas",
|
||||
"translated_text_options": "Opciones",
|
||||
"trash_page_delete": "Eliminar",
|
||||
"trash_page_delete_all": "Eliminar todos",
|
||||
"trash_page_empty_trash_btn": "Vaciar papelera",
|
||||
"trash_page_empty_trash_dialog_content": "¿Quieres vaciar los recursos de la papelera? Estos elementos serán eliminados permanentemente de Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Los elementos en la papelera serán borrados permanentemente luego de {} días",
|
||||
"trash_page_no_assets": "No hay recursos en la papelera",
|
||||
"trash_page_restore": "Restaurar",
|
||||
"trash_page_restore_all": "Restaurar todos",
|
||||
"trash_page_select_assets_btn": "Seleccionar recursos",
|
||||
"trash_page_select_btn": "Seleccionar",
|
||||
"trash_page_title": "Papelera ({})",
|
||||
"upload_dialog_cancel": "Cancelar",
|
||||
"upload_dialog_info": "¿Quieres respaldar los recursos seleccionados en el servidor?",
|
||||
"upload_dialog_ok": "Subir",
|
||||
"upload_dialog_title": "Subir recurso",
|
||||
"version_announcement_overlay_ack": "Aceptar",
|
||||
"version_announcement_overlay_release_notes": "notas de la versión",
|
||||
"version_announcement_overlay_text_1": "Hola, amigo, hay una nueva versión de",
|
||||
"version_announcement_overlay_text_2": "por favor, tómate tu tiempo para visitar las ",
|
||||
"version_announcement_overlay_text_3": " y asegúrate de que la configuración de docker-compose y .env estén actualizadas para evitar cualquier error de configuración, especialmente si utilizas WatchTower o cualquier mecanismo que actualice automáticamente la aplicación del servidor.",
|
||||
"version_announcement_overlay_title": "Nueva versión del servidor disponible \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Eliminar de la pila",
|
||||
"viewer_stack_use_as_main_asset": "Utilizar como recurso principal",
|
||||
"viewer_unstack": "Desapilar"
|
||||
}
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
|
||||
"home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan",
|
||||
"home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.",
|
||||
"home_page_album_err_partner": "Kumppanin kohteita ei voi vielä lisätä albumiin. Hypätään yli",
|
||||
"home_page_archive_err_local": "Paikallisten kohteiden arkistointi ei ole mahdollista, ohitetaan",
|
||||
"home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida. Hypätään yli",
|
||||
"home_page_building_timeline": "Rakennetaan aikajanaa",
|
||||
"home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa.Hypätään yli",
|
||||
"home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan",
|
||||
"home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi. Hypätään yli",
|
||||
"home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan",
|
||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} ei voi enää käyttää kuviasi.",
|
||||
"partner_page_stop_sharing_title": "Lopetetaanko kuvien jakaminen?",
|
||||
"partner_page_title": "Kumppani",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Jatka silti",
|
||||
"permission_onboarding_get_started": "Aloittaminen",
|
||||
"permission_onboarding_go_to_settings": "Siirry asetuksiin",
|
||||
|
@ -309,12 +315,12 @@
|
|||
"share_add_photos": "Lisää kuvia",
|
||||
"share_add_title": "Lisää nimi",
|
||||
"share_create_album": "Luo albumi",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_activities_input_disable": "Kommentointi on kytketty pois päältä",
|
||||
"shared_album_activities_input_hint": "Sano jotain",
|
||||
"shared_album_activity_remove_content": "Haluatko poistaa tämän aktiviteetin?",
|
||||
"shared_album_activity_remove_title": "Poista aktiviteetti",
|
||||
"shared_album_activity_setting_subtitle": "Anna muiden vastata",
|
||||
"shared_album_activity_setting_title": "Kommentit ja tykkäykset",
|
||||
"share_dialog_preparing": "Valmistellaan...",
|
||||
"shared_link_app_bar_title": "Jaetut linkit",
|
||||
"shared_link_create_app_bar_title": "Luo linkki jaettavaksi",
|
||||
|
@ -326,7 +332,7 @@
|
|||
"shared_link_edit_change_expiry": "Muuta erääntymisaikaa",
|
||||
"shared_link_edit_description": "Kuvaus",
|
||||
"shared_link_edit_description_hint": "Lisää jaon kuvaus",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after": "Umpeutuu",
|
||||
"shared_link_edit_password": "Salasana",
|
||||
"shared_link_edit_password_hint": "Syötä jaon salasana",
|
||||
"shared_link_edit_show_meta": "Näytä metadata",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} éléments ajoutés à l'album {album}. Les éléments {failed} sont déjà dans l'album.",
|
||||
"home_page_add_to_album_err_local": "Impossible d'ajouter des éléments locaux aux albums pour le moment, étape ignorée",
|
||||
"home_page_add_to_album_success": "{added} éléments ajoutés à l'album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Impossible d'archiver les ressources locales pour l'instant, étape ignorée",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Construction de la chronologie",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Impossible d'ajouter des éléments locaux aux favoris pour le moment, étape ignorée",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée",
|
||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} ne pourra plus accéder à vos photos.",
|
||||
"partner_page_stop_sharing_title": "Arrêter de partager vos photos ?",
|
||||
"partner_page_title": "Partenaire",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continuer quand même",
|
||||
"permission_onboarding_get_started": "Commencer",
|
||||
"permission_onboarding_go_to_settings": "Accéder aux paramètres",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Helyi médiát még nem lehet albumba tenni. Kihagyjuk.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Helyi média archiválása még nem támogatott, úgyhogy kihagyjuk",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Helyi médiát még nem lehet a kedvencek közé tenni. Kihagyjuk.",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltölteni, átugrás",
|
||||
"image_viewer_page_state_provider_download_error": "Letöltési Hiba",
|
||||
"image_viewer_page_state_provider_download_success": "Letöltés Sikeres",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Folytatás mindenképp",
|
||||
"permission_onboarding_get_started": "Kezdjük el",
|
||||
"permission_onboarding_go_to_settings": "Beállítások megnyitása",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Aggiunti {added} elementi all'album {album}. {failed} elementi erano già presenti nell'album.",
|
||||
"home_page_add_to_album_err_local": "Non puoi aggiungere negli album foto ancora non caricate",
|
||||
"home_page_add_to_album_success": "Aggiunti {added} elementi all'album {album}",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Non puoi archiviare immagini non ancora caricate",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Costruendo il Timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Non puoi aggiungere tra i preferiti le foto ancora non caricate",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Se è la prima volta che usi l'app, assicurati di scegliere gli album per avere il Timeline con immagini e video",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Puoi caricare al massimo 30 file per volta, ignora quelli in eccesso",
|
||||
"image_viewer_page_state_provider_download_error": "Errore nel Download",
|
||||
"image_viewer_page_state_provider_download_success": "Download con successo",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} non sarà più in grado di accedere alle tue foto.",
|
||||
"partner_page_stop_sharing_title": "Stoppare la condivisione delle tue foto?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continua lo stesso",
|
||||
"permission_onboarding_get_started": "Inizia",
|
||||
"permission_onboarding_go_to_settings": "Vai a Impostazioni",
|
||||
|
@ -373,10 +379,10 @@
|
|||
"upload_dialog_ok": "Carica",
|
||||
"upload_dialog_title": "Carica file",
|
||||
"version_announcement_overlay_ack": "Presa visione",
|
||||
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||
"version_announcement_overlay_release_notes": "note di rilascio",
|
||||
"version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per visitare il",
|
||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore di configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico dell'applicativo",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per visitare le ",
|
||||
"version_announcement_overlay_text_3": " e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore di configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico dell'applicativo",
|
||||
"version_announcement_overlay_title": "Nuova versione del server disponibile \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{album}に{added}枚写真を追加しました。追加済みの{failed}枚はスキップしました。",
|
||||
"home_page_add_to_album_err_local": "まだアップロードされてない項目はアルバムに登録できません",
|
||||
"home_page_add_to_album_success": "{album}に{added}枚写真を追加しました",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "まだアップロードされてない項目はアーカイブできません",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "タイムライン構築中",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "まだアップロードされてない項目はお気に入り登録できません",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "はじめてアプリを使う場合、タイムラインに写真を表示するためにアルバムを選択してください",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "ダウンロード失敗",
|
||||
"image_viewer_page_state_provider_download_success": "ダウンロード成功",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "無視して続行",
|
||||
"permission_onboarding_get_started": "はじめる",
|
||||
"permission_onboarding_go_to_settings": "システム設定",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{album} 앨범에 {added} 미디어를 추가했습니다. {failed} 이미 앨범에 있는 항목입니다.",
|
||||
"home_page_add_to_album_err_local": "아직 앨범에 로컬 미디어를 추가할 수 없으므로 건너뜁니다",
|
||||
"home_page_add_to_album_success": "{album} 앨범에 {added} 미디어를 추가했습니다. ",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "아직 로컬 미디어를 보관할 수 없습니다",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "타임라인 생성",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "아직 로컬 미디어를 즐겨찾기에 추가할 수 없으므로 건너뜁니다",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "앱을 처음 사용하는 경우 타임라인이 앨범의 사진과 비디오를 채울 수 있도록 백업대상 앨범을 선택해야 합니다.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "한번에 최대 30개의 미디어만 업로드할 수 있습니다",
|
||||
"image_viewer_page_state_provider_download_error": "다운로드 에러",
|
||||
"image_viewer_page_state_provider_download_success": "다운로드 완료",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "더 이상 {}에서 사진에 액세스할 수 없습니다.",
|
||||
"partner_page_stop_sharing_title": "사진 공유를 중단하시겠습니까?",
|
||||
"partner_page_title": "파트너",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "어쨌든 계속하기",
|
||||
"permission_onboarding_get_started": "시작하기",
|
||||
"permission_onboarding_go_to_settings": "설정으로 이동",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Pievienoja {added} aktīvus albumam {album}. {failed} aktīvi jau ir albumā.",
|
||||
"home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_add_to_album_success": "Pievienoja {added} aktīvus albumam {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Tiek izveidota laika skala",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Vēl nevar pievienot izlaisei vietējos aktīvus, notiek izlaišana",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Ja šī ir pirmā reize, kad izmantojat aplikāciju, lūdzu, izvēlieties dublējuma albumu(s), lai laika skala varētu aizpildīt fotoattēlus un videoklipus albumā(os).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Lejupielādes Kļūda",
|
||||
"image_viewer_page_state_provider_download_success": "Lejupielāde Izdevās",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} vairs nevarēs piekļūt jūsu fotoattēliem.",
|
||||
"partner_page_stop_sharing_title": "Beigt kopīgot jūsu fotogrāfijas?",
|
||||
"partner_page_title": "Partneris",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Tomēr turpināt",
|
||||
"permission_onboarding_get_started": "Darba sākšana",
|
||||
"permission_onboarding_go_to_settings": "Doties uz iestatījumiem",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Lagt til {added} objekter til album {album}. {failed} objekter er allerede i albumet.",
|
||||
"home_page_add_to_album_err_local": "Kan ikke legge til lokale objekter til album enda, hopper over",
|
||||
"home_page_add_to_album_success": "Lagt til {added} objekter til album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Kan ikke arkivere lokale objekter enda, hopper over",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Genererer tidslinjen",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Kan ikke sette favoritt på lokale objekter enda, hopper over",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Maksimalt 30 objekter kan lastes opp om gangen, hopper over",
|
||||
"image_viewer_page_state_provider_download_error": "Nedlasting feilet",
|
||||
"image_viewer_page_state_provider_download_success": "Nedlasting vellykket",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} vil ikke lenger ha tilgang til dine bilder.",
|
||||
"partner_page_stop_sharing_title": "Stopp deling av bildene dine?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Fortsett uansett",
|
||||
"permission_onboarding_get_started": "Kom i gang",
|
||||
"permission_onboarding_go_to_settings": "Gå til innstillinger",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.",
|
||||
"home_page_add_to_album_err_local": "Lokale items kunnen nog niet aan albums worden toegevoegd, overslaan",
|
||||
"home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Lokale items kunnen nog niet gearchiveerd worden, overslaan",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Tijdlijn opbouwen",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Kan maximaal 30 assets tegelijk uploaden, overslaan",
|
||||
"image_viewer_page_state_provider_download_error": "Download mislukt",
|
||||
"image_viewer_page_state_provider_download_success": "Download succesvol",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} zal geen toegang meer hebben tot je fotos's.",
|
||||
"partner_page_stop_sharing_title": "Stoppen met het delen van je foto's?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Toch doorgaan",
|
||||
"permission_onboarding_get_started": "Aan de slag",
|
||||
"permission_onboarding_go_to_settings": "Ga naar instellingen",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Dodano {added} zasoby do albumu {album}. {failed} zasobów jest już w albumie.",
|
||||
"home_page_add_to_album_err_local": "Nie można dodawać zasobów lokalnych do albumów, pomijam",
|
||||
"home_page_add_to_album_success": "Dodano {added} zasoby do albumu {album}.",
|
||||
"home_page_album_err_partner": "Nie można jeszcze dodać zasobów partnera do albumu, pomijam",
|
||||
"home_page_archive_err_local": "Nie można jeszcze zarchiwizować zasobów lokalnych, pomijanie",
|
||||
"home_page_archive_err_partner": "Nie można zarchiwizować zasobów partnera, pomijam",
|
||||
"home_page_building_timeline": "Budowanie osi czasu",
|
||||
"home_page_delete_err_partner": "Nie można usunąć zasobów partnera, pomijam",
|
||||
"home_page_favorite_err_local": "Nie można dodać do ulubionych lokalnych zasobów, pomijam",
|
||||
"home_page_favorite_err_partner": "Nie można jeszcze dodać do ulubionych zasobów partnera, pomijam",
|
||||
"home_page_first_time_notice": "Jeśli korzystasz z aplikacji po raz pierwszy, pamiętaj o wybraniu albumów zapasowych, aby oś czasu mogła zapełnić zdjęcia i filmy w albumach.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie",
|
||||
"image_viewer_page_state_provider_download_error": "Błąd pobierania",
|
||||
"image_viewer_page_state_provider_download_success": "Pobieranie zakończone",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} nie będziesz już mieć dostępu do swoich zdjęć.",
|
||||
"partner_page_stop_sharing_title": "Przestać udostępniać swoje zdjęcia?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Kontynuuj mimo to",
|
||||
"permission_onboarding_get_started": "Rozpocznij",
|
||||
"permission_onboarding_go_to_settings": "Przejdź do ustawień",
|
||||
|
@ -326,7 +332,7 @@
|
|||
"shared_link_edit_change_expiry": "Zmień czas ważności",
|
||||
"shared_link_edit_description": "Opis",
|
||||
"shared_link_edit_description_hint": "Wprowadź opis udostępnienia",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after": "Wygasa po",
|
||||
"shared_link_edit_password": "Hasło",
|
||||
"shared_link_edit_password_hint": "Wprowadź hasło udostępniania",
|
||||
"shared_link_edit_show_meta": "Pokaż metadane",
|
||||
|
@ -358,7 +364,7 @@
|
|||
"translated_text_options": "Opcje",
|
||||
"trash_page_delete": "Usuń",
|
||||
"trash_page_delete_all": "Usuń wszystko",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_btn": "Opróżnij kosz",
|
||||
"trash_page_empty_trash_dialog_content": "Czy chcesz opróżnić swoje usunięte zasoby? Przedmioty te zostaną trwale usunięte z Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Elementy przeniesione do kosza zostaną trwale usunięte po {} dniach",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
|
||||
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
|
||||
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Построение временной шкалы",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Вы можете выгрузить максимум 30 файлов за раз",
|
||||
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
|
||||
"image_viewer_page_state_provider_download_success": "Успешно загружено",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} больше не сможет получить доступ к вашим фотографиям",
|
||||
"partner_page_stop_sharing_title": "Закрыть доступ партнёра к вашим фото?",
|
||||
"partner_page_title": "Партнёр",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Все равно продолжить",
|
||||
"permission_onboarding_get_started": "Давайте начнём",
|
||||
"permission_onboarding_go_to_settings": "Перейти в настройки",
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"album_viewer_appbar_share_err_title": "Nepodarilo sa zmeniť názov albumu",
|
||||
"album_viewer_appbar_share_leave": "Opustiť album",
|
||||
"album_viewer_appbar_share_remove": "Odstrániť z albumu",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_appbar_share_to": "Zdieľať s",
|
||||
"album_viewer_page_share_add_users": "Pridať používateľov",
|
||||
"all_people_page_title": "Ľudia",
|
||||
"all_videos_page_title": "Videá",
|
||||
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Pridané {added} položiek do albumu {album}. {failed} položiek už je v albume.",
|
||||
"home_page_add_to_album_err_local": "Zatiaľ nie je možné pridať lokálne média do albumov, preskakuje sa",
|
||||
"home_page_add_to_album_success": "Pridané {added} položky do albumu {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Zatiaľ nemožno archivovať lokálne médiá, preskakuje sa",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Vytváranie časovej osi",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Zatiaľ nie je možné zaradiť lokálne média medzi obľúbené, preskakuje sa",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Ak aplikáciu používate prvý krát, nezabudnite si vybrať zálohované albumy, aby sa na časovej osi mohli nachádzať fotografie a videá z vybraných albumoch.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Naraz môžete nahrať len 30 médií, preskakujem...",
|
||||
"image_viewer_page_state_provider_download_error": "Chyba sťahovania",
|
||||
"image_viewer_page_state_provider_download_success": "Sťahovanie bolo úspešné",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} už nebude mať prístup ku vašim fotkám.",
|
||||
"partner_page_stop_sharing_title": "Zastaviť zdieľanie vašich fotiek?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Pokračovať aj tak",
|
||||
"permission_onboarding_get_started": "Začať",
|
||||
"permission_onboarding_go_to_settings": "Prejsť do nastavení",
|
||||
|
@ -309,12 +315,12 @@
|
|||
"share_add_photos": "Pridať fotografie",
|
||||
"share_add_title": "Pridať názov",
|
||||
"share_create_album": "Vytvoriť album",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_activities_input_disable": "Komentár je zakázaný",
|
||||
"shared_album_activities_input_hint": "Napíšte niečo",
|
||||
"shared_album_activity_remove_content": "Chcete vymazať túto aktivitu?",
|
||||
"shared_album_activity_remove_title": "Vymazať aktivitu",
|
||||
"shared_album_activity_setting_subtitle": "Nechajte ostatných reagovať",
|
||||
"shared_album_activity_setting_title": "Komentáre a lajky",
|
||||
"share_dialog_preparing": "Pripravujem...",
|
||||
"shared_link_app_bar_title": "Zdieľané odkazy",
|
||||
"shared_link_create_app_bar_title": "Vytvoriť odkaz na zdieľanie",
|
||||
|
@ -326,7 +332,7 @@
|
|||
"shared_link_edit_change_expiry": "Zmeniť čas vypršania",
|
||||
"shared_link_edit_description": "Popis",
|
||||
"shared_link_edit_description_hint": "Zadajte popis zdieľania",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after": "Expiruje po",
|
||||
"shared_link_edit_password": "Heslo",
|
||||
"shared_link_edit_password_hint": "Zadajte heslo zdieľania",
|
||||
"shared_link_edit_show_meta": "Zobraziť metadáta",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Dodat {added} zapis u album {album}. {failed} zapisi su već u albumu ",
|
||||
"home_page_add_to_album_err_local": "Trenutno nemoguće dodati lokalne zapise u albume, preskacu se",
|
||||
"home_page_add_to_album_success": "Dodate {added} stavke u album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Kreiranje hronološke linije",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Trenutno nije moguce dodati lokalne zapise u favorite, preskacu se",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Ako je ovo prvi put da koristite aplikaciju, molimo Vas da odaberete albume koje želite da sačuvate",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Preuzimanje Neuspešno",
|
||||
"image_viewer_page_state_provider_download_success": "Preuzimanje Uspešno",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Lade till {added} foton och videor i albumet {album}. {failed} foton och videor finns redan i albumet.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Lade till {added} foton och videor i albumet {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Sluta dela dina foton?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Kom igång",
|
||||
"permission_onboarding_go_to_settings": "Gå till inställningar",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "เพิ่ม {added} ทรัพยากรเข้าอัลบั้ม {album}. {failed} ทรัพยากรอยู่ในอัลบั้มอยู่แล้ว",
|
||||
"home_page_add_to_album_err_local": " ไม่สามารถเพิ่มทรัพยากรบนเครื่องเข้าอัลบั้ม กำลังข้าม",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "ไม่สามารถเก็บถาวรในขณะนี้ กำลังข้าม",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "กำลังสร้าง timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": " ไม่สามารถตั้งทรัพยากรบนเครื่องเป็นรายการโปรด กำลังข้าม",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "ถ้าครั้งนี้เป็นครั้งแรกที่ใช้แอปนี้ กรุณาเลือกอัลบั้มที่จะสำรองข้อมูล ไทม์ไลน์จะได้เพิ่มรูปภาพและวิดีโอที่อยู่ในอัลบั้ม",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "สามารถอัพโหลดได้มากสุดครั้งละ 30 ทรัพยากร กำลังข้าม",
|
||||
"image_viewer_page_state_provider_download_error": "ดาวน์โหลดผิดพลาด",
|
||||
"image_viewer_page_state_provider_download_success": "ดาวน์โหลดสำเร็จ",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} จะไม่สามารถเข้าถึงรูปภาพของคุณ",
|
||||
"partner_page_stop_sharing_title": "หยุดแชร์รูปภาพ?",
|
||||
"partner_page_title": "พันธมิตร",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "ดำเนินการต่อ",
|
||||
"permission_onboarding_get_started": "เริ่มต้น",
|
||||
"permission_onboarding_go_to_settings": "ไปยังการตั้งค่า",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Додано {added} елементів у альбом {album}. {failed} елементів вже було в альбомі.",
|
||||
"home_page_add_to_album_err_local": "Неможливо додати локальні елементи до альбомів, пропущено",
|
||||
"home_page_add_to_album_success": "Додано {added} елементів у альбом {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Поки що неможливо заархівувати локальні елементи, пропущено",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Побудова хронології",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Неможливо отримати улюблені локальні елементи, пропущено",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Якщо ви вперше користуєтеся програмою, переконайтеся, що ви вибрали альбоми для резервування, щоб могти заповнювати хронологію знімків та відео в альбомах.",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Можна вантажити не більше 30 елементів водночас, пропущено",
|
||||
"image_viewer_page_state_provider_download_error": "Помилка завантаження",
|
||||
"image_viewer_page_state_provider_download_success": "Усіпшно завантажено",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} втратить доступ до ваших знімків.",
|
||||
"partner_page_stop_sharing_title": "Припинити надання ваших знімків?",
|
||||
"partner_page_title": "Партнер",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Все одно продовжити",
|
||||
"permission_onboarding_get_started": "Розпочати",
|
||||
"permission_onboarding_go_to_settings": "Перейти до налаштувань",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Building the timeline",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Stop sharing your photos?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Get started",
|
||||
"permission_onboarding_go_to_settings": "Go to settings",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "已向相册 {album} 中添加 {added} 项。\n其中 {failed} 项在相册中已存在。",
|
||||
"home_page_add_to_album_err_local": "暂不能将本地项目添加到相册中,跳过",
|
||||
"home_page_add_to_album_success": "已向相册 {album} 中添加 {added} 项。",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "暂无法归档本地项目,跳过",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "正在生成时间线",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "暂不能收藏本地项目,跳过",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "如果这是您第一次使用该应用程序,请确保选择一个要备份的本地相册,以便可以在时间线中预览该相册中的照片和视频。",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过",
|
||||
"image_viewer_page_state_provider_download_error": "下载出现错误",
|
||||
"image_viewer_page_state_provider_download_success": "下载成功",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} 将无法再访问您的照片。",
|
||||
"partner_page_stop_sharing_title": "您确定要停止共享您的照片吗?",
|
||||
"partner_page_title": "同伴",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "仍然继续",
|
||||
"permission_onboarding_get_started": "开始使用",
|
||||
"permission_onboarding_go_to_settings": "转到设置",
|
||||
|
|
|
@ -170,10 +170,15 @@
|
|||
"home_page_add_to_album_conflicts": "已向相册 {album} 中添加 {added} 项。\n其中 {failed} 项在相册中已存在。",
|
||||
"home_page_add_to_album_err_local": "暂不能将本地资项目添加到相册中,跳过",
|
||||
"home_page_add_to_album_success": "已向相册 {album} 中添加 {added} 项。",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "暂无法归档本地项目,跳过",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "正在生成时间线",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_favorite_err_local": "暂不能收藏本地项目,跳过",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "如果这是您第一次使用该应用程序,请确保选择一个要备份的本地相册,以便可以在时间线中预览该相册中的照片和视频。",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过",
|
||||
"image_viewer_page_state_provider_download_error": "下载出现错误",
|
||||
"image_viewer_page_state_provider_download_success": "下载成功",
|
||||
|
@ -245,6 +250,7 @@
|
|||
"partner_page_stop_sharing_content": "{} 将无法再访问您的照片。",
|
||||
"partner_page_stop_sharing_title": "您确定要停止共享您的照片吗?",
|
||||
"partner_page_title": "同伴",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "仍然继续",
|
||||
"permission_onboarding_get_started": "开始使用",
|
||||
"permission_onboarding_go_to_settings": "转到设置",
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
<string>es</string>
|
||||
<string>vi</string>
|
||||
<string>fr</string>
|
||||
<string>fr</string>
|
||||
<string>ja</string>
|
||||
<string>pl</string>
|
||||
<string>fi</string>
|
||||
|
@ -44,15 +43,10 @@
|
|||
<string>mn</string>
|
||||
<string>ko</string>
|
||||
<string>sr</string>
|
||||
<string>sr</string>
|
||||
<string>hi</string>
|
||||
<string>es</string>
|
||||
<string>es</string>
|
||||
<string>sv</string>
|
||||
<string>ca</string>
|
||||
<string>hu</string>
|
||||
<string>lv</string>
|
||||
<string>zh</string>
|
||||
<string>th</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
|
|
|
@ -30,6 +30,7 @@ const List<Locale> locales = [
|
|||
Locale('hi', 'IN'),
|
||||
Locale('es', 'PE'),
|
||||
Locale('es', 'MX'),
|
||||
Locale('es', 'US'),
|
||||
Locale('sv', 'FI'),
|
||||
Locale('ca', 'CA'),
|
||||
Locale('hu', 'HU'),
|
||||
|
|
|
@ -44,6 +44,7 @@ class Store {
|
|||
|
||||
/// Stores the value synchronously in the cache and asynchronously in the DB
|
||||
static Future<void> put<T>(StoreKey<T> key, T value) {
|
||||
if (_cache[key.id] == value) return Future.value();
|
||||
_cache[key.id] = value;
|
||||
return _db.writeTxn(
|
||||
() async => _db.storeValues.put(await StoreValue._of(value, key)),
|
||||
|
@ -52,6 +53,7 @@ class Store {
|
|||
|
||||
/// Removes the value synchronously from the cache and asynchronously from the DB
|
||||
static Future<void> delete<T>(StoreKey<T> key) {
|
||||
if (_cache[key.id] == null) return Future.value();
|
||||
_cache[key.id] = null;
|
||||
return _db.writeTxn(() => _db.storeValues.delete(key.id));
|
||||
}
|
||||
|
|
3
mobile/openapi/.openapi-generator/FILES
generated
3
mobile/openapi/.openapi-generator/FILES
generated
|
@ -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
|
||||
|
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
|
@ -116,6 +116,7 @@ Class | Method | HTTP request | Description
|
|||
*AssetApi* | [**restoreTrash**](doc//AssetApi.md#restoretrash) | **POST** /asset/trash/restore |
|
||||
*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs |
|
||||
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
||||
*AssetApi* | [**searchAssets**](doc//AssetApi.md#searchassets) | **GET** /assets |
|
||||
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
|
||||
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} |
|
||||
*AssetApi* | [**updateAssets**](doc//AssetApi.md#updateassets) | **PUT** /asset |
|
||||
|
@ -229,6 +230,7 @@ Class | Method | HTTP request | Description
|
|||
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
|
||||
- [AssetJobName](doc//AssetJobName.md)
|
||||
- [AssetJobsDto](doc//AssetJobsDto.md)
|
||||
- [AssetOrder](doc//AssetOrder.md)
|
||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
|
||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||
|
|
134
mobile/openapi/doc/AssetApi.md
generated
134
mobile/openapi/doc/AssetApi.md
generated
|
@ -34,6 +34,7 @@ Method | HTTP request | Description
|
|||
[**restoreTrash**](AssetApi.md#restoretrash) | **POST** /asset/trash/restore |
|
||||
[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs |
|
||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||
[**searchAssets**](AssetApi.md#searchassets) | **GET** /assets |
|
||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
|
||||
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} |
|
||||
[**updateAssets**](AssetApi.md#updateassets) | **PUT** /asset |
|
||||
|
@ -1477,6 +1478,139 @@ Name | Type | Description | Notes
|
|||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **searchAssets**
|
||||
> List<AssetResponseDto> 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)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
// TODO Configure API key authorization: cookie
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
||||
// TODO Configure API key authorization: api_key
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
||||
// TODO Configure HTTP Bearer authorization: bearer
|
||||
// Case 1. Use String Token
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
||||
// Case 2. Use Function which generate token.
|
||||
// String yourTokenGeneratorFunction() { ... }
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final type = ; // AssetTypeEnum |
|
||||
final order = ; // AssetOrder |
|
||||
final deviceAssetId = deviceAssetId_example; // String |
|
||||
final deviceId = deviceId_example; // String |
|
||||
final checksum = checksum_example; // String |
|
||||
final isArchived = true; // bool |
|
||||
final isEncoded = true; // bool |
|
||||
final isExternal = true; // bool |
|
||||
final isFavorite = true; // bool |
|
||||
final isMotion = true; // bool |
|
||||
final isOffline = true; // bool |
|
||||
final isReadOnly = true; // bool |
|
||||
final isVisible = true; // bool |
|
||||
final withDeleted = true; // bool |
|
||||
final withStacked = true; // bool |
|
||||
final withExif = true; // bool |
|
||||
final withPeople = true; // bool |
|
||||
final createdBefore = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final createdAfter = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime |
|
||||
final originalFileName = originalFileName_example; // String |
|
||||
final originalPath = originalPath_example; // String |
|
||||
final resizePath = resizePath_example; // String |
|
||||
final webpPath = webpPath_example; // String |
|
||||
final encodedVideoPath = encodedVideoPath_example; // String |
|
||||
final city = city_example; // String |
|
||||
final state = state_example; // String |
|
||||
final country = country_example; // String |
|
||||
final make = make_example; // String |
|
||||
final model = model_example; // String |
|
||||
final lensModel = lensModel_example; // String |
|
||||
final page = 8.14; // num |
|
||||
final size = 8.14; // num |
|
||||
|
||||
try {
|
||||
final result = api_instance.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);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->searchAssets: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**id** | **String**| | [optional]
|
||||
**libraryId** | **String**| | [optional]
|
||||
**type** | [**AssetTypeEnum**](.md)| | [optional]
|
||||
**order** | [**AssetOrder**](.md)| | [optional]
|
||||
**deviceAssetId** | **String**| | [optional]
|
||||
**deviceId** | **String**| | [optional]
|
||||
**checksum** | **String**| | [optional]
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**isEncoded** | **bool**| | [optional]
|
||||
**isExternal** | **bool**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isMotion** | **bool**| | [optional]
|
||||
**isOffline** | **bool**| | [optional]
|
||||
**isReadOnly** | **bool**| | [optional]
|
||||
**isVisible** | **bool**| | [optional]
|
||||
**withDeleted** | **bool**| | [optional]
|
||||
**withStacked** | **bool**| | [optional]
|
||||
**withExif** | **bool**| | [optional]
|
||||
**withPeople** | **bool**| | [optional]
|
||||
**createdBefore** | **DateTime**| | [optional]
|
||||
**createdAfter** | **DateTime**| | [optional]
|
||||
**updatedBefore** | **DateTime**| | [optional]
|
||||
**updatedAfter** | **DateTime**| | [optional]
|
||||
**trashedBefore** | **DateTime**| | [optional]
|
||||
**trashedAfter** | **DateTime**| | [optional]
|
||||
**takenBefore** | **DateTime**| | [optional]
|
||||
**takenAfter** | **DateTime**| | [optional]
|
||||
**originalFileName** | **String**| | [optional]
|
||||
**originalPath** | **String**| | [optional]
|
||||
**resizePath** | **String**| | [optional]
|
||||
**webpPath** | **String**| | [optional]
|
||||
**encodedVideoPath** | **String**| | [optional]
|
||||
**city** | **String**| | [optional]
|
||||
**state** | **String**| | [optional]
|
||||
**country** | **String**| | [optional]
|
||||
**make** | **String**| | [optional]
|
||||
**model** | **String**| | [optional]
|
||||
**lensModel** | **String**| | [optional]
|
||||
**page** | **num**| | [optional]
|
||||
**size** | **num**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**List<AssetResponseDto>**](AssetResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **serveFile**
|
||||
> MultipartFile serveFile(id, isThumb, isWeb, key)
|
||||
|
||||
|
|
14
mobile/openapi/doc/AssetOrder.md
generated
Normal file
14
mobile/openapi/doc/AssetOrder.md
generated
Normal file
|
@ -0,0 +1,14 @@
|
|||
# openapi.model.AssetOrder
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
|
@ -68,6 +68,7 @@ part 'model/asset_ids_dto.dart';
|
|||
part 'model/asset_ids_response_dto.dart';
|
||||
part 'model/asset_job_name.dart';
|
||||
part 'model/asset_jobs_dto.dart';
|
||||
part 'model/asset_order.dart';
|
||||
part 'model/asset_response_dto.dart';
|
||||
part 'model/asset_stats_response_dto.dart';
|
||||
part 'model/asset_type_enum.dart';
|
||||
|
|
327
mobile/openapi/lib/api/asset_api.dart
generated
327
mobile/openapi/lib/api/asset_api.dart
generated
|
@ -1475,6 +1475,333 @@ class AssetApi {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /assets' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
///
|
||||
/// * [AssetOrder] order:
|
||||
///
|
||||
/// * [String] deviceAssetId:
|
||||
///
|
||||
/// * [String] deviceId:
|
||||
///
|
||||
/// * [String] checksum:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
///
|
||||
/// * [bool] isExternal:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
///
|
||||
/// * [bool] isReadOnly:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
///
|
||||
/// * [bool] withPeople:
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
///
|
||||
/// * [String] originalFileName:
|
||||
///
|
||||
/// * [String] originalPath:
|
||||
///
|
||||
/// * [String] resizePath:
|
||||
///
|
||||
/// * [String] webpPath:
|
||||
///
|
||||
/// * [String] encodedVideoPath:
|
||||
///
|
||||
/// * [String] city:
|
||||
///
|
||||
/// * [String] state:
|
||||
///
|
||||
/// * [String] country:
|
||||
///
|
||||
/// * [String] make:
|
||||
///
|
||||
/// * [String] model:
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
///
|
||||
/// * [num] page:
|
||||
///
|
||||
/// * [num] size:
|
||||
Future<Response> searchAssetsWithHttpInfo({ String? id, String? libraryId, AssetTypeEnum? type, AssetOrder? order, String? deviceAssetId, String? deviceId, String? checksum, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, bool? withDeleted, bool? withStacked, bool? withExif, bool? withPeople, DateTime? createdBefore, DateTime? createdAfter, DateTime? updatedBefore, DateTime? updatedAfter, DateTime? trashedBefore, DateTime? trashedAfter, DateTime? takenBefore, DateTime? takenAfter, String? originalFileName, String? originalPath, String? resizePath, String? webpPath, String? encodedVideoPath, String? city, String? state, String? country, String? make, String? model, String? lensModel, num? page, num? size, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/assets';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (id != null) {
|
||||
queryParams.addAll(_queryParams('', 'id', id));
|
||||
}
|
||||
if (libraryId != null) {
|
||||
queryParams.addAll(_queryParams('', 'libraryId', libraryId));
|
||||
}
|
||||
if (type != null) {
|
||||
queryParams.addAll(_queryParams('', 'type', type));
|
||||
}
|
||||
if (order != null) {
|
||||
queryParams.addAll(_queryParams('', 'order', order));
|
||||
}
|
||||
if (deviceAssetId != null) {
|
||||
queryParams.addAll(_queryParams('', 'deviceAssetId', deviceAssetId));
|
||||
}
|
||||
if (deviceId != null) {
|
||||
queryParams.addAll(_queryParams('', 'deviceId', deviceId));
|
||||
}
|
||||
if (checksum != null) {
|
||||
queryParams.addAll(_queryParams('', 'checksum', checksum));
|
||||
}
|
||||
if (isArchived != null) {
|
||||
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
|
||||
}
|
||||
if (isEncoded != null) {
|
||||
queryParams.addAll(_queryParams('', 'isEncoded', isEncoded));
|
||||
}
|
||||
if (isExternal != null) {
|
||||
queryParams.addAll(_queryParams('', 'isExternal', isExternal));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isMotion != null) {
|
||||
queryParams.addAll(_queryParams('', 'isMotion', isMotion));
|
||||
}
|
||||
if (isOffline != null) {
|
||||
queryParams.addAll(_queryParams('', 'isOffline', isOffline));
|
||||
}
|
||||
if (isReadOnly != null) {
|
||||
queryParams.addAll(_queryParams('', 'isReadOnly', isReadOnly));
|
||||
}
|
||||
if (isVisible != null) {
|
||||
queryParams.addAll(_queryParams('', 'isVisible', isVisible));
|
||||
}
|
||||
if (withDeleted != null) {
|
||||
queryParams.addAll(_queryParams('', 'withDeleted', withDeleted));
|
||||
}
|
||||
if (withStacked != null) {
|
||||
queryParams.addAll(_queryParams('', 'withStacked', withStacked));
|
||||
}
|
||||
if (withExif != null) {
|
||||
queryParams.addAll(_queryParams('', 'withExif', withExif));
|
||||
}
|
||||
if (withPeople != null) {
|
||||
queryParams.addAll(_queryParams('', 'withPeople', withPeople));
|
||||
}
|
||||
if (createdBefore != null) {
|
||||
queryParams.addAll(_queryParams('', 'createdBefore', createdBefore));
|
||||
}
|
||||
if (createdAfter != null) {
|
||||
queryParams.addAll(_queryParams('', 'createdAfter', createdAfter));
|
||||
}
|
||||
if (updatedBefore != null) {
|
||||
queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore));
|
||||
}
|
||||
if (updatedAfter != null) {
|
||||
queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter));
|
||||
}
|
||||
if (trashedBefore != null) {
|
||||
queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore));
|
||||
}
|
||||
if (trashedAfter != null) {
|
||||
queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter));
|
||||
}
|
||||
if (takenBefore != null) {
|
||||
queryParams.addAll(_queryParams('', 'takenBefore', takenBefore));
|
||||
}
|
||||
if (takenAfter != null) {
|
||||
queryParams.addAll(_queryParams('', 'takenAfter', takenAfter));
|
||||
}
|
||||
if (originalFileName != null) {
|
||||
queryParams.addAll(_queryParams('', 'originalFileName', originalFileName));
|
||||
}
|
||||
if (originalPath != null) {
|
||||
queryParams.addAll(_queryParams('', 'originalPath', originalPath));
|
||||
}
|
||||
if (resizePath != null) {
|
||||
queryParams.addAll(_queryParams('', 'resizePath', resizePath));
|
||||
}
|
||||
if (webpPath != null) {
|
||||
queryParams.addAll(_queryParams('', 'webpPath', webpPath));
|
||||
}
|
||||
if (encodedVideoPath != null) {
|
||||
queryParams.addAll(_queryParams('', 'encodedVideoPath', encodedVideoPath));
|
||||
}
|
||||
if (city != null) {
|
||||
queryParams.addAll(_queryParams('', 'city', city));
|
||||
}
|
||||
if (state != null) {
|
||||
queryParams.addAll(_queryParams('', 'state', state));
|
||||
}
|
||||
if (country != null) {
|
||||
queryParams.addAll(_queryParams('', 'country', country));
|
||||
}
|
||||
if (make != null) {
|
||||
queryParams.addAll(_queryParams('', 'make', make));
|
||||
}
|
||||
if (model != null) {
|
||||
queryParams.addAll(_queryParams('', 'model', model));
|
||||
}
|
||||
if (lensModel != null) {
|
||||
queryParams.addAll(_queryParams('', 'lensModel', lensModel));
|
||||
}
|
||||
if (page != null) {
|
||||
queryParams.addAll(_queryParams('', 'page', page));
|
||||
}
|
||||
if (size != null) {
|
||||
queryParams.addAll(_queryParams('', 'size', size));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id:
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
///
|
||||
/// * [AssetOrder] order:
|
||||
///
|
||||
/// * [String] deviceAssetId:
|
||||
///
|
||||
/// * [String] deviceId:
|
||||
///
|
||||
/// * [String] checksum:
|
||||
///
|
||||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
///
|
||||
/// * [bool] isExternal:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
///
|
||||
/// * [bool] isReadOnly:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
///
|
||||
/// * [bool] withStacked:
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
///
|
||||
/// * [bool] withPeople:
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
///
|
||||
/// * [String] originalFileName:
|
||||
///
|
||||
/// * [String] originalPath:
|
||||
///
|
||||
/// * [String] resizePath:
|
||||
///
|
||||
/// * [String] webpPath:
|
||||
///
|
||||
/// * [String] encodedVideoPath:
|
||||
///
|
||||
/// * [String] city:
|
||||
///
|
||||
/// * [String] state:
|
||||
///
|
||||
/// * [String] country:
|
||||
///
|
||||
/// * [String] make:
|
||||
///
|
||||
/// * [String] model:
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
///
|
||||
/// * [num] page:
|
||||
///
|
||||
/// * [num] size:
|
||||
Future<List<AssetResponseDto>?> searchAssets({ String? id, String? libraryId, AssetTypeEnum? type, AssetOrder? order, String? deviceAssetId, String? deviceId, String? checksum, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, bool? withDeleted, bool? withStacked, bool? withExif, bool? withPeople, DateTime? createdBefore, DateTime? createdAfter, DateTime? updatedBefore, DateTime? updatedAfter, DateTime? trashedBefore, DateTime? trashedAfter, DateTime? takenBefore, DateTime? takenAfter, String? originalFileName, String? originalPath, String? resizePath, String? webpPath, String? encodedVideoPath, String? city, String? state, String? country, String? make, String? model, String? lensModel, num? page, num? size, }) async {
|
||||
final response = await searchAssetsWithHttpInfo( id: id, libraryId: libraryId, type: type, order: order, deviceAssetId: deviceAssetId, deviceId: deviceId, checksum: checksum, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, withDeleted: withDeleted, withStacked: withStacked, withExif: withExif, withPeople: withPeople, createdBefore: createdBefore, createdAfter: createdAfter, updatedBefore: updatedBefore, updatedAfter: updatedAfter, trashedBefore: trashedBefore, trashedAfter: trashedAfter, takenBefore: takenBefore, takenAfter: takenAfter, originalFileName: originalFileName, originalPath: originalPath, resizePath: resizePath, webpPath: webpPath, encodedVideoPath: encodedVideoPath, city: city, state: state, country: country, make: make, model: model, lensModel: lensModel, page: page, size: size, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
|
||||
.cast<AssetResponseDto>()
|
||||
.toList();
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /asset/file/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
|
|
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
|
@ -225,6 +225,8 @@ class ApiClient {
|
|||
return AssetJobNameTypeTransformer().decode(value);
|
||||
case 'AssetJobsDto':
|
||||
return AssetJobsDto.fromJson(value);
|
||||
case 'AssetOrder':
|
||||
return AssetOrderTypeTransformer().decode(value);
|
||||
case 'AssetResponseDto':
|
||||
return AssetResponseDto.fromJson(value);
|
||||
case 'AssetStatsResponseDto':
|
||||
|
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
|
@ -58,6 +58,9 @@ String parameterToString(dynamic value) {
|
|||
if (value is AssetJobName) {
|
||||
return AssetJobNameTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetOrder) {
|
||||
return AssetOrderTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetTypeEnum) {
|
||||
return AssetTypeEnumTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
|
85
mobile/openapi/lib/model/asset_order.dart
generated
Normal file
85
mobile/openapi/lib/model/asset_order.dart
generated
Normal file
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class AssetOrder {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetOrder._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const asc = AssetOrder._(r'asc');
|
||||
static const desc = AssetOrder._(r'desc');
|
||||
|
||||
/// List of all possible values in this [enum][AssetOrder].
|
||||
static const values = <AssetOrder>[
|
||||
asc,
|
||||
desc,
|
||||
];
|
||||
|
||||
static AssetOrder? fromJson(dynamic value) => AssetOrderTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetOrder>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetOrder>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetOrder.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetOrder] to String,
|
||||
/// and [decode] dynamic data back to [AssetOrder].
|
||||
class AssetOrderTypeTransformer {
|
||||
factory AssetOrderTypeTransformer() => _instance ??= const AssetOrderTypeTransformer._();
|
||||
|
||||
const AssetOrderTypeTransformer._();
|
||||
|
||||
String encode(AssetOrder data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetOrder.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetOrder? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'asc': return AssetOrder.asc;
|
||||
case r'desc': return AssetOrder.desc;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetOrderTypeTransformer] instance.
|
||||
static AssetOrderTypeTransformer? _instance;
|
||||
}
|
||||
|
5
mobile/openapi/test/asset_api_test.dart
generated
5
mobile/openapi/test/asset_api_test.dart
generated
|
@ -152,6 +152,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<AssetResponseDto>> searchAssets({ String id, String libraryId, AssetTypeEnum type, AssetOrder order, String deviceAssetId, String deviceId, String checksum, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, bool withDeleted, bool withStacked, bool withExif, bool withPeople, DateTime createdBefore, DateTime createdAfter, DateTime updatedBefore, DateTime updatedAfter, DateTime trashedBefore, DateTime trashedAfter, DateTime takenBefore, DateTime takenAfter, String originalFileName, String originalPath, String resizePath, String webpPath, String encodedVideoPath, String city, String state, String country, String make, String model, String lensModel, num page, num size }) async
|
||||
test('test searchAssets', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<MultipartFile> serveFile(String id, { bool isThumb, bool isWeb, String key }) async
|
||||
test('test serveFile', () async {
|
||||
// TODO
|
||||
|
|
21
mobile/openapi/test/asset_order_test.dart
generated
Normal file
21
mobile/openapi/test/asset_order_test.dart
generated
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AssetOrder
|
||||
void main() {
|
||||
|
||||
group('test AssetOrder', () {
|
||||
|
||||
});
|
||||
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --max_old_space_size=4096' jest --config test/e2e/jest-e2e.json --runInBand",
|
||||
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config test/e2e/jest-e2e.json --runInBand",
|
||||
"typeorm": "typeorm",
|
||||
"typeorm:migrations:create": "typeorm migration:create",
|
||||
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js",
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './partner.dto';
|
||||
export * from './partner.service';
|
||||
|
|
|
@ -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[]>;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,33 @@ import request from 'supertest';
|
|||
|
||||
type UploadDto = Partial<CreateAssetDto> & { content?: Buffer };
|
||||
|
||||
const asset = {
|
||||
deviceAssetId: 'test-1',
|
||||
deviceId: 'test',
|
||||
fileCreatedAt: new Date(),
|
||||
fileModifiedAt: new Date(),
|
||||
};
|
||||
|
||||
export const assetApi = {
|
||||
create: async (
|
||||
server: any,
|
||||
accessToken: string,
|
||||
dto?: Omit<CreateAssetDto, 'assetData'>,
|
||||
): Promise<AssetResponseDto> => {
|
||||
dto = dto || asset;
|
||||
const { status, body } = await request(server)
|
||||
.post(`/asset/upload`)
|
||||
.field('deviceAssetId', dto.deviceAssetId)
|
||||
.field('deviceId', dto.deviceId)
|
||||
.field('fileCreatedAt', dto.fileCreatedAt.toISOString())
|
||||
.field('fileModifiedAt', dto.fileModifiedAt.toISOString())
|
||||
.attach('assetData', randomBytes(32), 'example.jpg')
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect([200, 201].includes(status)).toBe(true);
|
||||
|
||||
return body as AssetResponseDto;
|
||||
},
|
||||
get: async (server: any, accessToken: string, id: string): Promise<AssetResponseDto> => {
|
||||
const { body, status } = await request(server)
|
||||
.get(`/asset/assetById/${id}`)
|
||||
|
|
|
@ -3,6 +3,7 @@ import { albumApi } from './album-api';
|
|||
import { assetApi } from './asset-api';
|
||||
import { authApi } from './auth-api';
|
||||
import { libraryApi } from './library-api';
|
||||
import { partnerApi } from './partner-api';
|
||||
import { sharedLinkApi } from './shared-link-api';
|
||||
import { userApi } from './user-api';
|
||||
|
||||
|
@ -14,4 +15,5 @@ export const api = {
|
|||
sharedLinkApi,
|
||||
albumApi,
|
||||
userApi,
|
||||
partnerApi,
|
||||
};
|
||||
|
|
10
server/test/api/partner-api.ts
Normal file
10
server/test/api/partner-api.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { PartnerResponseDto } from '@app/domain';
|
||||
import request from 'supertest';
|
||||
|
||||
export const partnerApi = {
|
||||
create: async (server: any, accessToken: string, id: string) => {
|
||||
const { status, body } = await request(server).post(`/partner/${id}`).set('Authorization', `Bearer ${accessToken}`);
|
||||
expect(status).toBe(201);
|
||||
return body as PartnerResponseDto;
|
||||
},
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
import { dataSource } from '@app/infra';
|
||||
|
||||
export const db = {
|
||||
reset: async () => {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize();
|
||||
}
|
||||
|
||||
await dataSource.transaction(async (em) => {
|
||||
for (const entity of dataSource.entityMetadatas) {
|
||||
if (entity.tableName === 'users') {
|
||||
continue;
|
||||
}
|
||||
await em.query(`DELETE FROM ${entity.tableName} CASCADE;`);
|
||||
}
|
||||
await em.query(`DELETE FROM "users" CASCADE;`);
|
||||
});
|
||||
},
|
||||
disconnect: async () => {
|
||||
if (dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,9 +1,9 @@
|
|||
import { AlbumResponseDto, LoginResponseDto, ReactionType } from '@app/domain';
|
||||
import { ActivityController } from '@app/immich';
|
||||
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
||||
import { ActivityEntity } from '@app/infra/entities';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub, uuidStub } from '@test/fixtures';
|
||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
||||
|
@ -12,9 +12,23 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
let admin: LoginResponseDto;
|
||||
let asset: AssetFileUploadResponseDto;
|
||||
let album: AlbumResponseDto;
|
||||
let nonOwner: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
[server] = await testApp.create();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
asset = await api.assetApi.upload(server, admin.accessToken, 'example');
|
||||
|
||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
||||
nonOwner = await api.authApi.login(server, userDto.user1);
|
||||
|
||||
album = await api.albumApi.create(server, admin.accessToken, {
|
||||
albumName: 'Album 1',
|
||||
assetIds: [asset.id],
|
||||
sharedWithUserIds: [nonOwner.userId],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -22,11 +36,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
asset = await api.assetApi.upload(server, admin.accessToken, 'example');
|
||||
album = await api.albumApi.create(server, admin.accessToken, { albumName: 'Album 1', assetIds: [asset.id] });
|
||||
await testApp.reset({ entities: [ActivityEntity] });
|
||||
});
|
||||
|
||||
describe('GET /activity', () => {
|
||||
|
@ -347,13 +357,6 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should let the owner remove a comment by another user', async () => {
|
||||
const { id: userId } = await api.userApi.create(server, admin.accessToken, {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
});
|
||||
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
|
||||
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
|
||||
const reaction = await api.activityApi.create(server, nonOwner.accessToken, {
|
||||
albumId: album.id,
|
||||
type: ReactionType.COMMENT,
|
||||
|
@ -363,17 +366,11 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
const { status } = await request(server)
|
||||
.delete(`/activity/${reaction.id}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toEqual(204);
|
||||
});
|
||||
|
||||
it('should not let a user remove a comment by another user', async () => {
|
||||
const { id: userId } = await api.userApi.create(server, admin.accessToken, {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
});
|
||||
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
|
||||
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
|
||||
const reaction = await api.activityApi.create(server, admin.accessToken, {
|
||||
albumId: album.id,
|
||||
type: ReactionType.COMMENT,
|
||||
|
@ -383,18 +380,12 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
const { status, body } = await request(server)
|
||||
.delete(`/activity/${reaction.id}`)
|
||||
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest('Not found or no activity.delete access'));
|
||||
});
|
||||
|
||||
it('should let a non-owner remove their own comment', async () => {
|
||||
const { id: userId } = await api.userApi.create(server, admin.accessToken, {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
});
|
||||
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
|
||||
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
|
||||
const reaction = await api.activityApi.create(server, nonOwner.accessToken, {
|
||||
albumId: album.id,
|
||||
type: ReactionType.COMMENT,
|
||||
|
@ -404,6 +395,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||
const { status } = await request(server)
|
||||
.delete(`/activity/${reaction.id}`)
|
||||
.set('Authorization', `Bearer ${nonOwner.accessToken}`);
|
||||
|
||||
expect(status).toBe(204);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,8 +3,7 @@ import { AlbumController } from '@app/immich';
|
|||
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
|
||||
import { SharedLinkType } from '@app/infra/entities';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub, uuidStub } from '@test/fixtures';
|
||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
||||
|
@ -33,26 +32,18 @@ describe(`${AlbumController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
|
||||
await Promise.all([
|
||||
api.userApi.create(server, admin.accessToken, {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
}),
|
||||
api.userApi.create(server, admin.accessToken, {
|
||||
email: 'user2@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 2',
|
||||
}),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
||||
]);
|
||||
|
||||
[user1, user2] = await Promise.all([
|
||||
api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }),
|
||||
api.authApi.login(server, { email: 'user2@immich.app', password: 'Password123' }),
|
||||
api.authApi.login(server, userDto.user1),
|
||||
api.authApi.login(server, userDto.user2),
|
||||
]);
|
||||
|
||||
user1Asset = await api.assetApi.upload(server, user1.accessToken, 'example');
|
||||
|
|
|
@ -4,32 +4,24 @@ 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 { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { DateTime } from 'luxon';
|
||||
import request from 'supertest';
|
||||
|
||||
const user1Dto = {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
};
|
||||
|
||||
const user2Dto = {
|
||||
email: 'user2@immich.app',
|
||||
password: 'Password123',
|
||||
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> = {
|
||||
|
@ -49,81 +41,521 @@ 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);
|
||||
|
||||
await testApp.reset();
|
||||
|
||||
await api.authApi.adminSignUp(server);
|
||||
const admin = await api.authApi.adminLogin(server);
|
||||
|
||||
await Promise.all([
|
||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
||||
]);
|
||||
|
||||
[user1, user2] = await Promise.all([
|
||||
api.authApi.login(server, userDto.user1),
|
||||
api.authApi.login(server, userDto.user2),
|
||||
]);
|
||||
|
||||
const [user1Libraries, user2Libraries] = await Promise.all([
|
||||
api.libraryApi.getAll(server, user1.accessToken),
|
||||
api.libraryApi.getAll(server, user2.accessToken),
|
||||
]);
|
||||
|
||||
libraries = [...user1Libraries, ...user2Libraries];
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await testApp.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(),
|
||||
}),
|
||||
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([
|
||||
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();
|
||||
});
|
||||
|
||||
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),
|
||||
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')),
|
||||
]);
|
||||
|
||||
sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
assetIds: [asset1.id, asset2.id],
|
||||
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', () => {
|
||||
|
@ -369,8 +801,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 +812,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 +822,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 +832,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 +842,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 +864,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 +874,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 +938,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 +1014,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 +1083,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 +1141,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 +1184,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}`)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { AuthController } from '@app/immich';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import {
|
||||
adminSignupStub,
|
||||
changePasswordStub,
|
||||
|
@ -49,7 +48,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
const response = await api.authApi.adminLogin(server);
|
||||
accessToken = response.accessToken;
|
||||
|
@ -57,7 +56,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
|||
|
||||
describe('POST /auth/admin-sign-up', () => {
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
});
|
||||
|
||||
const invalid = [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { LoginResponseDto } from '@app/domain';
|
||||
import { AssetType, LibraryType } from '@app/infra/entities';
|
||||
import { api } from '@test/api';
|
||||
import { IMMICH_TEST_ASSET_PATH, db, runAllTests, testApp } from '@test/test-utils';
|
||||
import { IMMICH_TEST_ASSET_PATH, runAllTests, testApp } from '@test/test-utils';
|
||||
|
||||
describe(`Supported file formats (e2e)`, () => {
|
||||
let server: any;
|
||||
|
@ -176,7 +176,7 @@ describe(`Supported file formats (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/');
|
||||
|
|
|
@ -2,28 +2,16 @@ import { LibraryResponseDto, LoginResponseDto } from '@app/domain';
|
|||
import { LibraryController } from '@app/immich';
|
||||
import { AssetType, LibraryType } from '@app/infra/entities';
|
||||
import { api } from '@test/api';
|
||||
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, db, restoreTempFolder, testApp } from '@test/test-utils';
|
||||
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from '@test/test-utils';
|
||||
import * as fs from 'fs';
|
||||
import request from 'supertest';
|
||||
import { utimes } from 'utimes';
|
||||
import { errorStub, uuidStub } from '../fixtures';
|
||||
import { errorStub, userDto, uuidStub } from '../fixtures';
|
||||
|
||||
describe(`${LibraryController.name} (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
|
||||
const user1Dto = {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
};
|
||||
|
||||
const user2Dto = {
|
||||
email: 'user2@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 2',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
[server] = await testApp.create({ jobs: true });
|
||||
});
|
||||
|
@ -34,7 +22,7 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await restoreTempFolder();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
|
@ -195,8 +183,8 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should allow a user to create a library', async () => {
|
||||
await api.userApi.create(server, admin.accessToken, user1Dto);
|
||||
const user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
|
||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
||||
const user1 = await api.authApi.login(server, userDto.user1);
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.post('/library')
|
||||
|
@ -337,11 +325,15 @@ describe(`${LibraryController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it("should not allow getting another user's library", async () => {
|
||||
await api.userApi.create(server, admin.accessToken, user1Dto);
|
||||
const user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
|
||||
await Promise.all([
|
||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
||||
]);
|
||||
|
||||
await api.userApi.create(server, admin.accessToken, user2Dto);
|
||||
const user2 = await api.authApi.login(server, { email: user2Dto.email, password: user2Dto.password });
|
||||
const [user1, user2] = await Promise.all([
|
||||
api.authApi.login(server, userDto.user1),
|
||||
api.authApi.login(server, userDto.user2),
|
||||
]);
|
||||
|
||||
const library = await api.libraryApi.create(server, user1.accessToken, { type: LibraryType.EXTERNAL });
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { OAuthController } from '@app/immich';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
@ -17,15 +16,11 @@ describe(`${OAuthController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
});
|
||||
|
||||
describe('POST /oauth/authorize', () => {
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
});
|
||||
|
||||
it(`should throw an error if a redirect uri is not provided`, async () => {
|
||||
const { status, body } = await request(server).post('/oauth/authorize').send({});
|
||||
expect(status).toBe(400);
|
||||
|
|
|
@ -1,59 +1,45 @@
|
|||
import { IPartnerRepository, LoginResponseDto, PartnerDirection } from '@app/domain';
|
||||
import { LoginResponseDto, PartnerDirection } from '@app/domain';
|
||||
import { PartnerController } from '@app/immich';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub } from '@test/fixtures';
|
||||
import { errorStub, userDto } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
||||
const user1Dto = {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
};
|
||||
|
||||
const user2Dto = {
|
||||
email: 'user2@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 2',
|
||||
};
|
||||
|
||||
describe(`${PartnerController.name} (e2e)`, () => {
|
||||
let app: INestApplication;
|
||||
let server: any;
|
||||
let loginResponse: LoginResponseDto;
|
||||
let accessToken: string;
|
||||
let repository: IPartnerRepository;
|
||||
let user1: LoginResponseDto;
|
||||
let user2: LoginResponseDto;
|
||||
let user3: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
[server, app] = await testApp.create();
|
||||
repository = app.get<IPartnerRepository>(IPartnerRepository);
|
||||
[server] = await testApp.create();
|
||||
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
const admin = await api.authApi.adminLogin(server);
|
||||
|
||||
await Promise.all([
|
||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user3),
|
||||
]);
|
||||
|
||||
[user1, user2, user3] = await Promise.all([
|
||||
api.authApi.login(server, userDto.user1),
|
||||
api.authApi.login(server, userDto.user2),
|
||||
api.authApi.login(server, userDto.user3),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
api.partnerApi.create(server, user1.accessToken, user2.userId),
|
||||
api.partnerApi.create(server, user2.accessToken, user1.userId),
|
||||
]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testApp.teardown();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
loginResponse = await api.authApi.adminLogin(server);
|
||||
accessToken = loginResponse.accessToken;
|
||||
|
||||
await Promise.all([
|
||||
api.userApi.create(server, accessToken, user1Dto),
|
||||
api.userApi.create(server, accessToken, user2Dto),
|
||||
]);
|
||||
|
||||
[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 }),
|
||||
]);
|
||||
});
|
||||
|
||||
describe('GET /partner', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get('/partner');
|
||||
|
@ -63,7 +49,6 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should get all partners shared by user', async () => {
|
||||
await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId });
|
||||
const { status, body } = await request(server)
|
||||
.get('/partner')
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
|
@ -74,7 +59,6 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should get all partners that share with user', async () => {
|
||||
await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId });
|
||||
const { status, body } = await request(server)
|
||||
.get('/partner')
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
|
@ -87,7 +71,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
describe('POST /partner/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).post(`/partner/${user2.userId}`);
|
||||
const { status, body } = await request(server).post(`/partner/${user3.userId}`);
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
|
@ -95,15 +79,14 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
it('should share with new partner', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.post(`/partner/${user2.userId}`)
|
||||
.post(`/partner/${user3.userId}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(201);
|
||||
expect(body).toEqual(expect.objectContaining({ id: user2.userId }));
|
||||
expect(body).toEqual(expect.objectContaining({ id: user3.userId }));
|
||||
});
|
||||
|
||||
it('should not share with new partner if already sharing with this partner', async () => {
|
||||
await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId });
|
||||
const { status, body } = await request(server)
|
||||
.post(`/partner/${user2.userId}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
@ -122,7 +105,6 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should update partner', async () => {
|
||||
await repository.create({ sharedById: user2.userId, sharedWithId: user1.userId });
|
||||
const { status, body } = await request(server)
|
||||
.put(`/partner/${user2.userId}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
|
@ -135,16 +117,15 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
describe('DELETE /partner/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).delete(`/partner/${user2.userId}`);
|
||||
const { status, body } = await request(server).delete(`/partner/${user3.userId}`);
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
});
|
||||
|
||||
it('should delete partner', async () => {
|
||||
await repository.create({ sharedById: user1.userId, sharedWithId: user2.userId });
|
||||
const { status } = await request(server)
|
||||
.delete(`/partner/${user2.userId}`)
|
||||
.delete(`/partner/${user3.userId}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
|
@ -152,7 +133,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
it('should throw a bad request if partner not found', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.delete(`/partner/${user2.userId}`)
|
||||
.delete(`/partner/${user3.userId}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
|
|
|
@ -3,7 +3,6 @@ import { PersonController } from '@app/immich';
|
|||
import { PersonEntity } from '@app/infra/entities';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub, uuidStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
@ -27,7 +26,7 @@ describe(`${PersonController.name}`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
loginResponse = await api.authApi.adminLogin(server);
|
||||
accessToken = loginResponse.accessToken;
|
||||
|
|
|
@ -1,31 +1,29 @@
|
|||
import { LoginResponseDto } from '@app/domain';
|
||||
import { ServerInfoController } from '@app/immich';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub } from '@test/fixtures';
|
||||
import { errorStub, userDto } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
||||
describe(`${ServerInfoController.name} (e2e)`, () => {
|
||||
let server: any;
|
||||
let accessToken: string;
|
||||
let loginResponse: LoginResponseDto;
|
||||
let admin: LoginResponseDto;
|
||||
let nonAdmin: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
[server] = await testApp.create();
|
||||
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
||||
nonAdmin = await api.authApi.login(server, userDto.user1);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testApp.teardown();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
loginResponse = await api.authApi.adminLogin(server);
|
||||
accessToken = loginResponse.accessToken;
|
||||
});
|
||||
|
||||
describe('GET /server-info', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get('/server-info');
|
||||
|
@ -34,7 +32,9 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should return the disk information', async () => {
|
||||
const { status, body } = await request(server).get('/server-info').set('Authorization', `Bearer ${accessToken}`);
|
||||
const { status, body } = await request(server)
|
||||
.get('/server-info')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
diskAvailable: expect.any(String),
|
||||
|
@ -110,12 +110,9 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should only work for admins', async () => {
|
||||
const loginDto = { email: 'test@immich.app', password: 'Immich123' };
|
||||
await api.userApi.create(server, accessToken, { ...loginDto, name: 'test' });
|
||||
const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto);
|
||||
const { status, body } = await request(server)
|
||||
.get('/server-info/statistics')
|
||||
.set('Authorization', `Bearer ${userAccessToken}`);
|
||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
||||
expect(status).toBe(403);
|
||||
expect(body).toEqual(errorStub.forbidden);
|
||||
});
|
||||
|
@ -123,7 +120,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
|||
it('should return the server stats', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.get('/server-info/statistics')
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
photos: 0,
|
||||
|
@ -133,7 +130,14 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
|
|||
photos: 0,
|
||||
usage: 0,
|
||||
userName: 'Immich Admin',
|
||||
userId: loginResponse.userId,
|
||||
userId: admin.userId,
|
||||
videos: 0,
|
||||
},
|
||||
{
|
||||
photos: 0,
|
||||
usage: 0,
|
||||
userName: 'User 1',
|
||||
userId: nonAdmin.userId,
|
||||
videos: 0,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,48 +1,113 @@
|
|||
import { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@app/domain';
|
||||
import { PartnerController } from '@app/immich';
|
||||
import { LibraryType, SharedLinkType } from '@app/infra/entities';
|
||||
import {
|
||||
AlbumResponseDto,
|
||||
AssetResponseDto,
|
||||
IAssetRepository,
|
||||
LoginResponseDto,
|
||||
SharedLinkResponseDto,
|
||||
} from '@app/domain';
|
||||
import { SharedLinkController } from '@app/immich';
|
||||
import { SharedLinkType } from '@app/infra/entities';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub, uuidStub } from '@test/fixtures';
|
||||
import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from '@test/test-utils';
|
||||
import { cp } from 'fs/promises';
|
||||
import { errorStub, userDto, uuidStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import { DateTime } from 'luxon';
|
||||
import request from 'supertest';
|
||||
|
||||
const user1Dto = {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
};
|
||||
|
||||
describe(`${PartnerController.name} (e2e)`, () => {
|
||||
describe(`${SharedLinkController.name} (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
let asset1: AssetResponseDto;
|
||||
let asset2: AssetResponseDto;
|
||||
let user1: LoginResponseDto;
|
||||
let user2: LoginResponseDto;
|
||||
let album: AlbumResponseDto;
|
||||
let sharedLink: SharedLinkResponseDto;
|
||||
let metadataAlbum: AlbumResponseDto;
|
||||
let deletedAlbum: AlbumResponseDto;
|
||||
let linkWithDeletedAlbum: SharedLinkResponseDto;
|
||||
let linkWithPassword: SharedLinkResponseDto;
|
||||
let linkWithAlbum: SharedLinkResponseDto;
|
||||
let linkWithAssets: SharedLinkResponseDto;
|
||||
let linkWithMetadata: SharedLinkResponseDto;
|
||||
let linkWithoutMetadata: SharedLinkResponseDto;
|
||||
let app: INestApplication<any>;
|
||||
|
||||
beforeAll(async () => {
|
||||
[server] = await testApp.create({ jobs: true });
|
||||
[server, app] = await testApp.create();
|
||||
const assetRepository = app.get<IAssetRepository>(IAssetRepository);
|
||||
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
|
||||
await Promise.all([
|
||||
api.userApi.create(server, admin.accessToken, userDto.user1),
|
||||
api.userApi.create(server, admin.accessToken, userDto.user2),
|
||||
]);
|
||||
|
||||
[user1, user2] = await Promise.all([
|
||||
api.authApi.login(server, userDto.user1),
|
||||
api.authApi.login(server, userDto.user2),
|
||||
]);
|
||||
|
||||
[asset1, asset2] = await Promise.all([
|
||||
api.assetApi.create(server, user1.accessToken),
|
||||
api.assetApi.create(server, user1.accessToken),
|
||||
]);
|
||||
|
||||
await assetRepository.upsertExif({
|
||||
assetId: asset1.id,
|
||||
longitude: -108.400968333333,
|
||||
latitude: 39.115,
|
||||
orientation: '1',
|
||||
dateTimeOriginal: DateTime.fromISO('2022-01-10T19:15:44.310Z').toJSDate(),
|
||||
timeZone: 'UTC-4',
|
||||
state: 'Mesa County, Colorado',
|
||||
country: 'United States of America',
|
||||
});
|
||||
|
||||
[album, deletedAlbum, metadataAlbum] = await Promise.all([
|
||||
api.albumApi.create(server, user1.accessToken, { albumName: 'album' }),
|
||||
api.albumApi.create(server, user2.accessToken, { albumName: 'deleted album' }),
|
||||
api.albumApi.create(server, user1.accessToken, { albumName: 'metadata album', assetIds: [asset1.id] }),
|
||||
]);
|
||||
|
||||
[linkWithDeletedAlbum, linkWithAlbum, linkWithAssets, linkWithPassword, linkWithMetadata, linkWithoutMetadata] =
|
||||
await Promise.all([
|
||||
api.sharedLinkApi.create(server, user2.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: deletedAlbum.id,
|
||||
}),
|
||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
}),
|
||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
assetIds: [asset1.id],
|
||||
}),
|
||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
password: 'foo',
|
||||
}),
|
||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: metadataAlbum.id,
|
||||
showMetadata: true,
|
||||
}),
|
||||
api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: metadataAlbum.id,
|
||||
showMetadata: false,
|
||||
}),
|
||||
]);
|
||||
|
||||
await api.userApi.delete(server, admin.accessToken, user2.userId);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testApp.teardown();
|
||||
await restoreTempFolder();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
|
||||
await api.userApi.create(server, admin.accessToken, user1Dto);
|
||||
user1 = await api.authApi.login(server, { email: user1Dto.email, password: user1Dto.password });
|
||||
|
||||
album = await api.albumApi.create(server, user1.accessToken, { albumName: 'shared with link' });
|
||||
sharedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /shared-link', () => {
|
||||
|
@ -59,7 +124,16 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual([expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM })]);
|
||||
expect(body).toHaveLength(5);
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: linkWithAlbum.id }),
|
||||
expect.objectContaining({ id: linkWithAssets.id }),
|
||||
expect.objectContaining({ id: linkWithPassword.id }),
|
||||
expect.objectContaining({ id: linkWithMetadata.id }),
|
||||
expect.objectContaining({ id: linkWithoutMetadata.id }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not get shared links created by other users', async () => {
|
||||
|
@ -82,67 +156,92 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should get data for correct shared link', async () => {
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: sharedLink.key });
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithAlbum.key });
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM }));
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
album,
|
||||
userId: user1.userId,
|
||||
type: SharedLinkType.ALBUM,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return unauthorized for incorrect shared link', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.get('/shared-link/me')
|
||||
.query({ key: sharedLink.key + 'foo' });
|
||||
.query({ key: linkWithAlbum.key + 'foo' });
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.invalidShareKey);
|
||||
});
|
||||
|
||||
it('should return unauthorized if target has been soft deleted', async () => {
|
||||
const softDeletedAlbum = await api.albumApi.create(server, user1.accessToken, { albumName: 'shared with link' });
|
||||
const softDeletedAlbumLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: softDeletedAlbum.id,
|
||||
});
|
||||
await api.userApi.delete(server, admin.accessToken, user1.userId);
|
||||
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: softDeletedAlbumLink.key });
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithDeletedAlbum.key });
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.invalidShareKey);
|
||||
});
|
||||
|
||||
it('should return unauthorized for password protected link', async () => {
|
||||
const passwordProtectedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
password: 'foo',
|
||||
});
|
||||
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: passwordProtectedLink.key });
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithPassword.key });
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.invalidSharePassword);
|
||||
});
|
||||
|
||||
it('should get data for correct password protected link', async () => {
|
||||
const passwordProtectedLink = await api.sharedLinkApi.create(server, user1.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
password: 'foo',
|
||||
});
|
||||
|
||||
const { status, body } = await request(server)
|
||||
.get('/shared-link/me')
|
||||
.query({ key: passwordProtectedLink.key, password: 'foo' });
|
||||
.query({ key: linkWithPassword.key, password: 'foo' });
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(expect.objectContaining({ album, userId: user1.userId, type: SharedLinkType.ALBUM }));
|
||||
});
|
||||
|
||||
it('should return metadata for album shared link', async () => {
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithMetadata.key });
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body.assets).toHaveLength(1);
|
||||
expect(body.assets[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
originalFileName: 'example',
|
||||
localDateTime: expect.any(String),
|
||||
fileCreatedAt: expect.any(String),
|
||||
exifInfo: expect.objectContaining({
|
||||
longitude: -108.400968333333,
|
||||
latitude: 39.115,
|
||||
orientation: '1',
|
||||
dateTimeOriginal: expect.any(String),
|
||||
timeZone: 'UTC-4',
|
||||
state: 'Mesa County, Colorado',
|
||||
country: 'United States of America',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(body.album).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not return metadata for album shared link without metadata', async () => {
|
||||
const { status, body } = await request(server).get('/shared-link/me').query({ key: linkWithoutMetadata.key });
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body.assets).toHaveLength(1);
|
||||
expect(body.album).toBeDefined();
|
||||
|
||||
const asset = body.assets[0];
|
||||
expect(asset).not.toHaveProperty('exifInfo');
|
||||
expect(asset).not.toHaveProperty('fileCreatedAt');
|
||||
expect(asset).not.toHaveProperty('originalFilename');
|
||||
expect(asset).not.toHaveProperty('originalPath');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /shared-link/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get(`/shared-link/${sharedLink.id}`);
|
||||
const { status, body } = await request(server).get(`/shared-link/${linkWithAlbum.id}`);
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
|
@ -150,7 +249,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
it('should get shared link by id', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.get(`/shared-link/${sharedLink.id}`)
|
||||
.get(`/shared-link/${linkWithAlbum.id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
|
@ -159,7 +258,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
it('should not get shared link by id if user has not created the link or it does not exist', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.get(`/shared-link/${sharedLink.id}`)
|
||||
.get(`/shared-link/${linkWithAlbum.id}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
|
@ -220,7 +319,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
describe('PATCH /shared-link/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.patch(`/shared-link/${sharedLink.id}`)
|
||||
.patch(`/shared-link/${linkWithAlbum.id}`)
|
||||
.send({ description: 'foo' });
|
||||
|
||||
expect(status).toBe(401);
|
||||
|
@ -239,7 +338,7 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
|
||||
it('should update shared link', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.patch(`/shared-link/${sharedLink.id}`)
|
||||
.patch(`/shared-link/${linkWithAlbum.id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ description: 'foo' });
|
||||
|
||||
|
@ -250,9 +349,53 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /shared-link/:id/assets', () => {
|
||||
it('should not add assets to shared link (album)', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/shared-link/${linkWithAlbum.id}/assets`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ assetIds: [asset2.id] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest('Invalid shared link type'));
|
||||
});
|
||||
|
||||
it('should add an assets to a shared link (individual)', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/shared-link/${linkWithAssets.id}/assets`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ assetIds: [asset2.id] });
|
||||
|
||||
expect(body).toEqual([{ assetId: asset2.id, success: true }]);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /shared-link/:id/assets', () => {
|
||||
it('should not remove assets from a shared link (album)', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.delete(`/shared-link/${linkWithAlbum.id}/assets`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ assetIds: [asset2.id] });
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorStub.badRequest('Invalid shared link type'));
|
||||
});
|
||||
|
||||
it('should remove assets from a shared link (individual)', async () => {
|
||||
const { status, body } = await request(server)
|
||||
.delete(`/shared-link/${linkWithAssets.id}/assets`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ assetIds: [asset2.id] });
|
||||
|
||||
expect(body).toEqual([{ assetId: asset2.id, success: true }]);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /shared-link/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).delete(`/shared-link/${sharedLink.id}`);
|
||||
const { status, body } = await request(server).delete(`/shared-link/${linkWithAlbum.id}`);
|
||||
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorStub.unauthorized);
|
||||
|
@ -267,89 +410,12 @@ describe(`${PartnerController.name} (e2e)`, () => {
|
|||
expect(body).toEqual(errorStub.badRequest());
|
||||
});
|
||||
|
||||
it('should update shared link', async () => {
|
||||
it('should delete a shared link', async () => {
|
||||
const { status } = await request(server)
|
||||
.delete(`/shared-link/${sharedLink.id}`)
|
||||
.delete(`/shared-link/${linkWithAlbum.id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shared link metadata', () => {
|
||||
beforeEach(async () => {
|
||||
await restoreTempFolder();
|
||||
|
||||
await cp(
|
||||
`${IMMICH_TEST_ASSET_PATH}/metadata/gps-position/thompson-springs.jpg`,
|
||||
`${IMMICH_TEST_ASSET_TEMP_PATH}/thompson-springs.jpg`,
|
||||
);
|
||||
|
||||
await api.userApi.setExternalPath(server, admin.accessToken, admin.userId, '/');
|
||||
|
||||
const library = await api.libraryApi.create(server, admin.accessToken, {
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: [`${IMMICH_TEST_ASSET_TEMP_PATH}`],
|
||||
});
|
||||
|
||||
await api.libraryApi.scanLibrary(server, admin.accessToken, library.id);
|
||||
|
||||
const assets = await api.assetApi.getAllAssets(server, admin.accessToken);
|
||||
|
||||
expect(assets).toHaveLength(1);
|
||||
|
||||
album = await api.albumApi.create(server, admin.accessToken, { albumName: 'New album' });
|
||||
await api.albumApi.addAssets(server, admin.accessToken, album.id, { ids: [assets[0].id] });
|
||||
});
|
||||
|
||||
it('should return metadata for album shared link', async () => {
|
||||
const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
});
|
||||
|
||||
const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key);
|
||||
|
||||
expect(returnedLink.assets).toHaveLength(1);
|
||||
expect(returnedLink.album).toBeDefined();
|
||||
|
||||
const returnedAsset = returnedLink.assets[0];
|
||||
expect(returnedAsset).toEqual(
|
||||
expect.objectContaining({
|
||||
originalFileName: 'thompson-springs',
|
||||
resized: true,
|
||||
localDateTime: '2022-01-10T15:15:44.310Z',
|
||||
fileCreatedAt: '2022-01-10T19:15:44.310Z',
|
||||
exifInfo: expect.objectContaining({
|
||||
longitude: -108.400968333333,
|
||||
latitude: 39.115,
|
||||
orientation: '1',
|
||||
dateTimeOriginal: '2022-01-10T19:15:44.310Z',
|
||||
timeZone: 'UTC-4',
|
||||
state: 'Mesa County, Colorado',
|
||||
country: 'United States of America',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not return metadata for album shared link without metadata', async () => {
|
||||
const sharedLink = await api.sharedLinkApi.create(server, admin.accessToken, {
|
||||
type: SharedLinkType.ALBUM,
|
||||
albumId: album.id,
|
||||
showMetadata: false,
|
||||
});
|
||||
|
||||
const returnedLink = await api.sharedLinkApi.getMySharedLink(server, sharedLink.key);
|
||||
|
||||
expect(returnedLink.assets).toHaveLength(1);
|
||||
expect(returnedLink.album).toBeDefined();
|
||||
|
||||
const returnedAsset = returnedLink.assets[0];
|
||||
expect(returnedAsset).not.toHaveProperty('exifInfo');
|
||||
expect(returnedAsset).not.toHaveProperty('fileCreatedAt');
|
||||
expect(returnedAsset).not.toHaveProperty('originalFilename');
|
||||
expect(returnedAsset).not.toHaveProperty('originalPath');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import { LoginResponseDto } from '@app/domain';
|
||||
import { SystemConfigController } from '@app/immich';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub } from '@test/fixtures';
|
||||
import { errorStub, userDto } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
|
||||
describe(`${SystemConfigController.name} (e2e)`, () => {
|
||||
let server: any;
|
||||
let admin: LoginResponseDto;
|
||||
let nonAdmin: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
[server] = await testApp.create();
|
||||
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
await api.userApi.create(server, admin.accessToken, userDto.user1);
|
||||
nonAdmin = await api.authApi.login(server, userDto.user1);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testApp.teardown();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
admin = await api.authApi.adminLogin(server);
|
||||
});
|
||||
|
||||
describe('GET /system-config/map/style.json', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get('/system-config/map/style.json');
|
||||
|
@ -61,16 +61,10 @@ describe(`${SystemConfigController.name} (e2e)`, () => {
|
|||
});
|
||||
|
||||
it('should not require admin authentication', async () => {
|
||||
const credentials = { email: 'user1@immich.app', password: 'Password123' };
|
||||
await api.userApi.create(server, admin.accessToken, {
|
||||
...credentials,
|
||||
name: 'User 1',
|
||||
});
|
||||
const { accessToken } = await api.authApi.login(server, credentials);
|
||||
const { status, body } = await request(server)
|
||||
.get('/system-config/map/style.json')
|
||||
.query({ theme: 'dark' })
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
|
||||
});
|
||||
|
|
|
@ -4,8 +4,7 @@ import { UserEntity } from '@app/infra/entities';
|
|||
import { INestApplication } from '@nestjs/common';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { api } from '@test/api';
|
||||
import { db } from '@test/db';
|
||||
import { errorStub, userSignupStub, userStub } from '@test/fixtures';
|
||||
import { errorStub, userDto, userSignupStub, userStub } from '@test/fixtures';
|
||||
import { testApp } from '@test/test-utils';
|
||||
import request from 'supertest';
|
||||
import { Repository } from 'typeorm';
|
||||
|
@ -28,7 +27,7 @@ describe(`${UserController.name}`, () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.reset();
|
||||
await testApp.reset();
|
||||
await api.authApi.adminSignUp(server);
|
||||
loginResponse = await api.authApi.adminLogin(server);
|
||||
accessToken = loginResponse.accessToken;
|
||||
|
@ -36,11 +35,6 @@ describe(`${UserController.name}`, () => {
|
|||
userService = app.get<UserService>(UserService);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db.disconnect();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('GET /user', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(server).get('/user');
|
||||
|
@ -74,11 +68,7 @@ describe(`${UserController.name}`, () => {
|
|||
});
|
||||
|
||||
it('should include deleted users', async () => {
|
||||
const user1 = await api.userApi.create(server, accessToken, {
|
||||
email: `user1@immich.app`,
|
||||
password: 'Password123',
|
||||
name: `User 1`,
|
||||
});
|
||||
const user1 = await api.userApi.create(server, accessToken, userDto.user1);
|
||||
|
||||
await api.userApi.delete(server, accessToken, user1.id);
|
||||
|
||||
|
@ -86,6 +76,7 @@ describe(`${UserController.name}`, () => {
|
|||
.get(`/user`)
|
||||
.query({ isAll: false })
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body[0]).toMatchObject({ id: user1.id, email: 'user1@immich.app', deletedAt: expect.any(String) });
|
||||
|
|
18
server/test/fixtures/user.stub.ts
vendored
18
server/test/fixtures/user.stub.ts
vendored
|
@ -1,6 +1,24 @@
|
|||
import { UserAvatarColor, UserEntity } from '@app/infra/entities';
|
||||
import { authStub } from './auth.stub';
|
||||
|
||||
export const userDto = {
|
||||
user1: {
|
||||
email: 'user1@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 1',
|
||||
},
|
||||
user2: {
|
||||
email: 'user2@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 2',
|
||||
},
|
||||
user3: {
|
||||
email: 'user3@immich.app',
|
||||
password: 'Password123',
|
||||
name: 'User 3',
|
||||
},
|
||||
};
|
||||
|
||||
export const userStub = {
|
||||
admin: Object.freeze<UserEntity>({
|
||||
...authStub.admin,
|
||||
|
|
|
@ -31,5 +31,6 @@ export const newAssetRepositoryMock = (): jest.Mocked<IAssetRepository> => {
|
|||
getTimeBuckets: jest.fn(),
|
||||
restoreAll: jest.fn(),
|
||||
softDeleteAll: jest.fn(),
|
||||
search: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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 () => {
|
||||
|
@ -71,8 +85,8 @@ export const testApp = {
|
|||
|
||||
return [app.getHttpServer(), app];
|
||||
},
|
||||
reset: async () => {
|
||||
await db.reset();
|
||||
reset: async (options?: ResetOptions) => {
|
||||
await db.reset(options);
|
||||
},
|
||||
teardown: async () => {
|
||||
await app.get(AppService).teardown();
|
||||
|
|
70
web/package-lock.json
generated
70
web/package-lock.json
generated
|
@ -29,11 +29,11 @@
|
|||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@faker-js/faker": "^8.0.0",
|
||||
"@floating-ui/dom": "^1.5.1",
|
||||
"@sveltejs/adapter-node": "^1.2.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/svelte": "^4.0.3",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/dom-to-image": "^2.6.4",
|
||||
|
@ -1986,13 +1986,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@faker-js/faker": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
|
||||
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.3.1.tgz",
|
||||
"integrity": "sha512-FdgpFxY6V6rLZE9mmIBb9hM0xpfvQOSNOLnzolzKwsE1DH+gC7lEKV1p1IbR0lAYyvYd5a4u3qWJzowUkw1bIw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=6.14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
|
@ -3313,14 +3319,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "5.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
||||
"integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==",
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz",
|
||||
"integrity": "sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.0.1",
|
||||
"@adobe/css-tools": "^4.3.1",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@types/testing-library__jest-dom": "^5.9.1",
|
||||
"aria-query": "^5.0.0",
|
||||
"chalk": "^3.0.0",
|
||||
"css.escape": "^1.5.1",
|
||||
|
@ -3329,9 +3334,29 @@
|
|||
"redent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@jest/globals": ">= 28",
|
||||
"@types/jest": ">= 28",
|
||||
"jest": ">= 28",
|
||||
"vitest": ">= 0.32"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@jest/globals": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/jest": {
|
||||
"optional": true
|
||||
},
|
||||
"jest": {
|
||||
"optional": true
|
||||
},
|
||||
"vitest": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/ansi-styles": {
|
||||
|
@ -3532,6 +3557,8 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.5.tgz",
|
||||
"integrity": "sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"expect": "^29.0.0",
|
||||
"pretty-format": "^29.0.0"
|
||||
|
@ -3542,6 +3569,8 @@
|
|||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
|
@ -3554,6 +3583,8 @@
|
|||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/schemas": "^29.6.3",
|
||||
"ansi-styles": "^5.0.0",
|
||||
|
@ -3567,7 +3598,9 @@
|
|||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "20.0.1",
|
||||
|
@ -3671,15 +3704,6 @@
|
|||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/testing-library__jest-dom": {
|
||||
"version": "5.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
|
||||
"integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/jest": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz",
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@faker-js/faker": "^8.0.0",
|
||||
"@floating-ui/dom": "^1.5.1",
|
||||
"@sveltejs/adapter-node": "^1.2.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/svelte": "^4.0.3",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/dom-to-image": "^2.6.4",
|
||||
|
|
624
web/src/api/open-api/api.ts
generated
624
web/src/api/open-api/api.ts
generated
|
@ -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.
|
||||
|
|
|
@ -733,6 +733,7 @@
|
|||
>
|
||||
<DetailPanel
|
||||
{asset}
|
||||
albumId={album?.id}
|
||||
albums={appearsInAlbums}
|
||||
on:close={() => ($isShowDetail = false)}
|
||||
on:close-viewer={handleCloseViewer}
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
} from '@mdi/js';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import Map from '../shared-components/map/map.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
|
||||
export let asset: AssetResponseDto;
|
||||
export let albums: AlbumResponseDto[] = [];
|
||||
export let albumId: string | null = null;
|
||||
|
||||
let textarea: HTMLTextAreaElement;
|
||||
let description: string;
|
||||
|
@ -136,7 +138,11 @@
|
|||
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
{#each people as person (person.id)}
|
||||
<a href="/people/{person.id}" class="w-[90px]" on:click={() => dispatch('close-viewer')}>
|
||||
<a
|
||||
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}"
|
||||
class="w-[90px]"
|
||||
on:click={() => dispatch('close-viewer')}
|
||||
>
|
||||
<ImageThumbnail
|
||||
curve
|
||||
shadow
|
||||
|
|
3
web/src/lib/utils/navigation.ts
Normal file
3
web/src/lib/utils/navigation.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const isExternalUrl = (url: string): boolean => {
|
||||
return new URL(url, window.location.href).origin !== window.location.origin;
|
||||
};
|
|
@ -34,6 +34,7 @@
|
|||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { mdiPlus, mdiDotsVertical, mdiArrowLeft } from '@mdi/js';
|
||||
import { isExternalUrl } from '$lib/utils/navigation';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
@ -126,7 +127,7 @@
|
|||
onMount(() => {
|
||||
const action = $page.url.searchParams.get('action');
|
||||
const getPreviousRoute = $page.url.searchParams.get('previousRoute');
|
||||
if (getPreviousRoute) {
|
||||
if (getPreviousRoute && !isExternalUrl(getPreviousRoute)) {
|
||||
previousRoute = getPreviousRoute;
|
||||
}
|
||||
if (action == 'merge') {
|
||||
|
|
Loading…
Reference in a new issue