Merge branch 'main' of https://github.com/immich-app/immich into feat/cli-albums
This commit is contained in:
commit
4439bc2355
286 changed files with 23395 additions and 15933 deletions
2
.github/workflows/build-mobile.yml
vendored
2
.github/workflows/build-mobile.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.13.6"
|
||||
cache: true
|
||||
|
||||
- name: Create the Keystore
|
||||
|
|
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
|||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.13.6"
|
||||
|
||||
- name: Install dependencies
|
||||
run: dart pub get
|
||||
|
|
33
.github/workflows/test.yml
vendored
33
.github/workflows/test.yml
vendored
|
@ -13,20 +13,15 @@ jobs:
|
|||
e2e-tests:
|
||||
name: Run end-to-end test suites
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./server
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Run e2e tests
|
||||
run: npm run test:e2e
|
||||
if: ${{ !cancelled() }}
|
||||
run: docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
|
||||
|
||||
doc-tests:
|
||||
name: Run documentation checks
|
||||
|
@ -149,7 +144,7 @@ jobs:
|
|||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.3"
|
||||
flutter-version: "3.13.6"
|
||||
- name: Run tests
|
||||
working-directory: ./mobile
|
||||
run: flutter test -j 1
|
||||
|
@ -223,15 +218,27 @@ jobs:
|
|||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./server
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install server dependencies
|
||||
run: npm --prefix server ci
|
||||
run: npm ci
|
||||
|
||||
- name: Build the
|
||||
run: npm run build
|
||||
|
||||
- name: Run existing migrations
|
||||
run: npm --prefix server run typeorm:migrations:run
|
||||
run: npm run typeorm:migrations:run
|
||||
|
||||
- name: Generate new migrations
|
||||
continue-on-error: true
|
||||
run: npm --prefix server run typeorm:migrations:generate ./src/infra/migrations/TestMigration
|
||||
run: npm run typeorm:migrations:generate ./src/infra/migrations/TestMigration
|
||||
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@v13.1
|
||||
id: verify-changed-files
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
|||
[submodule "mobile/.isar"]
|
||||
path = mobile/.isar
|
||||
url = https://github.com/isar/isar
|
||||
[submodule "server/test/assets"]
|
||||
path = server/test/assets
|
||||
url = https://github.com/immich-app/test-assets
|
||||
|
|
2
Makefile
2
Makefile
|
@ -20,7 +20,7 @@ pull-stage:
|
|||
docker-compose -f ./docker/docker-compose.staging.yml pull
|
||||
|
||||
test-e2e:
|
||||
docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
|
||||
docker-compose -f ./docker/docker-compose.test.yml -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
|
||||
|
||||
prod:
|
||||
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
|
||||
|
|
6808
cli/package-lock.json
generated
6808
cli/package-lock.json
generated
File diff suppressed because it is too large
Load diff
592
cli/src/api/open-api/api.ts
generated
592
cli/src/api/open-api/api.ts
generated
|
@ -356,6 +356,25 @@ export interface AllJobStatusResponseDto {
|
|||
*/
|
||||
'videoConversion': JobStatusDto;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AssetBulkDeleteDto
|
||||
*/
|
||||
export interface AssetBulkDeleteDto {
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AssetBulkDeleteDto
|
||||
*/
|
||||
'force'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof AssetBulkDeleteDto
|
||||
*/
|
||||
'ids': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
@ -657,6 +676,12 @@ export interface AssetResponseDto {
|
|||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'isReadOnly': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'isTrashed': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -669,6 +694,12 @@ export interface AssetResponseDto {
|
|||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'livePhotoVideoId'?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'localDateTime': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -1351,54 +1382,6 @@ export interface CuratedObjectsResponseDto {
|
|||
*/
|
||||
'resizePath': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DeleteAssetDto
|
||||
*/
|
||||
export interface DeleteAssetDto {
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof DeleteAssetDto
|
||||
*/
|
||||
'ids': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DeleteAssetResponseDto
|
||||
*/
|
||||
export interface DeleteAssetResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DeleteAssetResponseDto
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
*
|
||||
* @type {DeleteAssetStatus}
|
||||
* @memberof DeleteAssetResponseDto
|
||||
*/
|
||||
'status': DeleteAssetStatus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @enum {string}
|
||||
*/
|
||||
|
||||
export const DeleteAssetStatus = {
|
||||
Success: 'SUCCESS',
|
||||
Failed: 'FAILED'
|
||||
} as const;
|
||||
|
||||
export type DeleteAssetStatus = typeof DeleteAssetStatus[keyof typeof DeleteAssetStatus];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
@ -2599,6 +2582,12 @@ export interface SearchResponseDto {
|
|||
* @interface ServerConfigDto
|
||||
*/
|
||||
export interface ServerConfigDto {
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ServerConfigDto
|
||||
*/
|
||||
'isInitialized': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -2617,6 +2606,12 @@ export interface ServerConfigDto {
|
|||
* @memberof ServerConfigDto
|
||||
*/
|
||||
'oauthButtonText': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ServerConfigDto
|
||||
*/
|
||||
'trashDays': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
@ -2690,6 +2685,12 @@ export interface ServerFeaturesDto {
|
|||
* @memberof ServerFeaturesDto
|
||||
*/
|
||||
'tagImage': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ServerFeaturesDto
|
||||
*/
|
||||
'trash': boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
@ -3133,6 +3134,12 @@ export interface SystemConfigDto {
|
|||
* @memberof SystemConfigDto
|
||||
*/
|
||||
'thumbnail': SystemConfigThumbnailDto;
|
||||
/**
|
||||
*
|
||||
* @type {SystemConfigTrashDto}
|
||||
* @memberof SystemConfigDto
|
||||
*/
|
||||
'trash': SystemConfigTrashDto;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
@ -3588,6 +3595,25 @@ export interface SystemConfigThumbnailDto {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SystemConfigTrashDto
|
||||
*/
|
||||
export interface SystemConfigTrashDto {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigTrashDto
|
||||
*/
|
||||
'days': number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof SystemConfigTrashDto
|
||||
*/
|
||||
'enabled': boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
@ -5676,13 +5702,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {DeleteAssetDto} deleteAssetDto
|
||||
* @param {AssetBulkDeleteDto} assetBulkDeleteDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteAsset: async (deleteAssetDto: DeleteAssetDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'deleteAssetDto' is not null or undefined
|
||||
assertParamExists('deleteAsset', 'deleteAssetDto', deleteAssetDto)
|
||||
deleteAssets: async (assetBulkDeleteDto: AssetBulkDeleteDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'assetBulkDeleteDto' is not null or undefined
|
||||
assertParamExists('deleteAssets', 'assetBulkDeleteDto', assetBulkDeleteDto)
|
||||
const localVarPath = `/asset`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
@ -5711,7 +5737,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(deleteAssetDto, localVarRequestOptions, configuration)
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(assetBulkDeleteDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
|
@ -5805,6 +5831,44 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
emptyTrash: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/trash/empty`;
|
||||
// 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: 'POST', ...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)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
@ -5973,10 +6037,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
*
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAssetStats: async (isArchived?: boolean, isFavorite?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
getAssetStats: async (isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/statistics`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
@ -6006,6 +6071,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
localVarQueryParameter['isFavorite'] = isFavorite;
|
||||
}
|
||||
|
||||
if (isTrashed !== undefined) {
|
||||
localVarQueryParameter['isTrashed'] = isTrashed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
|
@ -6078,11 +6147,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
* @param {string} [personId]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getByTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
getByTimeBucket: async (size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'size' is not null or undefined
|
||||
assertParamExists('getByTimeBucket', 'size', size)
|
||||
// verify required parameter 'timeBucket' is not null or undefined
|
||||
|
@ -6132,6 +6202,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
localVarQueryParameter['isFavorite'] = isFavorite;
|
||||
}
|
||||
|
||||
if (isTrashed !== undefined) {
|
||||
localVarQueryParameter['isTrashed'] = isTrashed;
|
||||
}
|
||||
|
||||
if (timeBucket !== undefined) {
|
||||
localVarQueryParameter['timeBucket'] = timeBucket;
|
||||
}
|
||||
|
@ -6340,13 +6414,16 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} timestamp Get pictures for +24 hours from this time going back x years
|
||||
* @param {number} day
|
||||
* @param {number} month
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getMemoryLane: async (timestamp: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'timestamp' is not null or undefined
|
||||
assertParamExists('getMemoryLane', 'timestamp', timestamp)
|
||||
getMemoryLane: async (day: number, month: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'day' is not null or undefined
|
||||
assertParamExists('getMemoryLane', 'day', day)
|
||||
// verify required parameter 'month' is not null or undefined
|
||||
assertParamExists('getMemoryLane', 'month', month)
|
||||
const localVarPath = `/asset/memory-lane`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
@ -6368,10 +6445,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (timestamp !== undefined) {
|
||||
localVarQueryParameter['timestamp'] = (timestamp as any instanceof Date) ?
|
||||
(timestamp as any).toISOString() :
|
||||
timestamp;
|
||||
if (day !== undefined) {
|
||||
localVarQueryParameter['day'] = day;
|
||||
}
|
||||
|
||||
if (month !== undefined) {
|
||||
localVarQueryParameter['month'] = month;
|
||||
}
|
||||
|
||||
|
||||
|
@ -6436,11 +6515,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
* @param {string} [personId]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
getTimeBuckets: async (size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'size' is not null or undefined
|
||||
assertParamExists('getTimeBuckets', 'size', size)
|
||||
const localVarPath = `/asset/time-buckets`;
|
||||
|
@ -6488,6 +6568,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
localVarQueryParameter['isFavorite'] = isFavorite;
|
||||
}
|
||||
|
||||
if (isTrashed !== undefined) {
|
||||
localVarQueryParameter['isTrashed'] = isTrashed;
|
||||
}
|
||||
|
||||
if (key !== undefined) {
|
||||
localVarQueryParameter['key'] = key;
|
||||
}
|
||||
|
@ -6589,6 +6673,88 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
|||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {BulkIdsDto} bulkIdsDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
restoreAssets: async (bulkIdsDto: BulkIdsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'bulkIdsDto' is not null or undefined
|
||||
assertParamExists('restoreAssets', 'bulkIdsDto', bulkIdsDto)
|
||||
const localVarPath = `/asset/restore`;
|
||||
// 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: 'POST', ...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)
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(bulkIdsDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
restoreTrash: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/trash/restore`;
|
||||
// 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: 'POST', ...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)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetJobsDto} assetJobsDto
|
||||
|
@ -7003,12 +7169,12 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {DeleteAssetDto} deleteAssetDto
|
||||
* @param {AssetBulkDeleteDto} assetBulkDeleteDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteAsset(deleteAssetDto: DeleteAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DeleteAssetResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAsset(deleteAssetDto, options);
|
||||
async deleteAssets(assetBulkDeleteDto: AssetBulkDeleteDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAssets(assetBulkDeleteDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -7033,6 +7199,15 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(id, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async emptyTrash(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.emptyTrash(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {string} [userId]
|
||||
|
@ -7072,11 +7247,12 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
*
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getAssetStats(isArchived?: boolean, isFavorite?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetStatsResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetStats(isArchived, isFavorite, options);
|
||||
async getAssetStats(isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetStatsResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetStats(isArchived, isFavorite, isTrashed, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -7100,12 +7276,13 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
* @param {string} [personId]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getByTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, key, options);
|
||||
async getByTimeBucket(size: TimeBucketSize, timeBucket: string, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -7152,12 +7329,13 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} timestamp Get pictures for +24 hours from this time going back x years
|
||||
* @param {number} day
|
||||
* @param {number} month
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getMemoryLane(timestamp: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MemoryLaneResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(timestamp, options);
|
||||
async getMemoryLane(day: number, month: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MemoryLaneResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMemoryLane(day, month, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -7178,12 +7356,13 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
* @param {string} [personId]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isTrashed]
|
||||
* @param {string} [key]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TimeBucketResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, key, options);
|
||||
async getTimeBuckets(size: TimeBucketSize, userId?: string, albumId?: string, personId?: string, isArchived?: boolean, isFavorite?: boolean, isTrashed?: boolean, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TimeBucketResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, key, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
|
@ -7206,6 +7385,25 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {BulkIdsDto} bulkIdsDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async restoreAssets(bulkIdsDto: BulkIdsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreAssets(bulkIdsDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async restoreTrash(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.restoreTrash(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetJobsDto} assetJobsDto
|
||||
|
@ -7324,12 +7522,12 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiDeleteAssetRequest} requestParameters Request parameters.
|
||||
* @param {AssetApiDeleteAssetsRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteAsset(requestParameters: AssetApiDeleteAssetRequest, options?: AxiosRequestConfig): AxiosPromise<Array<DeleteAssetResponseDto>> {
|
||||
return localVarFp.deleteAsset(requestParameters.deleteAssetDto, options).then((request) => request(axios, basePath));
|
||||
deleteAssets(requestParameters: AssetApiDeleteAssetsRequest, options?: AxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.deleteAssets(requestParameters.assetBulkDeleteDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
|
@ -7349,6 +7547,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
downloadFile(requestParameters: AssetApiDownloadFileRequest, options?: AxiosRequestConfig): AxiosPromise<File> {
|
||||
return localVarFp.downloadFile(requestParameters.id, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
emptyTrash(options?: AxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.emptyTrash(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {AssetApiGetAllAssetsRequest} requestParameters Request parameters.
|
||||
|
@ -7382,7 +7588,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
* @throws {RequiredError}
|
||||
*/
|
||||
getAssetStats(requestParameters: AssetApiGetAssetStatsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<AssetStatsResponseDto> {
|
||||
return localVarFp.getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, options).then((request) => request(axios, basePath));
|
||||
return localVarFp.getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
|
@ -7400,7 +7606,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
* @throws {RequiredError}
|
||||
*/
|
||||
getByTimeBucket(requestParameters: AssetApiGetByTimeBucketRequest, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
|
||||
return localVarFp.getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
return localVarFp.getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
|
@ -7443,7 +7649,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
* @throws {RequiredError}
|
||||
*/
|
||||
getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig): AxiosPromise<Array<MemoryLaneResponseDto>> {
|
||||
return localVarFp.getMemoryLane(requestParameters.timestamp, options).then((request) => request(axios, basePath));
|
||||
return localVarFp.getMemoryLane(requestParameters.day, requestParameters.month, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
|
@ -7461,7 +7667,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
* @throws {RequiredError}
|
||||
*/
|
||||
getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig): AxiosPromise<Array<TimeBucketResponseDto>> {
|
||||
return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
|
@ -7481,6 +7687,23 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
|||
importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise<AssetFileUploadResponseDto> {
|
||||
return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiRestoreAssetsRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
restoreAssets(requestParameters: AssetApiRestoreAssetsRequest, options?: AxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
restoreTrash(options?: AxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.restoreTrash(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters.
|
||||
|
@ -7588,17 +7811,17 @@ export interface AssetApiCheckExistingAssetsRequest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Request parameters for deleteAsset operation in AssetApi.
|
||||
* Request parameters for deleteAssets operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiDeleteAssetRequest
|
||||
* @interface AssetApiDeleteAssetsRequest
|
||||
*/
|
||||
export interface AssetApiDeleteAssetRequest {
|
||||
export interface AssetApiDeleteAssetsRequest {
|
||||
/**
|
||||
*
|
||||
* @type {DeleteAssetDto}
|
||||
* @memberof AssetApiDeleteAsset
|
||||
* @type {AssetBulkDeleteDto}
|
||||
* @memberof AssetApiDeleteAssets
|
||||
*/
|
||||
readonly deleteAssetDto: DeleteAssetDto
|
||||
readonly assetBulkDeleteDto: AssetBulkDeleteDto
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7732,6 +7955,13 @@ export interface AssetApiGetAssetStatsRequest {
|
|||
* @memberof AssetApiGetAssetStats
|
||||
*/
|
||||
readonly isFavorite?: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AssetApiGetAssetStats
|
||||
*/
|
||||
readonly isTrashed?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7817,6 +8047,13 @@ export interface AssetApiGetByTimeBucketRequest {
|
|||
*/
|
||||
readonly isFavorite?: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AssetApiGetByTimeBucket
|
||||
*/
|
||||
readonly isTrashed?: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -7888,11 +8125,18 @@ export interface AssetApiGetMapMarkersRequest {
|
|||
*/
|
||||
export interface AssetApiGetMemoryLaneRequest {
|
||||
/**
|
||||
* Get pictures for +24 hours from this time going back x years
|
||||
* @type {string}
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetApiGetMemoryLane
|
||||
*/
|
||||
readonly timestamp: string
|
||||
readonly day: number
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetApiGetMemoryLane
|
||||
*/
|
||||
readonly month: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7957,6 +8201,13 @@ export interface AssetApiGetTimeBucketsRequest {
|
|||
*/
|
||||
readonly isFavorite?: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof AssetApiGetTimeBuckets
|
||||
*/
|
||||
readonly isTrashed?: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -7993,6 +8244,20 @@ export interface AssetApiImportFileRequest {
|
|||
readonly importAssetDto: ImportAssetDto
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for restoreAssets operation in AssetApi.
|
||||
* @export
|
||||
* @interface AssetApiRestoreAssetsRequest
|
||||
*/
|
||||
export interface AssetApiRestoreAssetsRequest {
|
||||
/**
|
||||
*
|
||||
* @type {BulkIdsDto}
|
||||
* @memberof AssetApiRestoreAssets
|
||||
*/
|
||||
readonly bulkIdsDto: BulkIdsDto
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for runAssetJobs operation in AssetApi.
|
||||
* @export
|
||||
|
@ -8252,13 +8517,13 @@ export class AssetApi extends BaseAPI {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiDeleteAssetRequest} requestParameters Request parameters.
|
||||
* @param {AssetApiDeleteAssetsRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public deleteAsset(requestParameters: AssetApiDeleteAssetRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).deleteAsset(requestParameters.deleteAssetDto, options).then((request) => request(this.axios, this.basePath));
|
||||
public deleteAssets(requestParameters: AssetApiDeleteAssetsRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).deleteAssets(requestParameters.assetBulkDeleteDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8283,6 +8548,16 @@ export class AssetApi extends BaseAPI {
|
|||
return AssetApiFp(this.configuration).downloadFile(requestParameters.id, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public emptyTrash(options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).emptyTrash(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all AssetEntity belong to the user
|
||||
* @param {AssetApiGetAllAssetsRequest} requestParameters Request parameters.
|
||||
|
@ -8323,7 +8598,7 @@ export class AssetApi extends BaseAPI {
|
|||
* @memberof AssetApi
|
||||
*/
|
||||
public getAssetStats(requestParameters: AssetApiGetAssetStatsRequest = {}, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, options).then((request) => request(this.axios, this.basePath));
|
||||
return AssetApiFp(this.configuration).getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8345,7 +8620,7 @@ export class AssetApi extends BaseAPI {
|
|||
* @memberof AssetApi
|
||||
*/
|
||||
public getByTimeBucket(requestParameters: AssetApiGetByTimeBucketRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
return AssetApiFp(this.configuration).getByTimeBucket(requestParameters.size, requestParameters.timeBucket, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8398,7 +8673,7 @@ export class AssetApi extends BaseAPI {
|
|||
* @memberof AssetApi
|
||||
*/
|
||||
public getMemoryLane(requestParameters: AssetApiGetMemoryLaneRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getMemoryLane(requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath));
|
||||
return AssetApiFp(this.configuration).getMemoryLane(requestParameters.day, requestParameters.month, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8420,7 +8695,7 @@ export class AssetApi extends BaseAPI {
|
|||
* @memberof AssetApi
|
||||
*/
|
||||
public getTimeBuckets(requestParameters: AssetApiGetTimeBucketsRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
return AssetApiFp(this.configuration).getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8445,6 +8720,27 @@ export class AssetApi extends BaseAPI {
|
|||
return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiRestoreAssetsRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public restoreAssets(requestParameters: AssetApiRestoreAssetsRequest, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).restoreAssets(requestParameters.bulkIdsDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public restoreTrash(options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).restoreTrash(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters.
|
||||
|
@ -11849,6 +12145,51 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio
|
|||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
searchPerson: async (name: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'name' is not null or undefined
|
||||
assertParamExists('searchPerson', 'name', name)
|
||||
const localVarPath = `/search/person`;
|
||||
// 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 (name !== undefined) {
|
||||
localVarQueryParameter['name'] = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
@ -11902,6 +12243,16 @@ export const SearchApiFp = function(configuration?: Configuration) {
|
|||
const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} name
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async searchPerson(name: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PersonResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.searchPerson(name, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11929,6 +12280,15 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat
|
|||
search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig): AxiosPromise<SearchResponseDto> {
|
||||
return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {SearchApiSearchPersonRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
searchPerson(requestParameters: SearchApiSearchPersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
|
||||
return localVarFp.searchPerson(requestParameters.name, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -12051,6 +12411,20 @@ export interface SearchApiSearchRequest {
|
|||
readonly motion?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for searchPerson operation in SearchApi.
|
||||
* @export
|
||||
* @interface SearchApiSearchPersonRequest
|
||||
*/
|
||||
export interface SearchApiSearchPersonRequest {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SearchApiSearchPerson
|
||||
*/
|
||||
readonly name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchApi - object-oriented interface
|
||||
* @export
|
||||
|
@ -12078,6 +12452,17 @@ export class SearchApi extends BaseAPI {
|
|||
public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) {
|
||||
return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SearchApiSearchPersonRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof SearchApi
|
||||
*/
|
||||
public searchPerson(requestParameters: SearchApiSearchPersonRequest, options?: AxiosRequestConfig) {
|
||||
return SearchApiFp(this.configuration).searchPerson(requestParameters.name, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -14702,6 +15087,15 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
|
|||
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 (admin !== undefined) {
|
||||
localVarQueryParameter['admin'] = admin;
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# Database
|
||||
DB_HOSTNAME=immich-database-test
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_DATABASE_NAME=e2e_test
|
||||
|
||||
# Redis
|
||||
REDIS_HOSTNAME=immich-redis-test
|
||||
|
||||
# Upload File Config
|
||||
UPLOAD_LOCATION=./upload
|
||||
|
||||
# WEB
|
||||
VITE_SERVER_ENDPOINT=http://localhost:2283/api
|
||||
|
||||
TYPESENSE_ENABLED=false
|
|
@ -121,7 +121,7 @@ services:
|
|||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}/postgres:/data
|
||||
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
version: "3.8"
|
||||
|
||||
# Compose file for dockerized end-to-end testing of the backend
|
||||
|
||||
services:
|
||||
immich-server-test:
|
||||
image: immich-server-test
|
||||
|
@ -8,39 +10,31 @@ services:
|
|||
dockerfile: Dockerfile
|
||||
target: builder
|
||||
command: npm run test:e2e
|
||||
expose:
|
||||
- "3000"
|
||||
volumes:
|
||||
- ../server:/usr/src/app
|
||||
- /usr/src/app/node_modules
|
||||
env_file:
|
||||
- .env.test
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- TYPESENSE_ENABLED=false
|
||||
- DB_HOSTNAME=immich-database-test
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=postgres
|
||||
- DB_DATABASE_NAME=e2e_test
|
||||
- IMMICH_RUN_ALL_TESTS=true
|
||||
depends_on:
|
||||
- immich-redis-test
|
||||
- immich-database-test
|
||||
networks:
|
||||
- immich-test-network
|
||||
immich-redis-test:
|
||||
container_name: immich-redis-test
|
||||
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
|
||||
networks:
|
||||
- immich-test-network
|
||||
|
||||
immich-database-test:
|
||||
container_name: immich-database-test
|
||||
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
|
||||
env_file:
|
||||
- .env.test
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
volumes:
|
||||
- /var/lib/postgresql/data
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: e2e_test
|
||||
networks:
|
||||
- immich-test-network
|
||||
logging:
|
||||
driver: none
|
||||
|
||||
networks:
|
||||
immich-test-network:
|
||||
|
|
|
@ -20,22 +20,9 @@ Immich doesn't have two-way synchronization ([yet](https://github.com/immich-app
|
|||
|
||||
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
|
||||
|
||||
### Why does my uploaded photo show up with the wrong date or time in Immich?
|
||||
|
||||
When a photo is initially uploaded Immich uses the create date of the file to determine where it belongs in the timeline. After that, background jobs will run that extract [exif metadata](https://en.wikipedia.org/wiki/Exif), including the CreateDate, to provide a more accurate date for the photo. If that is not available it will fallback to the modified date. If you want to ensure your photo has the right date, check the exif metadata before uploading.
|
||||
|
||||
If the timezone is incorrect in an uploaded photo, check the `DateTimeOriginal` exif field of the uploaded file. Immich uses the very competent library [exiftool-vendored.js](https://github.com/photostructure/exiftool-vendored.js#dates) to handle timezone parsing, but in some cases (like photos taken with DSLR cameras) it has to fallback on the local timezone. If you are using docker, this fallback will be UTC. (Note that even the photo backup app that can't be named [has the same bug!](https://photo.stackexchange.com/a/126978)) In Immich, it is possible to change this assumed fallback timezone system-wide by setting the timezone in the microservices docker container. You might need to run the "Extract Metadata" job after to effect the change.
|
||||
|
||||
As an example, the following modification of `docker-compose.yml` will set the timezone of the microservices container to be `Europe/Stockholm`
|
||||
|
||||
```
|
||||
environment:
|
||||
- TZ=Europe/Stockholm # <---- Add this line in the microservices config
|
||||
```
|
||||
|
||||
### Why are only photos and not videos being uploaded to Immich?
|
||||
|
||||
This often happens when using a reverse proxy or cloudflare tunnel in front of Immich. Make sure to set your reverse proxy to allow large POST requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Cloudflare tunnels are limited to 100 mb file sizes.
|
||||
This often happens when using a reverse proxy or cloudflare tunnel in front of Immich. Make sure to set your reverse proxy to allow large POST requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Cloudflare tunnels are limited to 100 mb file sizes. Also check the disk space of your reverse proxy, in some cases proxies caches requests to disk before passing them on, and if disk space runs out the request fails.
|
||||
|
||||
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ Users can deploy a custom reverse proxy that forwards requests to Immich's rever
|
|||
|
||||
### Nginx example config
|
||||
|
||||
Below is an example config for nginx:
|
||||
Below is an example config for nginx. Make sure to include `client_max_body_size 50000M;` also in a `http` block in `/etc/nginx/nginx.conf`.
|
||||
|
||||
```nginx
|
||||
server {
|
||||
|
|
17
docs/docs/developer/testing.md
Normal file
17
docs/docs/developer/testing.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Testing
|
||||
|
||||
## Server
|
||||
|
||||
### Unit tests
|
||||
|
||||
Unit are run by calling `npm run test` from the `server` directory.
|
||||
|
||||
### End to end tests
|
||||
|
||||
The backend has an end-to-end test suite that can be called with `npm run test:e2e` from the `server` directory. This will set up a dummy database inside a temporary container and run the tests against it. Setup and teardown is automatically taken care of. That test, however, can not set up all prerequisites to parse file formats, as that is very complex and error-prone. As such, this test excludes some test cases like HEIC file imports. The test suite will also print a friendly warning to remind you that not all tests are being run.
|
||||
|
||||
Note that there is a bug in nodejs <20.8 that causes segmentation faults when running these tests. If you run into segfaults, ensure you are using at least version 20.8.
|
||||
|
||||
To perform a full e2e test, you need to run e2e tests inside docker. The easiest way to do that is to run `make test-e2e` in the root directory. This will build and start a docker-compose consisting of the server, microservices, and a postgres database. It will then perfom the tests and exit.
|
||||
|
||||
If you manually install the dependencies (see the DOCKERFILE) on your development machine, you can also run the full e2e tests manually by setting the `IMMICH_RUN_ALL_TESTS` environment value to true, i.e. `IMMICH_RUN_ALL_TESTS=true npm run test:e2e`.
|
|
@ -42,8 +42,26 @@ Finally, files can be deleted from Immich via the `Remove Offline Files` job. An
|
|||
|
||||
External libraries use import paths to determine which files to scan. Each library can have multiple import paths so that files from different locations can be added to the same library. Import paths are scanned recursively, and if a file is in multiple import paths, it will only be added once. If the import paths are edited in a way that an external file is no longer in any import path, it will be removed from the library in the same way a deleted file would. If the file is moved back to an import path, it will be added again as if it was a new file.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Sometimes, an external library will not scan correctly. This can happen if the immich_server or immich_microservices can't access the files. Here are some things to check:
|
||||
|
||||
- Is the external path set correctly?
|
||||
- In the docker-compose file, are the volumes mounted correctly?
|
||||
- Are the volumes identical between the `server` and `microservices` container?
|
||||
- Are the import paths set correctly, and do they match the path set in docker-compose file?
|
||||
- Are the permissions set correctly?
|
||||
|
||||
If all else fails, you can always start a shell inside the container and check if the path is accessible. For example, `docker exec -it immich_microservices /bin/bash` will start a bash shell. If your import path, for instance, is `/data/import/photos`, you can check if the files are accessible by running `ls /data/import/photos`. Also check the `immich_server` container in the same way.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
:::caution
|
||||
|
||||
Please read and understand this section before setting external paths, as there are important security considerations.
|
||||
|
||||
:::
|
||||
|
||||
For security purposes, each Immich user is disallowed to add external files by default. This is to prevent devastating [path traversal attacks](https://owasp.org/www-community/attacks/Path_Traversal). An admin can allow individual users to use external path feature via the `external path` setting found in the admin panel. Without the external path restriction, a user can add any image or video file on the Immich host filesystem to be imported into Immich, potentially allowing sensitive data to be accessed. If you are running Immich as root in your Docker setup (which is the default), all external file reads are done with root privileges. This is particularly dangerous if the Immich host is a shared server.
|
||||
|
||||
With the `external path` set, a user is restricted to accessing external files to files or directories within that path. The Immich admin should still be careful not set the external path too generously. For example, `user1` wants to read their photos in to `/home/user1`. A lazy admin sets that user's external path to `/home/` since it "gets the job done". However, that user will then be able to read all photos in `/home/user2/private-photos`, too! Please set the external path as specific as possible. If multiple folders must be added, do this using the docker volume mount feature described below.
|
||||
|
@ -59,6 +77,10 @@ Some basic examples:
|
|||
- `**/Raw/**` will exclude all files in any directory named `Raw`
|
||||
- `*.(tif,jpg)` will exclude all files with the extension `.tif` or `.jpg`
|
||||
|
||||
### Nightly job
|
||||
|
||||
There is an automatic job that's run once a day and refreshes all modified files in all libraries as well as cleans up any libraries stuck in deletion.
|
||||
|
||||
## Usage
|
||||
|
||||
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
|
||||
|
|
|
@ -66,6 +66,10 @@ ORDER BY
|
|||
"users"."email";
|
||||
```
|
||||
|
||||
```sql title="Failed file movements"
|
||||
SELECT * FROM "move_history";
|
||||
```
|
||||
|
||||
## Users
|
||||
|
||||
```sql title="List"
|
||||
|
|
9112
docs/package-lock.json
generated
9112
docs/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -17,10 +17,11 @@
|
|||
"check": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.4.1",
|
||||
"@docusaurus/preset-classic": "^2.4.1",
|
||||
"@docusaurus/core": "^2.4.3",
|
||||
"@docusaurus/preset-classic": "^2.4.3",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-lunr-search": "^2.3.2",
|
||||
"docusaurus-preset-openapi": "^0.6.3",
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"module": "Node16"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,6 @@ from ..config import log
|
|||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
_ST_TO_JINA_MODEL_NAME = {
|
||||
"clip-ViT-B-16": "ViT-B-16::openai",
|
||||
"clip-ViT-B-32": "ViT-B-32::openai",
|
||||
"clip-ViT-B-32-multilingual-v1": "M-CLIP/XLM-Roberta-Large-Vit-B-32",
|
||||
"clip-ViT-L-14": "ViT-L-14::openai",
|
||||
}
|
||||
|
||||
|
||||
class CLIPEncoder(InferenceModel):
|
||||
_model_type = ModelType.CLIP
|
||||
|
@ -36,11 +29,10 @@ class CLIPEncoder(InferenceModel):
|
|||
) -> None:
|
||||
if mode is not None and mode not in ("text", "vision"):
|
||||
raise ValueError(f"Mode must be 'text', 'vision', or omitted; got '{mode}'")
|
||||
if "vit-b" not in model_name.lower():
|
||||
raise ValueError(f"Only ViT-B models are currently supported; got '{model_name}'")
|
||||
if model_name not in _MODELS:
|
||||
raise ValueError(f"Unknown model name {model_name}.")
|
||||
self.mode = mode
|
||||
jina_model_name = self._get_jina_model_name(model_name)
|
||||
super().__init__(jina_model_name, cache_dir, **model_kwargs)
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self) -> None:
|
||||
models: tuple[tuple[str, str], tuple[str, str]] = _MODELS[self.model_name]
|
||||
|
@ -104,20 +96,6 @@ class CLIPEncoder(InferenceModel):
|
|||
|
||||
return outputs[0][0].tolist()
|
||||
|
||||
def _get_jina_model_name(self, model_name: str) -> str:
|
||||
if model_name in _MODELS:
|
||||
return model_name
|
||||
elif model_name in _ST_TO_JINA_MODEL_NAME:
|
||||
log.warn(
|
||||
(
|
||||
f"Sentence-Transformer models like '{model_name}' are not supported."
|
||||
f"Using '{_ST_TO_JINA_MODEL_NAME[model_name]}' instead as it is the best match for '{model_name}'."
|
||||
),
|
||||
)
|
||||
return _ST_TO_JINA_MODEL_NAME[model_name]
|
||||
else:
|
||||
raise ValueError(f"Unknown model name {model_name}.")
|
||||
|
||||
def _download_model(self, model_name: str, model_md5: str) -> bool:
|
||||
# downloading logic is adapted from clip-server's CLIPOnnxModel class
|
||||
download_model(
|
||||
|
|
|
@ -163,7 +163,6 @@
|
|||
"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",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"library_page_albums": "Albums",
|
||||
|
@ -322,5 +321,17 @@
|
|||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes"
|
||||
"map_location_dialog_yes": "Yes",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"trash_page_info": "Backed up items will be permanently deleted after {} days",
|
||||
"trash_page_no_assets": "No trashed assets",
|
||||
"trash_page_delete": "Delete",
|
||||
"trash_page_delete_all": "Delete All",
|
||||
"trash_page_restore": "Restore",
|
||||
"trash_page_restore_all": "Restore All",
|
||||
"trash_page_select_btn": "Select",
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich"
|
||||
}
|
||||
|
|
|
@ -169,4 +169,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.11.3
|
||||
|
|
|
@ -17,6 +17,7 @@ final archiveProvider = StreamProvider<RenderList>((ref) async* {
|
|||
.ownerIdEqualToAnyChecksum(user.isarId)
|
||||
.filter()
|
||||
.isArchivedEqualTo(true)
|
||||
.isTrashedEqualTo(false)
|
||||
.sortByFileCreatedAt();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy =
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart';
|
||||
|
@ -19,11 +20,14 @@ import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart'
|
|||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
|
||||
import 'package:immich_mobile/shared/cache/original_image_provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:immich_mobile/shared/ui/photo_view/photo_view_gallery.dart';
|
||||
import 'package:immich_mobile/shared/ui/photo_view/src/photo_view_computed_scale.dart';
|
||||
import 'package:immich_mobile/shared/ui/photo_view/src/photo_view_scale_state.dart';
|
||||
|
@ -67,6 +71,12 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
final header = {"Authorization": authToken};
|
||||
final currentIndex = useState(initialIndex);
|
||||
final currentAsset = loadAsset(currentIndex.value);
|
||||
final isTrashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final navStack = AutoRouter.of(context).stackData;
|
||||
final isFromTrash = isTrashEnabled &&
|
||||
navStack.length > 2 &&
|
||||
navStack.elementAt(navStack.length - 2).name == TrashRoute.name;
|
||||
|
||||
Asset asset() => currentAsset;
|
||||
|
||||
|
@ -161,25 +171,47 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
void handleDelete(Asset deleteAsset) {
|
||||
void handleDelete(Asset deleteAsset) async {
|
||||
Future<bool> onDelete(bool force) async {
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
||||
{deleteAsset},
|
||||
force: force,
|
||||
);
|
||||
if (isDeleted) {
|
||||
if (totalAssets == 1) {
|
||||
// Handle only one asset
|
||||
AutoRouter.of(context).pop();
|
||||
} else {
|
||||
// Go to next page otherwise
|
||||
controller.nextPage(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
// Asset is trashed
|
||||
if (isTrashEnabled && !isFromTrash) {
|
||||
final isDeleted = await onDelete(false);
|
||||
// Can only trash assets stored in server. Local assets are always permanently removed for now
|
||||
if (context.mounted && isDeleted && deleteAsset.isRemote) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'Asset trashed',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Asset is permanently removed
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return DeleteDialog(
|
||||
onDelete: () {
|
||||
if (totalAssets == 1) {
|
||||
// Handle only one asset
|
||||
AutoRouter.of(context).pop();
|
||||
} else {
|
||||
// Go to next page otherwise
|
||||
controller.nextPage(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
);
|
||||
}
|
||||
ref.watch(assetProvider.notifier).deleteAssets({deleteAsset});
|
||||
},
|
||||
);
|
||||
return DeleteDialog(onDelete: () => onDelete(true));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -81,7 +81,9 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
context: context,
|
||||
msg: "Deleting ${assets.length} assets on the server...",
|
||||
);
|
||||
await ref.read(assetProvider.notifier).deleteAssets(assets);
|
||||
await ref
|
||||
.read(assetProvider.notifier)
|
||||
.deleteAssets(assets, force: true);
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "Deleted ${assets.length} assets on the server. "
|
||||
|
|
|
@ -17,6 +17,7 @@ final favoriteAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
|||
.ownerIdEqualToAnyChecksum(user.isarId)
|
||||
.filter()
|
||||
.isFavoriteEqualTo(true)
|
||||
.isTrashedEqualTo(false)
|
||||
.sortByFileCreatedAt();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy =
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
|||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
|
||||
|
@ -43,6 +44,8 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
var hasRemote = selectionAssetState == AssetState.remote;
|
||||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
|
||||
Widget renderActionButtons() {
|
||||
return Row(
|
||||
|
@ -70,14 +73,20 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||
iconData: Icons.delete_outline_rounded,
|
||||
label: "control_bottom_app_bar_delete".tr(),
|
||||
onPressed: enabled
|
||||
? () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DeleteDialog(
|
||||
onDelete: onDelete,
|
||||
);
|
||||
},
|
||||
)
|
||||
? () {
|
||||
if (!trashEnabled) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DeleteDialog(
|
||||
onDelete: onDelete,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
onDelete();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
),
|
||||
if (!hasRemote)
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.dar
|
|||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
|
||||
class ProfileDrawer extends HookConsumerWidget {
|
||||
|
@ -16,6 +17,9 @@ class ProfileDrawer extends HookConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
|
||||
buildSignOutButton() {
|
||||
return ListTile(
|
||||
leading: SizedBox(
|
||||
|
@ -91,6 +95,29 @@ class ProfileDrawer extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
buildTrashButton() {
|
||||
return ListTile(
|
||||
leading: SizedBox(
|
||||
height: double.infinity,
|
||||
child: Icon(
|
||||
Icons.delete_rounded,
|
||||
color: Theme.of(context).textTheme.labelMedium?.color,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
"Trash",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(const TrashRoute());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Drawer(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
|
@ -105,6 +132,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
|||
const ProfileDrawerHeader(),
|
||||
buildSettingButton(),
|
||||
buildAppLogButton(),
|
||||
if (trashEnabled) buildTrashButton(),
|
||||
buildSignOutButton(),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -43,6 +43,8 @@ class HomePage extends HookConsumerWidget {
|
|||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
final refreshCount = useState(0);
|
||||
|
@ -139,30 +141,34 @@ class HomePage extends HookConsumerWidget {
|
|||
void onDelete() async {
|
||||
processing.value = true;
|
||||
try {
|
||||
await ref.read(assetProvider.notifier).deleteAssets(selection.value);
|
||||
await ref
|
||||
.read(assetProvider.notifier)
|
||||
.deleteAssets(selection.value, force: !trashEnabled);
|
||||
|
||||
final hasRemote = selection.value.any((a) => a.isRemote);
|
||||
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
|
||||
final trashOrRemoved =
|
||||
!trashEnabled ? 'deleted permanently' : 'trashed';
|
||||
if (hasRemote) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: '${selection.value.length} $assetOrAssets $trashOrRemoved',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
selectionEnabledHook.value = false;
|
||||
} finally {
|
||||
processing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void onUpload() async {
|
||||
void onUpload() {
|
||||
processing.value = true;
|
||||
selectionEnabledHook.value = false;
|
||||
try {
|
||||
final Set<Asset> assets = selection.value;
|
||||
if (assets.length > 30) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'home_page_upload_err_limit'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
await ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, assets);
|
||||
}
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, selection.value);
|
||||
} finally {
|
||||
processing.value = false;
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ class MemoryService {
|
|||
Future<List<Memory>?> getMemoryLane() async {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final beginningOfDate = DateTime(now.year, now.month, now.day);
|
||||
final data = await _apiService.assetApi.getMemoryLane(
|
||||
beginningOfDate,
|
||||
now.day,
|
||||
now.month,
|
||||
);
|
||||
|
||||
if (data == null) {
|
||||
|
|
144
mobile/lib/modules/trash/providers/trashed_asset.provider.dart
Normal file
144
mobile/lib/modules/trash/providers/trashed_asset.provider.dart
Normal file
|
@ -0,0 +1,144 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:immich_mobile/modules/trash/services/trash.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class TrashNotifier extends StateNotifier<bool> {
|
||||
final Isar _db;
|
||||
final Ref _ref;
|
||||
final TrashService _trashService;
|
||||
final _log = Logger('TrashNotifier');
|
||||
|
||||
TrashNotifier(
|
||||
this._trashService,
|
||||
this._db,
|
||||
this._ref,
|
||||
) : super(false);
|
||||
|
||||
Future<void> emptyTrash() async {
|
||||
try {
|
||||
final user = _ref.read(currentUserProvider);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
await _trashService.emptyTrash();
|
||||
|
||||
final dbIds = await _db.assets
|
||||
.where()
|
||||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
.isTrashedEqualTo(true)
|
||||
.idProperty()
|
||||
.findAll();
|
||||
|
||||
await _db.writeTxn(() async {
|
||||
await _db.exifInfos.deleteAll(dbIds);
|
||||
await _db.assets.deleteAll(dbIds);
|
||||
});
|
||||
|
||||
// Refresh assets in background
|
||||
Future.delayed(
|
||||
const Duration(seconds: 4),
|
||||
() async => await _ref.read(assetProvider.notifier).getAllAsset(),
|
||||
);
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot empty trash ${error.toString()}", error, stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> restoreAssets(Iterable<Asset> assetList) async {
|
||||
try {
|
||||
final result = await _trashService.restoreAssets(assetList);
|
||||
|
||||
if (result) {
|
||||
final remoteAssets = assetList.where((a) => a.isRemote).toList();
|
||||
|
||||
final updatedAssets = remoteAssets.map((e) {
|
||||
e.isTrashed = false;
|
||||
return e;
|
||||
}).toList();
|
||||
|
||||
await _db.writeTxn(() async {
|
||||
await _db.assets.putAll(updatedAssets);
|
||||
});
|
||||
|
||||
// Refresh assets in background
|
||||
Future.delayed(
|
||||
const Duration(seconds: 4),
|
||||
() async => await _ref.read(assetProvider.notifier).getAllAsset(),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot restore trash ${error.toString()}", error, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> restoreTrash() async {
|
||||
try {
|
||||
final user = _ref.read(currentUserProvider);
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
await _trashService.restoreTrash();
|
||||
|
||||
final assets = await _db.assets
|
||||
.where()
|
||||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
.isTrashedEqualTo(true)
|
||||
.findAll();
|
||||
|
||||
final updatedAssets = assets.map((e) {
|
||||
e.isTrashed = false;
|
||||
return e;
|
||||
}).toList();
|
||||
|
||||
await _db.writeTxn(() async {
|
||||
await _db.assets.putAll(updatedAssets);
|
||||
});
|
||||
|
||||
// Refresh assets in background
|
||||
Future.delayed(
|
||||
const Duration(seconds: 4),
|
||||
() async => await _ref.read(assetProvider.notifier).getAllAsset(),
|
||||
);
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot restore trash ${error.toString()}", error, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final trashProvider = StateNotifierProvider<TrashNotifier, bool>((ref) {
|
||||
return TrashNotifier(
|
||||
ref.watch(trashServiceProvider),
|
||||
ref.watch(dbProvider),
|
||||
ref,
|
||||
);
|
||||
});
|
||||
|
||||
final trashedAssetsProvider = StreamProvider<RenderList>((ref) async* {
|
||||
final user = ref.read(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final query = ref
|
||||
.watch(dbProvider)
|
||||
.assets
|
||||
.filter()
|
||||
.ownerIdEqualTo(user.isarId)
|
||||
.isTrashedEqualTo(true)
|
||||
.sortByFileCreatedAt();
|
||||
const groupBy = GroupAssetsBy.none;
|
||||
yield await RenderList.fromQuery(query, groupBy);
|
||||
await for (final _ in query.watchLazy()) {
|
||||
yield await RenderList.fromQuery(query, groupBy);
|
||||
}
|
||||
});
|
48
mobile/lib/modules/trash/services/trash.service.dart
Normal file
48
mobile/lib/modules/trash/services/trash.service.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final trashServiceProvider = Provider<TrashService>((ref) {
|
||||
return TrashService(
|
||||
ref.watch(apiServiceProvider),
|
||||
);
|
||||
});
|
||||
|
||||
class TrashService {
|
||||
final _log = Logger("TrashService");
|
||||
|
||||
final ApiService _apiService;
|
||||
|
||||
TrashService(this._apiService);
|
||||
|
||||
Future<bool> restoreAssets(Iterable<Asset> assetList) async {
|
||||
try {
|
||||
List<String> remoteIds =
|
||||
assetList.where((a) => a.isRemote).map((e) => e.remoteId!).toList();
|
||||
await _apiService.assetApi.restoreAssets(BulkIdsDto(ids: remoteIds));
|
||||
return true;
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot restore assets ${error.toString()}", error, stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> emptyTrash() async {
|
||||
try {
|
||||
await _apiService.assetApi.emptyTrash();
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot empty trash ${error.toString()}", error, stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> restoreTrash() async {
|
||||
try {
|
||||
await _apiService.assetApi.restoreTrash();
|
||||
} catch (error, stack) {
|
||||
_log.severe("Cannot restore trash ${error.toString()}", error, stack);
|
||||
}
|
||||
}
|
||||
}
|
276
mobile/lib/modules/trash/views/trash_page.dart
Normal file
276
mobile/lib/modules/trash/views/trash_page.dart
Normal file
|
@ -0,0 +1,276 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||
import 'package:immich_mobile/modules/trash/providers/trashed_asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
|
||||
class TrashPage extends HookConsumerWidget {
|
||||
const TrashPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final trashedAssets = ref.watch(trashedAssetsProvider);
|
||||
final trashDays =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverConfig.trashDays));
|
||||
final selectionEnabledHook = useState(false);
|
||||
final selection = useState(<Asset>{});
|
||||
final processing = useState(false);
|
||||
|
||||
void selectionListener(
|
||||
bool multiselect,
|
||||
Set<Asset> selectedAssets,
|
||||
) {
|
||||
selectionEnabledHook.value = multiselect;
|
||||
selection.value = selectedAssets;
|
||||
}
|
||||
|
||||
onEmptyTrash() async {
|
||||
processing.value = true;
|
||||
await ref.read(trashProvider.notifier).emptyTrash();
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'Emptied trash',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleEmptyTrash() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
onOk: () => onEmptyTrash(),
|
||||
title: "trash_page_empty_trash_btn".tr(),
|
||||
ok: "trash_page_empty_trash_dialog_ok".tr(),
|
||||
content: "trash_page_empty_trash_dialog_content".tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onPermanentlyDelete() async {
|
||||
processing.value = true;
|
||||
try {
|
||||
if (selection.value.isNotEmpty) {
|
||||
await ref
|
||||
.read(assetProvider.notifier)
|
||||
.deleteAssets(selection.value, force: true);
|
||||
|
||||
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg:
|
||||
'${selection.value.length} $assetOrAssets deleted permanently',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
handlePermanentDelete() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeleteDialog(
|
||||
onDelete: () => onPermanentlyDelete(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> handleRestoreAll() async {
|
||||
processing.value = true;
|
||||
await ref.read(trashProvider.notifier).restoreTrash();
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
|
||||
Future<void> handleRestore() async {
|
||||
processing.value = true;
|
||||
try {
|
||||
if (selection.value.isNotEmpty) {
|
||||
final result = await ref
|
||||
.read(trashProvider.notifier)
|
||||
.restoreAssets(selection.value);
|
||||
|
||||
final assetOrAssets = selection.value.length > 1 ? 'assets' : 'asset';
|
||||
if (result && context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg:
|
||||
'${selection.value.length} $assetOrAssets restored successfully',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
String getAppBarTitle(String count) {
|
||||
if (selectionEnabledHook.value) {
|
||||
return selection.value.isNotEmpty
|
||||
? "${selection.value.length}"
|
||||
: "trash_page_select_assets_btn".tr();
|
||||
}
|
||||
return 'trash_page_title'.tr(args: [count]);
|
||||
}
|
||||
|
||||
AppBar buildAppBar(String count) {
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: !selectionEnabledHook.value
|
||||
? () => AutoRouter.of(context).pop()
|
||||
: () {
|
||||
selectionEnabledHook.value = false;
|
||||
selection.value = {};
|
||||
},
|
||||
icon: !selectionEnabledHook.value
|
||||
? const Icon(Icons.arrow_back_ios_rounded)
|
||||
: const Icon(Icons.close_rounded),
|
||||
),
|
||||
centerTitle: !selectionEnabledHook.value,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(getAppBarTitle(count)),
|
||||
actions: <Widget>[
|
||||
if (!selectionEnabledHook.value)
|
||||
PopupMenuButton<void Function()>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: () => selectionEnabledHook.value = true,
|
||||
child: const Text('trash_page_select_btn').tr(),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: handleEmptyTrash,
|
||||
child: const Text('trash_page_empty_trash_btn').tr(),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (fn) => fn(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBottomBar() {
|
||||
return SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SizedBox(
|
||||
height: 64,
|
||||
child: Container(
|
||||
color: Theme.of(context).canvasColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
icon: Icon(
|
||||
Icons.delete_forever,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
label: Text(
|
||||
selection.value.isEmpty
|
||||
? 'trash_page_delete_all'.tr()
|
||||
: 'trash_page_delete'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
onPressed: processing.value
|
||||
? null
|
||||
: selection.value.isEmpty
|
||||
? handleEmptyTrash
|
||||
: handlePermanentDelete,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(
|
||||
Icons.history_rounded,
|
||||
),
|
||||
label: Text(
|
||||
selection.value.isEmpty
|
||||
? 'trash_page_restore_all'.tr()
|
||||
: 'trash_page_restore'.tr(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
onPressed: processing.value
|
||||
? null
|
||||
: selection.value.isEmpty
|
||||
? handleRestoreAll
|
||||
: handleRestore,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return trashedAssets.when(
|
||||
loading: () => Scaffold(
|
||||
appBar: buildAppBar("?"),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, stackTrace) => Scaffold(
|
||||
appBar: buildAppBar("!"),
|
||||
body: Center(child: Text(error.toString())),
|
||||
),
|
||||
data: (data) => Scaffold(
|
||||
appBar: buildAppBar(data.totalAssets.toString()),
|
||||
body: data.isEmpty
|
||||
? Center(
|
||||
child: Text('trash_page_no_assets'.tr()),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: ImmichAssetGrid(
|
||||
renderList: data,
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
showMultiSelectIndicator: false,
|
||||
topWidget: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
bottom: 24,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: const Text(
|
||||
"trash_page_info",
|
||||
).tr(args: ["$trashDays"]),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selectionEnabledHook.value) buildBottomBar(),
|
||||
if (processing.value)
|
||||
const Center(child: ImmichLoadingIndicator()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
|||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
|
||||
import 'package:immich_mobile/modules/trash/views/trash_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/all_motion_videos_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/all_people_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/all_videos_page.dart';
|
||||
|
@ -155,6 +156,7 @@ part 'router.gr.dart';
|
|||
AutoRoute(page: MemoryPage, guards: [AuthGuard, DuplicateGuard]),
|
||||
AutoRoute(page: MapPage, guards: [AuthGuard, DuplicateGuard]),
|
||||
AutoRoute(page: AlbumOptionsPage, guards: [AuthGuard, DuplicateGuard]),
|
||||
AutoRoute(page: TrashPage, guards: [AuthGuard, DuplicateGuard]),
|
||||
],
|
||||
)
|
||||
class AppRouter extends _$AppRouter {
|
||||
|
|
|
@ -312,6 +312,12 @@ class _$AppRouter extends RootStackRouter {
|
|||
),
|
||||
);
|
||||
},
|
||||
TrashRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const TrashPage(),
|
||||
);
|
||||
},
|
||||
HomeRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData,
|
||||
|
@ -624,6 +630,14 @@ class _$AppRouter extends RootStackRouter {
|
|||
duplicateGuard,
|
||||
],
|
||||
),
|
||||
RouteConfig(
|
||||
TrashRoute.name,
|
||||
path: '/trash-page',
|
||||
guards: [
|
||||
authGuard,
|
||||
duplicateGuard,
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1394,6 +1408,18 @@ class AlbumOptionsRouteArgs {
|
|||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [TrashPage]
|
||||
class TrashRoute extends PageRouteInfo<void> {
|
||||
const TrashRoute()
|
||||
: super(
|
||||
TrashRoute.name,
|
||||
path: '/trash-page',
|
||||
);
|
||||
|
||||
static const String name = 'TrashRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [HomePage]
|
||||
class HomeRoute extends PageRouteInfo<void> {
|
||||
|
|
|
@ -30,7 +30,8 @@ class Asset {
|
|||
exifInfo =
|
||||
remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
|
||||
isFavorite = remote.isFavorite,
|
||||
isArchived = remote.isArchived;
|
||||
isArchived = remote.isArchived,
|
||||
isTrashed = remote.isTrashed;
|
||||
|
||||
Asset.local(AssetEntity local, List<int> hash)
|
||||
: localId = local.id,
|
||||
|
@ -45,6 +46,7 @@ class Asset {
|
|||
updatedAt = local.modifiedDateTime,
|
||||
isFavorite = local.isFavorite,
|
||||
isArchived = false,
|
||||
isTrashed = false,
|
||||
fileCreatedAt = local.createDateTime {
|
||||
if (fileCreatedAt.year == 1970) {
|
||||
fileCreatedAt = fileModifiedAt;
|
||||
|
@ -74,6 +76,7 @@ class Asset {
|
|||
this.exifInfo,
|
||||
required this.isFavorite,
|
||||
required this.isArchived,
|
||||
required this.isTrashed,
|
||||
});
|
||||
|
||||
@ignore
|
||||
|
@ -138,6 +141,8 @@ class Asset {
|
|||
|
||||
bool isArchived;
|
||||
|
||||
bool isTrashed;
|
||||
|
||||
@ignore
|
||||
ExifInfo? exifInfo;
|
||||
|
||||
|
@ -194,7 +199,8 @@ class Asset {
|
|||
livePhotoVideoId == other.livePhotoVideoId &&
|
||||
isFavorite == other.isFavorite &&
|
||||
isLocal == other.isLocal &&
|
||||
isArchived == other.isArchived;
|
||||
isArchived == other.isArchived &&
|
||||
isTrashed == other.isTrashed;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -216,7 +222,8 @@ class Asset {
|
|||
livePhotoVideoId.hashCode ^
|
||||
isFavorite.hashCode ^
|
||||
isLocal.hashCode ^
|
||||
isArchived.hashCode;
|
||||
isArchived.hashCode ^
|
||||
isTrashed.hashCode;
|
||||
|
||||
/// Returns `true` if this [Asset] can updated with values from parameter [a]
|
||||
bool canUpdate(Asset a) {
|
||||
|
@ -229,8 +236,9 @@ class Asset {
|
|||
width == null && a.width != null ||
|
||||
height == null && a.height != null ||
|
||||
livePhotoVideoId == null && a.livePhotoVideoId != null ||
|
||||
!isRemote && a.isRemote && isFavorite != a.isFavorite ||
|
||||
!isRemote && a.isRemote && isArchived != a.isArchived;
|
||||
isFavorite != a.isFavorite ||
|
||||
isArchived != a.isArchived ||
|
||||
isTrashed != a.isTrashed;
|
||||
}
|
||||
|
||||
/// Returns a new [Asset] with values from this and merged & updated with [a]
|
||||
|
@ -261,6 +269,7 @@ class Asset {
|
|||
livePhotoVideoId: livePhotoVideoId,
|
||||
isFavorite: isFavorite,
|
||||
isArchived: isArchived,
|
||||
isTrashed: isTrashed,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -275,6 +284,7 @@ class Asset {
|
|||
// isFavorite + isArchived are not set by device-only assets
|
||||
isFavorite: a.isFavorite,
|
||||
isArchived: a.isArchived,
|
||||
isTrashed: a.isTrashed,
|
||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||
);
|
||||
} else {
|
||||
|
@ -306,6 +316,7 @@ class Asset {
|
|||
String? livePhotoVideoId,
|
||||
bool? isFavorite,
|
||||
bool? isArchived,
|
||||
bool? isTrashed,
|
||||
ExifInfo? exifInfo,
|
||||
}) =>
|
||||
Asset(
|
||||
|
@ -325,6 +336,7 @@ class Asset {
|
|||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
isArchived: isArchived ?? this.isArchived,
|
||||
isTrashed: isTrashed ?? this.isTrashed,
|
||||
exifInfo: exifInfo ?? this.exifInfo,
|
||||
);
|
||||
|
||||
|
@ -378,7 +390,8 @@ class Asset {
|
|||
"storage": "$storage",
|
||||
"width": ${width ?? "N/A"},
|
||||
"height": ${height ?? "N/A"},
|
||||
"isArchived": $isArchived
|
||||
"isArchived": $isArchived,
|
||||
"isTrashed": $isTrashed,
|
||||
}""";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,39 +57,44 @@ const AssetSchema = CollectionSchema(
|
|||
name: r'isFavorite',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'livePhotoVideoId': PropertySchema(
|
||||
r'isTrashed': PropertySchema(
|
||||
id: 8,
|
||||
name: r'isTrashed',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'livePhotoVideoId': PropertySchema(
|
||||
id: 9,
|
||||
name: r'livePhotoVideoId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'localId': PropertySchema(
|
||||
id: 9,
|
||||
id: 10,
|
||||
name: r'localId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'ownerId': PropertySchema(
|
||||
id: 10,
|
||||
id: 11,
|
||||
name: r'ownerId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'remoteId': PropertySchema(
|
||||
id: 11,
|
||||
id: 12,
|
||||
name: r'remoteId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'type': PropertySchema(
|
||||
id: 12,
|
||||
id: 13,
|
||||
name: r'type',
|
||||
type: IsarType.byte,
|
||||
enumMap: _AssettypeEnumValueMap,
|
||||
),
|
||||
r'updatedAt': PropertySchema(
|
||||
id: 13,
|
||||
id: 14,
|
||||
name: r'updatedAt',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'width': PropertySchema(
|
||||
id: 14,
|
||||
id: 15,
|
||||
name: r'width',
|
||||
type: IsarType.int,
|
||||
)
|
||||
|
@ -196,13 +201,14 @@ void _assetSerialize(
|
|||
writer.writeInt(offsets[5], object.height);
|
||||
writer.writeBool(offsets[6], object.isArchived);
|
||||
writer.writeBool(offsets[7], object.isFavorite);
|
||||
writer.writeString(offsets[8], object.livePhotoVideoId);
|
||||
writer.writeString(offsets[9], object.localId);
|
||||
writer.writeLong(offsets[10], object.ownerId);
|
||||
writer.writeString(offsets[11], object.remoteId);
|
||||
writer.writeByte(offsets[12], object.type.index);
|
||||
writer.writeDateTime(offsets[13], object.updatedAt);
|
||||
writer.writeInt(offsets[14], object.width);
|
||||
writer.writeBool(offsets[8], object.isTrashed);
|
||||
writer.writeString(offsets[9], object.livePhotoVideoId);
|
||||
writer.writeString(offsets[10], object.localId);
|
||||
writer.writeLong(offsets[11], object.ownerId);
|
||||
writer.writeString(offsets[12], object.remoteId);
|
||||
writer.writeByte(offsets[13], object.type.index);
|
||||
writer.writeDateTime(offsets[14], object.updatedAt);
|
||||
writer.writeInt(offsets[15], object.width);
|
||||
}
|
||||
|
||||
Asset _assetDeserialize(
|
||||
|
@ -221,14 +227,15 @@ Asset _assetDeserialize(
|
|||
id: id,
|
||||
isArchived: reader.readBool(offsets[6]),
|
||||
isFavorite: reader.readBool(offsets[7]),
|
||||
livePhotoVideoId: reader.readStringOrNull(offsets[8]),
|
||||
localId: reader.readStringOrNull(offsets[9]),
|
||||
ownerId: reader.readLong(offsets[10]),
|
||||
remoteId: reader.readStringOrNull(offsets[11]),
|
||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[12])] ??
|
||||
isTrashed: reader.readBool(offsets[8]),
|
||||
livePhotoVideoId: reader.readStringOrNull(offsets[9]),
|
||||
localId: reader.readStringOrNull(offsets[10]),
|
||||
ownerId: reader.readLong(offsets[11]),
|
||||
remoteId: reader.readStringOrNull(offsets[12]),
|
||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
|
||||
AssetType.other,
|
||||
updatedAt: reader.readDateTime(offsets[13]),
|
||||
width: reader.readIntOrNull(offsets[14]),
|
||||
updatedAt: reader.readDateTime(offsets[14]),
|
||||
width: reader.readIntOrNull(offsets[15]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
@ -257,19 +264,21 @@ P _assetDeserializeProp<P>(
|
|||
case 7:
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
return (reader.readBool(offset)) as P;
|
||||
case 9:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 10:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 12:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 13:
|
||||
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
AssetType.other) as P;
|
||||
case 13:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
case 14:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
case 15:
|
||||
return (reader.readIntOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
@ -1290,6 +1299,16 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> isTrashedEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'isTrashed',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> livePhotoVideoIdIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
|
@ -2058,6 +2077,18 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsTrashedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByLivePhotoVideoId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
||||
|
@ -2252,6 +2283,18 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsTrashedDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByLivePhotoVideoId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'livePhotoVideoId', Sort.asc);
|
||||
|
@ -2388,6 +2431,12 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QDistinct> distinctByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'isTrashed');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QDistinct> distinctByLivePhotoVideoId(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -2490,6 +2539,12 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, bool, QQueryOperations> isTrashedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isTrashed');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, String?, QQueryOperations> livePhotoVideoIdProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'livePhotoVideoId');
|
||||
|
|
|
@ -15,7 +15,6 @@ import 'package:immich_mobile/shared/services/user.service.dart';
|
|||
import 'package:immich_mobile/utils/db.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class AssetNotifier extends StateNotifier<bool> {
|
||||
|
@ -92,23 +91,45 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||
await _syncService.syncNewAssetToDb(newAsset);
|
||||
}
|
||||
|
||||
Future<void> deleteAssets(Iterable<Asset> deleteAssets) async {
|
||||
Future<bool> deleteAssets(
|
||||
Iterable<Asset> deleteAssets, {
|
||||
bool? force = false,
|
||||
}) async {
|
||||
_deleteInProgress = true;
|
||||
state = true;
|
||||
try {
|
||||
final localDeleted = await _deleteLocalAssets(deleteAssets);
|
||||
final remoteDeleted = await _deleteRemoteAssets(deleteAssets);
|
||||
final remoteDeleted = await _deleteRemoteAssets(deleteAssets, force);
|
||||
if (localDeleted.isNotEmpty || remoteDeleted.isNotEmpty) {
|
||||
final dbIds = deleteAssets.map((e) => e.id).toList();
|
||||
List<Asset>? assetsToUpdate;
|
||||
// Local only assets are permanently deleted for now. So always remove them from db
|
||||
final dbIds = deleteAssets
|
||||
.where((a) => a.isLocal && !a.isRemote)
|
||||
.map((e) => e.id)
|
||||
.toList();
|
||||
if (force == null || !force) {
|
||||
assetsToUpdate = remoteDeleted.map((e) {
|
||||
e.isTrashed = true;
|
||||
return e;
|
||||
}).toList();
|
||||
} else {
|
||||
// Add all remote assets to be deleted from isar as since they are permanently deleted
|
||||
dbIds.addAll(remoteDeleted.map((e) => e.id));
|
||||
}
|
||||
await _db.writeTxn(() async {
|
||||
if (assetsToUpdate != null) {
|
||||
await _db.assets.putAll(assetsToUpdate);
|
||||
}
|
||||
await _db.exifInfos.deleteAll(dbIds);
|
||||
await _db.assets.deleteAll(dbIds);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
_deleteInProgress = false;
|
||||
state = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<List<String>> _deleteLocalAssets(
|
||||
|
@ -127,15 +148,14 @@ class AssetNotifier extends StateNotifier<bool> {
|
|||
return [];
|
||||
}
|
||||
|
||||
Future<Iterable<String>> _deleteRemoteAssets(
|
||||
Future<Iterable<Asset>> _deleteRemoteAssets(
|
||||
Iterable<Asset> assetsToDelete,
|
||||
bool? force,
|
||||
) async {
|
||||
final Iterable<Asset> remote = assetsToDelete.where((e) => e.isRemote);
|
||||
final List<DeleteAssetResponseDto> deleteAssetResult =
|
||||
await _assetService.deleteAssets(remote) ?? [];
|
||||
return deleteAssetResult
|
||||
.where((a) => a.status == DeleteAssetStatus.SUCCESS)
|
||||
.map((a) => a.id);
|
||||
|
||||
final isSuccess = await _assetService.deleteAssets(remote, force: force);
|
||||
return isSuccess ? remote : [];
|
||||
}
|
||||
|
||||
Future<void> toggleFavorite(List<Asset> assets, bool status) async {
|
||||
|
@ -190,6 +210,7 @@ final assetsProvider =
|
|||
.ownerIdEqualToAnyChecksum(userId)
|
||||
.filter()
|
||||
.isArchivedEqualTo(false)
|
||||
.isTrashedEqualTo(false)
|
||||
.sortByFileCreatedAtDesc();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy =
|
||||
|
@ -210,6 +231,7 @@ final remoteAssetsProvider =
|
|||
.remoteIdIsNotNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(userId)
|
||||
.isTrashedEqualTo(false)
|
||||
.sortByFileCreatedAtDesc();
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy =
|
||||
|
|
|
@ -26,12 +26,15 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
|
|||
search: true,
|
||||
sidecar: true,
|
||||
tagImage: true,
|
||||
trash: true,
|
||||
reverseGeocoding: true,
|
||||
),
|
||||
serverConfig: ServerConfigDto(
|
||||
loginPageMessage: "",
|
||||
mapTileUrl: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
oauthButtonText: "",
|
||||
trashDays: 30,
|
||||
isInitialized: false,
|
||||
),
|
||||
isVersionMismatch: false,
|
||||
versionMismatchErrorMessage: "",
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:immich_mobile/modules/login/providers/authentication.provider.da
|
|||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:socket_io_client/socket_io_client.dart';
|
||||
|
@ -92,6 +93,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||
});
|
||||
|
||||
socket.on('on_upload_success', _handleOnUploadSuccess);
|
||||
socket.on('on_config_update', _handleOnConfigUpdate);
|
||||
} catch (e) {
|
||||
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
|
||||
}
|
||||
|
@ -126,6 +128,11 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||
ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
|
||||
}
|
||||
}
|
||||
|
||||
_handleOnConfigUpdate(dynamic data) {
|
||||
ref.read(serverInfoProvider.notifier).getServerFeatures();
|
||||
ref.read(serverInfoProvider.notifier).getServerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
final websocketProvider =
|
||||
|
|
|
@ -64,7 +64,9 @@ class AssetService {
|
|||
Future<List<Asset>?> _getRemoteAssets(User user) async {
|
||||
try {
|
||||
final List<AssetResponseDto>? assets =
|
||||
await _apiService.assetApi.getAllAssets(userId: user.id);
|
||||
await _apiService.assetApi.getAllAssets(
|
||||
userId: user.id,
|
||||
);
|
||||
if (assets == null) {
|
||||
return null;
|
||||
} else if (assets.isNotEmpty && assets.first.ownerId != user.id) {
|
||||
|
@ -84,9 +86,10 @@ class AssetService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<List<DeleteAssetResponseDto>?> deleteAssets(
|
||||
Iterable<Asset> deleteAssets,
|
||||
) async {
|
||||
Future<bool> deleteAssets(
|
||||
Iterable<Asset> deleteAssets, {
|
||||
bool? force = false,
|
||||
}) async {
|
||||
try {
|
||||
final List<String> payload = [];
|
||||
|
||||
|
@ -94,12 +97,17 @@ class AssetService {
|
|||
payload.add(asset.remoteId!);
|
||||
}
|
||||
|
||||
return await _apiService.assetApi
|
||||
.deleteAsset(DeleteAssetDto(ids: payload));
|
||||
await _apiService.assetApi.deleteAssets(
|
||||
AssetBulkDeleteDto(
|
||||
ids: payload,
|
||||
force: force,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (error, stack) {
|
||||
log.severe("Error deleteAssets ${error.toString()}", error, stack);
|
||||
return null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Loads the exif information from the database. If there is none, loads
|
||||
|
|
15
mobile/openapi/.openapi-generator/FILES
generated
15
mobile/openapi/.openapi-generator/FILES
generated
|
@ -15,6 +15,7 @@ doc/AlbumCountResponseDto.md
|
|||
doc/AlbumResponseDto.md
|
||||
doc/AllJobStatusResponseDto.md
|
||||
doc/AssetApi.md
|
||||
doc/AssetBulkDeleteDto.md
|
||||
doc/AssetBulkUpdateDto.md
|
||||
doc/AssetBulkUploadCheckDto.md
|
||||
doc/AssetBulkUploadCheckItem.md
|
||||
|
@ -53,9 +54,6 @@ doc/CreateTagDto.md
|
|||
doc/CreateUserDto.md
|
||||
doc/CuratedLocationsResponseDto.md
|
||||
doc/CuratedObjectsResponseDto.md
|
||||
doc/DeleteAssetDto.md
|
||||
doc/DeleteAssetResponseDto.md
|
||||
doc/DeleteAssetStatus.md
|
||||
doc/DownloadArchiveInfo.md
|
||||
doc/DownloadInfoDto.md
|
||||
doc/DownloadResponseDto.md
|
||||
|
@ -131,6 +129,7 @@ doc/SystemConfigReverseGeocodingDto.md
|
|||
doc/SystemConfigStorageTemplateDto.md
|
||||
doc/SystemConfigTemplateStorageOptionDto.md
|
||||
doc/SystemConfigThumbnailDto.md
|
||||
doc/SystemConfigTrashDto.md
|
||||
doc/TagApi.md
|
||||
doc/TagResponseDto.md
|
||||
doc/TagTypeEnum.md
|
||||
|
@ -186,6 +185,7 @@ lib/model/api_key_create_dto.dart
|
|||
lib/model/api_key_create_response_dto.dart
|
||||
lib/model/api_key_response_dto.dart
|
||||
lib/model/api_key_update_dto.dart
|
||||
lib/model/asset_bulk_delete_dto.dart
|
||||
lib/model/asset_bulk_update_dto.dart
|
||||
lib/model/asset_bulk_upload_check_dto.dart
|
||||
lib/model/asset_bulk_upload_check_item.dart
|
||||
|
@ -222,9 +222,6 @@ lib/model/create_tag_dto.dart
|
|||
lib/model/create_user_dto.dart
|
||||
lib/model/curated_locations_response_dto.dart
|
||||
lib/model/curated_objects_response_dto.dart
|
||||
lib/model/delete_asset_dto.dart
|
||||
lib/model/delete_asset_response_dto.dart
|
||||
lib/model/delete_asset_status.dart
|
||||
lib/model/download_archive_info.dart
|
||||
lib/model/download_info_dto.dart
|
||||
lib/model/download_response_dto.dart
|
||||
|
@ -291,6 +288,7 @@ lib/model/system_config_reverse_geocoding_dto.dart
|
|||
lib/model/system_config_storage_template_dto.dart
|
||||
lib/model/system_config_template_storage_option_dto.dart
|
||||
lib/model/system_config_thumbnail_dto.dart
|
||||
lib/model/system_config_trash_dto.dart
|
||||
lib/model/tag_response_dto.dart
|
||||
lib/model/tag_type_enum.dart
|
||||
lib/model/thumbnail_format.dart
|
||||
|
@ -322,6 +320,7 @@ test/api_key_create_response_dto_test.dart
|
|||
test/api_key_response_dto_test.dart
|
||||
test/api_key_update_dto_test.dart
|
||||
test/asset_api_test.dart
|
||||
test/asset_bulk_delete_dto_test.dart
|
||||
test/asset_bulk_update_dto_test.dart
|
||||
test/asset_bulk_upload_check_dto_test.dart
|
||||
test/asset_bulk_upload_check_item_test.dart
|
||||
|
@ -360,9 +359,6 @@ test/create_tag_dto_test.dart
|
|||
test/create_user_dto_test.dart
|
||||
test/curated_locations_response_dto_test.dart
|
||||
test/curated_objects_response_dto_test.dart
|
||||
test/delete_asset_dto_test.dart
|
||||
test/delete_asset_response_dto_test.dart
|
||||
test/delete_asset_status_test.dart
|
||||
test/download_archive_info_test.dart
|
||||
test/download_info_dto_test.dart
|
||||
test/download_response_dto_test.dart
|
||||
|
@ -438,6 +434,7 @@ test/system_config_reverse_geocoding_dto_test.dart
|
|||
test/system_config_storage_template_dto_test.dart
|
||||
test/system_config_template_storage_option_dto_test.dart
|
||||
test/system_config_thumbnail_dto_test.dart
|
||||
test/system_config_trash_dto_test.dart
|
||||
test/tag_api_test.dart
|
||||
test/tag_response_dto_test.dart
|
||||
test/tag_type_enum_test.dart
|
||||
|
|
11
mobile/openapi/README.md
generated
11
mobile/openapi/README.md
generated
|
@ -90,9 +90,10 @@ Class | Method | HTTP request | Description
|
|||
*AssetApi* | [**bulkUploadCheck**](doc//AssetApi.md#bulkuploadcheck) | **POST** /asset/bulk-upload-check |
|
||||
*AssetApi* | [**checkDuplicateAsset**](doc//AssetApi.md#checkduplicateasset) | **POST** /asset/check |
|
||||
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
|
||||
*AssetApi* | [**deleteAsset**](doc//AssetApi.md#deleteasset) | **DELETE** /asset |
|
||||
*AssetApi* | [**deleteAssets**](doc//AssetApi.md#deleteassets) | **DELETE** /asset |
|
||||
*AssetApi* | [**downloadArchive**](doc//AssetApi.md#downloadarchive) | **POST** /asset/download/archive |
|
||||
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **POST** /asset/download/{id} |
|
||||
*AssetApi* | [**emptyTrash**](doc//AssetApi.md#emptytrash) | **POST** /asset/trash/empty |
|
||||
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
|
||||
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
|
||||
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||
|
@ -108,6 +109,8 @@ Class | Method | HTTP request | Description
|
|||
*AssetApi* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
|
||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||
*AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import |
|
||||
*AssetApi* | [**restoreAssets**](doc//AssetApi.md#restoreassets) | **POST** /asset/restore |
|
||||
*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* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
|
||||
|
@ -151,6 +154,7 @@ Class | Method | HTTP request | Description
|
|||
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
|
||||
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
|
||||
*SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search |
|
||||
*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person |
|
||||
*ServerInfoApi* | [**getServerConfig**](doc//ServerInfoApi.md#getserverconfig) | **GET** /server-info/config |
|
||||
*ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features |
|
||||
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
|
||||
|
@ -201,6 +205,7 @@ Class | Method | HTTP request | Description
|
|||
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
|
||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
|
||||
- [AssetBulkDeleteDto](doc//AssetBulkDeleteDto.md)
|
||||
- [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md)
|
||||
- [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md)
|
||||
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
|
||||
|
@ -237,9 +242,6 @@ Class | Method | HTTP request | Description
|
|||
- [CreateUserDto](doc//CreateUserDto.md)
|
||||
- [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md)
|
||||
- [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md)
|
||||
- [DeleteAssetDto](doc//DeleteAssetDto.md)
|
||||
- [DeleteAssetResponseDto](doc//DeleteAssetResponseDto.md)
|
||||
- [DeleteAssetStatus](doc//DeleteAssetStatus.md)
|
||||
- [DownloadArchiveInfo](doc//DownloadArchiveInfo.md)
|
||||
- [DownloadInfoDto](doc//DownloadInfoDto.md)
|
||||
- [DownloadResponseDto](doc//DownloadResponseDto.md)
|
||||
|
@ -306,6 +308,7 @@ Class | Method | HTTP request | Description
|
|||
- [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md)
|
||||
- [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md)
|
||||
- [SystemConfigThumbnailDto](doc//SystemConfigThumbnailDto.md)
|
||||
- [SystemConfigTrashDto](doc//SystemConfigTrashDto.md)
|
||||
- [TagResponseDto](doc//TagResponseDto.md)
|
||||
- [TagTypeEnum](doc//TagTypeEnum.md)
|
||||
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
||||
|
|
204
mobile/openapi/doc/AssetApi.md
generated
204
mobile/openapi/doc/AssetApi.md
generated
|
@ -12,9 +12,10 @@ Method | HTTP request | Description
|
|||
[**bulkUploadCheck**](AssetApi.md#bulkuploadcheck) | **POST** /asset/bulk-upload-check |
|
||||
[**checkDuplicateAsset**](AssetApi.md#checkduplicateasset) | **POST** /asset/check |
|
||||
[**checkExistingAssets**](AssetApi.md#checkexistingassets) | **POST** /asset/exist |
|
||||
[**deleteAsset**](AssetApi.md#deleteasset) | **DELETE** /asset |
|
||||
[**deleteAssets**](AssetApi.md#deleteassets) | **DELETE** /asset |
|
||||
[**downloadArchive**](AssetApi.md#downloadarchive) | **POST** /asset/download/archive |
|
||||
[**downloadFile**](AssetApi.md#downloadfile) | **POST** /asset/download/{id} |
|
||||
[**emptyTrash**](AssetApi.md#emptytrash) | **POST** /asset/trash/empty |
|
||||
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
|
||||
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
|
||||
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||
|
@ -30,6 +31,8 @@ Method | HTTP request | Description
|
|||
[**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
|
||||
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||
[**importFile**](AssetApi.md#importfile) | **POST** /asset/import |
|
||||
[**restoreAssets**](AssetApi.md#restoreassets) | **POST** /asset/restore |
|
||||
[**restoreTrash**](AssetApi.md#restoretrash) | **POST** /asset/trash/restore |
|
||||
[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs |
|
||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
|
||||
|
@ -211,8 +214,8 @@ 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)
|
||||
|
||||
# **deleteAsset**
|
||||
> List<DeleteAssetResponseDto> deleteAsset(deleteAssetDto)
|
||||
# **deleteAssets**
|
||||
> deleteAssets(assetBulkDeleteDto)
|
||||
|
||||
|
||||
|
||||
|
@ -235,13 +238,12 @@ import 'package:openapi/api.dart';
|
|||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final deleteAssetDto = DeleteAssetDto(); // DeleteAssetDto |
|
||||
final assetBulkDeleteDto = AssetBulkDeleteDto(); // AssetBulkDeleteDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.deleteAsset(deleteAssetDto);
|
||||
print(result);
|
||||
api_instance.deleteAssets(assetBulkDeleteDto);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->deleteAsset: $e\n');
|
||||
print('Exception when calling AssetApi->deleteAssets: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -249,11 +251,11 @@ try {
|
|||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**deleteAssetDto** | [**DeleteAssetDto**](DeleteAssetDto.md)| |
|
||||
**assetBulkDeleteDto** | [**AssetBulkDeleteDto**](AssetBulkDeleteDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**List<DeleteAssetResponseDto>**](DeleteAssetResponseDto.md)
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
|
@ -262,7 +264,7 @@ Name | Type | Description | Notes
|
|||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[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)
|
||||
|
||||
|
@ -380,6 +382,56 @@ 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)
|
||||
|
||||
# **emptyTrash**
|
||||
> emptyTrash()
|
||||
|
||||
|
||||
|
||||
### 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();
|
||||
|
||||
try {
|
||||
api_instance.emptyTrash();
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->emptyTrash: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[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)
|
||||
|
||||
# **getAllAssets**
|
||||
> List<AssetResponseDto> getAllAssets(userId, isFavorite, isArchived, skip, updatedAfter, ifNoneMatch)
|
||||
|
||||
|
@ -558,7 +610,7 @@ This endpoint does not need any parameter.
|
|||
[[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)
|
||||
|
||||
# **getAssetStats**
|
||||
> AssetStatsResponseDto getAssetStats(isArchived, isFavorite)
|
||||
> AssetStatsResponseDto getAssetStats(isArchived, isFavorite, isTrashed)
|
||||
|
||||
|
||||
|
||||
|
@ -583,9 +635,10 @@ import 'package:openapi/api.dart';
|
|||
final api_instance = AssetApi();
|
||||
final isArchived = true; // bool |
|
||||
final isFavorite = true; // bool |
|
||||
final isTrashed = true; // bool |
|
||||
|
||||
try {
|
||||
final result = api_instance.getAssetStats(isArchived, isFavorite);
|
||||
final result = api_instance.getAssetStats(isArchived, isFavorite, isTrashed);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getAssetStats: $e\n');
|
||||
|
@ -598,6 +651,7 @@ Name | Type | Description | Notes
|
|||
------------- | ------------- | ------------- | -------------
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isTrashed** | **bool**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
|
@ -674,7 +728,7 @@ 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)
|
||||
|
||||
# **getByTimeBucket**
|
||||
> List<AssetResponseDto> getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, key)
|
||||
> List<AssetResponseDto> getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, key)
|
||||
|
||||
|
||||
|
||||
|
@ -704,10 +758,11 @@ final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
|||
final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final isArchived = true; // bool |
|
||||
final isFavorite = true; // bool |
|
||||
final isTrashed = true; // bool |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, key);
|
||||
final result = api_instance.getByTimeBucket(size, timeBucket, userId, albumId, personId, isArchived, isFavorite, isTrashed, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getByTimeBucket: $e\n');
|
||||
|
@ -725,6 +780,7 @@ Name | Type | Description | Notes
|
|||
**personId** | **String**| | [optional]
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isTrashed** | **bool**| | [optional]
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
@ -963,7 +1019,7 @@ 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)
|
||||
|
||||
# **getMemoryLane**
|
||||
> List<MemoryLaneResponseDto> getMemoryLane(timestamp)
|
||||
> List<MemoryLaneResponseDto> getMemoryLane(day, month)
|
||||
|
||||
|
||||
|
||||
|
@ -986,10 +1042,11 @@ import 'package:openapi/api.dart';
|
|||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final timestamp = 2013-10-20T19:20:30+01:00; // DateTime | Get pictures for +24 hours from this time going back x years
|
||||
final day = 56; // int |
|
||||
final month = 56; // int |
|
||||
|
||||
try {
|
||||
final result = api_instance.getMemoryLane(timestamp);
|
||||
final result = api_instance.getMemoryLane(day, month);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getMemoryLane: $e\n');
|
||||
|
@ -1000,7 +1057,8 @@ try {
|
|||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**timestamp** | **DateTime**| Get pictures for +24 hours from this time going back x years |
|
||||
**day** | **int**| |
|
||||
**month** | **int**| |
|
||||
|
||||
### Return type
|
||||
|
||||
|
@ -1073,7 +1131,7 @@ 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)
|
||||
|
||||
# **getTimeBuckets**
|
||||
> List<TimeBucketResponseDto> getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, key)
|
||||
> List<TimeBucketResponseDto> getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, key)
|
||||
|
||||
|
||||
|
||||
|
@ -1102,10 +1160,11 @@ final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
|||
final personId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final isArchived = true; // bool |
|
||||
final isFavorite = true; // bool |
|
||||
final isTrashed = true; // bool |
|
||||
final key = key_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, key);
|
||||
final result = api_instance.getTimeBuckets(size, userId, albumId, personId, isArchived, isFavorite, isTrashed, key);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getTimeBuckets: $e\n');
|
||||
|
@ -1122,6 +1181,7 @@ Name | Type | Description | Notes
|
|||
**personId** | **String**| | [optional]
|
||||
**isArchived** | **bool**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isTrashed** | **bool**| | [optional]
|
||||
**key** | **String**| | [optional]
|
||||
|
||||
### Return type
|
||||
|
@ -1251,6 +1311,110 @@ 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)
|
||||
|
||||
# **restoreAssets**
|
||||
> restoreAssets(bulkIdsDto)
|
||||
|
||||
|
||||
|
||||
### 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 bulkIdsDto = BulkIdsDto(); // BulkIdsDto |
|
||||
|
||||
try {
|
||||
api_instance.restoreAssets(bulkIdsDto);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->restoreAssets: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**bulkIdsDto** | [**BulkIdsDto**](BulkIdsDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[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)
|
||||
|
||||
# **restoreTrash**
|
||||
> restoreTrash()
|
||||
|
||||
|
||||
|
||||
### 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();
|
||||
|
||||
try {
|
||||
api_instance.restoreTrash();
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->restoreTrash: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[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)
|
||||
|
||||
# **runAssetJobs**
|
||||
> runAssetJobs(assetJobsDto)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# openapi.model.DeleteAssetDto
|
||||
# openapi.model.AssetBulkDeleteDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
|
@ -8,6 +8,7 @@ import 'package:openapi/api.dart';
|
|||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**force** | **bool** | | [optional]
|
||||
**ids** | **List<String>** | | [default to const []]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
2
mobile/openapi/doc/AssetResponseDto.md
generated
2
mobile/openapi/doc/AssetResponseDto.md
generated
|
@ -21,8 +21,10 @@ Name | Type | Description | Notes
|
|||
**isFavorite** | **bool** | |
|
||||
**isOffline** | **bool** | |
|
||||
**isReadOnly** | **bool** | |
|
||||
**isTrashed** | **bool** | |
|
||||
**libraryId** | **String** | |
|
||||
**livePhotoVideoId** | **String** | | [optional]
|
||||
**localDateTime** | [**DateTime**](DateTime.md) | |
|
||||
**originalFileName** | **String** | |
|
||||
**originalPath** | **String** | |
|
||||
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
|
||||
|
|
16
mobile/openapi/doc/DeleteAssetResponseDto.md
generated
16
mobile/openapi/doc/DeleteAssetResponseDto.md
generated
|
@ -1,16 +0,0 @@
|
|||
# openapi.model.DeleteAssetResponseDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **String** | |
|
||||
**status** | [**DeleteAssetStatus**](DeleteAssetStatus.md) | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
56
mobile/openapi/doc/SearchApi.md
generated
56
mobile/openapi/doc/SearchApi.md
generated
|
@ -11,6 +11,7 @@ Method | HTTP request | Description
|
|||
------------- | ------------- | -------------
|
||||
[**getExploreData**](SearchApi.md#getexploredata) | **GET** /search/explore |
|
||||
[**search**](SearchApi.md#search) | **GET** /search |
|
||||
[**searchPerson**](SearchApi.md#searchperson) | **GET** /search/person |
|
||||
|
||||
|
||||
# **getExploreData**
|
||||
|
@ -149,3 +150,58 @@ 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)
|
||||
|
||||
# **searchPerson**
|
||||
> List<PersonResponseDto> searchPerson(name)
|
||||
|
||||
|
||||
|
||||
### 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 = SearchApi();
|
||||
final name = name_example; // String |
|
||||
|
||||
try {
|
||||
final result = api_instance.searchPerson(name);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling SearchApi->searchPerson: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**name** | **String**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**List<PersonResponseDto>**](PersonResponseDto.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)
|
||||
|
||||
|
|
2
mobile/openapi/doc/ServerConfigDto.md
generated
2
mobile/openapi/doc/ServerConfigDto.md
generated
|
@ -8,9 +8,11 @@ import 'package:openapi/api.dart';
|
|||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**isInitialized** | **bool** | |
|
||||
**loginPageMessage** | **String** | |
|
||||
**mapTileUrl** | **String** | |
|
||||
**oauthButtonText** | **String** | |
|
||||
**trashDays** | **int** | |
|
||||
|
||||
[[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/doc/ServerFeaturesDto.md
generated
1
mobile/openapi/doc/ServerFeaturesDto.md
generated
|
@ -19,6 +19,7 @@ Name | Type | Description | Notes
|
|||
**search** | **bool** | |
|
||||
**sidecar** | **bool** | |
|
||||
**tagImage** | **bool** | |
|
||||
**trash** | **bool** | |
|
||||
|
||||
[[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/doc/SystemConfigDto.md
generated
1
mobile/openapi/doc/SystemConfigDto.md
generated
|
@ -17,6 +17,7 @@ Name | Type | Description | Notes
|
|||
**reverseGeocoding** | [**SystemConfigReverseGeocodingDto**](SystemConfigReverseGeocodingDto.md) | |
|
||||
**storageTemplate** | [**SystemConfigStorageTemplateDto**](SystemConfigStorageTemplateDto.md) | |
|
||||
**thumbnail** | [**SystemConfigThumbnailDto**](SystemConfigThumbnailDto.md) | |
|
||||
**trash** | [**SystemConfigTrashDto**](SystemConfigTrashDto.md) | |
|
||||
|
||||
[[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,4 +1,4 @@
|
|||
# openapi.model.DeleteAssetStatus
|
||||
# openapi.model.SystemConfigTrashDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
|
@ -8,6 +8,8 @@ import 'package:openapi/api.dart';
|
|||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**days** | **int** | |
|
||||
**enabled** | **bool** | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
16
mobile/openapi/doc/UserApi.md
generated
16
mobile/openapi/doc/UserApi.md
generated
|
@ -410,6 +410,20 @@ Name | Type | Description | Notes
|
|||
### 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 = UserApi();
|
||||
final admin = true; // bool |
|
||||
|
@ -434,7 +448,7 @@ Name | Type | Description | Notes
|
|||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
|
|
5
mobile/openapi/lib/api.dart
generated
5
mobile/openapi/lib/api.dart
generated
|
@ -54,6 +54,7 @@ part 'model/admin_signup_response_dto.dart';
|
|||
part 'model/album_count_response_dto.dart';
|
||||
part 'model/album_response_dto.dart';
|
||||
part 'model/all_job_status_response_dto.dart';
|
||||
part 'model/asset_bulk_delete_dto.dart';
|
||||
part 'model/asset_bulk_update_dto.dart';
|
||||
part 'model/asset_bulk_upload_check_dto.dart';
|
||||
part 'model/asset_bulk_upload_check_item.dart';
|
||||
|
@ -90,9 +91,6 @@ part 'model/create_tag_dto.dart';
|
|||
part 'model/create_user_dto.dart';
|
||||
part 'model/curated_locations_response_dto.dart';
|
||||
part 'model/curated_objects_response_dto.dart';
|
||||
part 'model/delete_asset_dto.dart';
|
||||
part 'model/delete_asset_response_dto.dart';
|
||||
part 'model/delete_asset_status.dart';
|
||||
part 'model/download_archive_info.dart';
|
||||
part 'model/download_info_dto.dart';
|
||||
part 'model/download_response_dto.dart';
|
||||
|
@ -159,6 +157,7 @@ part 'model/system_config_reverse_geocoding_dto.dart';
|
|||
part 'model/system_config_storage_template_dto.dart';
|
||||
part 'model/system_config_template_storage_option_dto.dart';
|
||||
part 'model/system_config_thumbnail_dto.dart';
|
||||
part 'model/system_config_trash_dto.dart';
|
||||
part 'model/tag_response_dto.dart';
|
||||
part 'model/tag_type_enum.dart';
|
||||
part 'model/thumbnail_format.dart';
|
||||
|
|
186
mobile/openapi/lib/api/asset_api.dart
generated
186
mobile/openapi/lib/api/asset_api.dart
generated
|
@ -183,13 +183,13 @@ class AssetApi {
|
|||
/// Performs an HTTP 'DELETE /asset' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DeleteAssetDto] deleteAssetDto (required):
|
||||
Future<Response> deleteAssetWithHttpInfo(DeleteAssetDto deleteAssetDto,) async {
|
||||
/// * [AssetBulkDeleteDto] assetBulkDeleteDto (required):
|
||||
Future<Response> deleteAssetsWithHttpInfo(AssetBulkDeleteDto assetBulkDeleteDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = deleteAssetDto;
|
||||
Object? postBody = assetBulkDeleteDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
|
@ -211,23 +211,12 @@ class AssetApi {
|
|||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DeleteAssetDto] deleteAssetDto (required):
|
||||
Future<List<DeleteAssetResponseDto>?> deleteAsset(DeleteAssetDto deleteAssetDto,) async {
|
||||
final response = await deleteAssetWithHttpInfo(deleteAssetDto,);
|
||||
/// * [AssetBulkDeleteDto] assetBulkDeleteDto (required):
|
||||
Future<void> deleteAssets(AssetBulkDeleteDto assetBulkDeleteDto,) async {
|
||||
final response = await deleteAssetsWithHttpInfo(assetBulkDeleteDto,);
|
||||
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<DeleteAssetResponseDto>') as List)
|
||||
.cast<DeleteAssetResponseDto>()
|
||||
.toList();
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /asset/download/archive' operation and returns the [Response].
|
||||
|
@ -341,6 +330,39 @@ class AssetApi {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /asset/trash/empty' operation and returns the [Response].
|
||||
Future<Response> emptyTrashWithHttpInfo() async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/trash/empty';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> emptyTrash() async {
|
||||
final response = await emptyTrashWithHttpInfo();
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all AssetEntity belong to the user
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
|
@ -549,7 +571,9 @@ class AssetApi {
|
|||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
Future<Response> getAssetStatsWithHttpInfo({ bool? isArchived, bool? isFavorite, }) async {
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
Future<Response> getAssetStatsWithHttpInfo({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/statistics';
|
||||
|
||||
|
@ -566,6 +590,9 @@ class AssetApi {
|
|||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isTrashed != null) {
|
||||
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
@ -586,8 +613,10 @@ class AssetApi {
|
|||
/// * [bool] isArchived:
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
Future<AssetStatsResponseDto?> getAssetStats({ bool? isArchived, bool? isFavorite, }) async {
|
||||
final response = await getAssetStatsWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, );
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
Future<AssetStatsResponseDto?> getAssetStats({ bool? isArchived, bool? isFavorite, bool? isTrashed, }) async {
|
||||
final response = await getAssetStatsWithHttpInfo( isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -681,8 +710,10 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> getByTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, String? key, }) async {
|
||||
Future<Response> getByTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/time-bucket';
|
||||
|
||||
|
@ -708,6 +739,9 @@ class AssetApi {
|
|||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isTrashed != null) {
|
||||
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
|
||||
}
|
||||
queryParams.addAll(_queryParams('', 'timeBucket', timeBucket));
|
||||
if (key != null) {
|
||||
|
@ -744,9 +778,11 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<List<AssetResponseDto>?> getByTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, String? key, }) async {
|
||||
final response = await getByTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, key: key, );
|
||||
Future<List<AssetResponseDto>?> getByTimeBucket(TimeBucketSize size, String timeBucket, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, }) async {
|
||||
final response = await getByTimeBucketWithHttpInfo(size, timeBucket, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -984,9 +1020,10 @@ class AssetApi {
|
|||
/// Performs an HTTP 'GET /asset/memory-lane' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] timestamp (required):
|
||||
/// Get pictures for +24 hours from this time going back x years
|
||||
Future<Response> getMemoryLaneWithHttpInfo(DateTime timestamp,) async {
|
||||
/// * [int] day (required):
|
||||
///
|
||||
/// * [int] month (required):
|
||||
Future<Response> getMemoryLaneWithHttpInfo(int day, int month,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/memory-lane';
|
||||
|
||||
|
@ -997,7 +1034,8 @@ class AssetApi {
|
|||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
queryParams.addAll(_queryParams('', 'timestamp', timestamp));
|
||||
queryParams.addAll(_queryParams('', 'day', day));
|
||||
queryParams.addAll(_queryParams('', 'month', month));
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
@ -1015,10 +1053,11 @@ class AssetApi {
|
|||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [DateTime] timestamp (required):
|
||||
/// Get pictures for +24 hours from this time going back x years
|
||||
Future<List<MemoryLaneResponseDto>?> getMemoryLane(DateTime timestamp,) async {
|
||||
final response = await getMemoryLaneWithHttpInfo(timestamp,);
|
||||
/// * [int] day (required):
|
||||
///
|
||||
/// * [int] month (required):
|
||||
Future<List<MemoryLaneResponseDto>?> getMemoryLane(int day, int month,) async {
|
||||
final response = await getMemoryLaneWithHttpInfo(day, month,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -1104,8 +1143,10 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, String? key, }) async {
|
||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/time-buckets';
|
||||
|
||||
|
@ -1132,6 +1173,9 @@ class AssetApi {
|
|||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isTrashed != null) {
|
||||
queryParams.addAll(_queryParams('', 'isTrashed', isTrashed));
|
||||
}
|
||||
if (key != null) {
|
||||
queryParams.addAll(_queryParams('', 'key', key));
|
||||
}
|
||||
|
@ -1164,9 +1208,11 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isFavorite:
|
||||
///
|
||||
/// * [bool] isTrashed:
|
||||
///
|
||||
/// * [String] key:
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, String? key, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, key: key, );
|
||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? userId, String? albumId, String? personId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo(size, userId: userId, albumId: albumId, personId: personId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
@ -1286,6 +1332,78 @@ class AssetApi {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /asset/restore' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<Response> restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/restore';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = bulkIdsDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [BulkIdsDto] bulkIdsDto (required):
|
||||
Future<void> restoreAssets(BulkIdsDto bulkIdsDto,) async {
|
||||
final response = await restoreAssetsWithHttpInfo(bulkIdsDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /asset/trash/restore' operation and returns the [Response].
|
||||
Future<Response> restoreTrashWithHttpInfo() async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/trash/restore';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> restoreTrash() async {
|
||||
final response = await restoreTrashWithHttpInfo();
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /asset/jobs' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
|
|
52
mobile/openapi/lib/api/search_api.dart
generated
52
mobile/openapi/lib/api/search_api.dart
generated
|
@ -215,4 +215,56 @@ class SearchApi {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /search/person' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
Future<Response> searchPersonWithHttpInfo(String name,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/search/person';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
queryParams.addAll(_queryParams('', 'name', name));
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'GET',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] name (required):
|
||||
Future<List<PersonResponseDto>?> searchPerson(String name,) async {
|
||||
final response = await searchPersonWithHttpInfo(name,);
|
||||
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<PersonResponseDto>') as List)
|
||||
.cast<PersonResponseDto>()
|
||||
.toList();
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
10
mobile/openapi/lib/api_client.dart
generated
10
mobile/openapi/lib/api_client.dart
generated
|
@ -199,6 +199,8 @@ class ApiClient {
|
|||
return AlbumResponseDto.fromJson(value);
|
||||
case 'AllJobStatusResponseDto':
|
||||
return AllJobStatusResponseDto.fromJson(value);
|
||||
case 'AssetBulkDeleteDto':
|
||||
return AssetBulkDeleteDto.fromJson(value);
|
||||
case 'AssetBulkUpdateDto':
|
||||
return AssetBulkUpdateDto.fromJson(value);
|
||||
case 'AssetBulkUploadCheckDto':
|
||||
|
@ -271,12 +273,6 @@ class ApiClient {
|
|||
return CuratedLocationsResponseDto.fromJson(value);
|
||||
case 'CuratedObjectsResponseDto':
|
||||
return CuratedObjectsResponseDto.fromJson(value);
|
||||
case 'DeleteAssetDto':
|
||||
return DeleteAssetDto.fromJson(value);
|
||||
case 'DeleteAssetResponseDto':
|
||||
return DeleteAssetResponseDto.fromJson(value);
|
||||
case 'DeleteAssetStatus':
|
||||
return DeleteAssetStatusTypeTransformer().decode(value);
|
||||
case 'DownloadArchiveInfo':
|
||||
return DownloadArchiveInfo.fromJson(value);
|
||||
case 'DownloadInfoDto':
|
||||
|
@ -409,6 +405,8 @@ class ApiClient {
|
|||
return SystemConfigTemplateStorageOptionDto.fromJson(value);
|
||||
case 'SystemConfigThumbnailDto':
|
||||
return SystemConfigThumbnailDto.fromJson(value);
|
||||
case 'SystemConfigTrashDto':
|
||||
return SystemConfigTrashDto.fromJson(value);
|
||||
case 'TagResponseDto':
|
||||
return TagResponseDto.fromJson(value);
|
||||
case 'TagTypeEnum':
|
||||
|
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
|
@ -76,9 +76,6 @@ String parameterToString(dynamic value) {
|
|||
if (value is Colorspace) {
|
||||
return ColorspaceTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is DeleteAssetStatus) {
|
||||
return DeleteAssetStatusTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is EntityType) {
|
||||
return EntityTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
|
|
@ -10,40 +10,57 @@
|
|||
|
||||
part of openapi.api;
|
||||
|
||||
class DeleteAssetDto {
|
||||
/// Returns a new [DeleteAssetDto] instance.
|
||||
DeleteAssetDto({
|
||||
class AssetBulkDeleteDto {
|
||||
/// Returns a new [AssetBulkDeleteDto] instance.
|
||||
AssetBulkDeleteDto({
|
||||
this.force,
|
||||
this.ids = const [],
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? force;
|
||||
|
||||
List<String> ids;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is DeleteAssetDto &&
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto &&
|
||||
other.force == force &&
|
||||
other.ids == ids;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(force == null ? 0 : force!.hashCode) +
|
||||
(ids.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'DeleteAssetDto[ids=$ids]';
|
||||
String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.force != null) {
|
||||
json[r'force'] = this.force;
|
||||
} else {
|
||||
// json[r'force'] = null;
|
||||
}
|
||||
json[r'ids'] = this.ids;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [DeleteAssetDto] instance and imports its values from
|
||||
/// Returns a new [AssetBulkDeleteDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static DeleteAssetDto? fromJson(dynamic value) {
|
||||
static AssetBulkDeleteDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return DeleteAssetDto(
|
||||
return AssetBulkDeleteDto(
|
||||
force: mapValueOfType<bool>(json, r'force'),
|
||||
ids: json[r'ids'] is List
|
||||
? (json[r'ids'] as List).cast<String>()
|
||||
: const [],
|
||||
|
@ -52,11 +69,11 @@ class DeleteAssetDto {
|
|||
return null;
|
||||
}
|
||||
|
||||
static List<DeleteAssetDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <DeleteAssetDto>[];
|
||||
static List<AssetBulkDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetBulkDeleteDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = DeleteAssetDto.fromJson(row);
|
||||
final value = AssetBulkDeleteDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
|
@ -65,12 +82,12 @@ class DeleteAssetDto {
|
|||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, DeleteAssetDto> mapFromJson(dynamic json) {
|
||||
final map = <String, DeleteAssetDto>{};
|
||||
static Map<String, AssetBulkDeleteDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AssetBulkDeleteDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = DeleteAssetDto.fromJson(entry.value);
|
||||
final value = AssetBulkDeleteDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
|
@ -79,14 +96,14 @@ class DeleteAssetDto {
|
|||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of DeleteAssetDto-objects as value to a dart map
|
||||
static Map<String, List<DeleteAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<DeleteAssetDto>>{};
|
||||
// maps a json object with a list of AssetBulkDeleteDto-objects as value to a dart map
|
||||
static Map<String, List<AssetBulkDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AssetBulkDeleteDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = DeleteAssetDto.listFromJson(entry.value, growable: growable,);
|
||||
map[entry.key] = AssetBulkDeleteDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
18
mobile/openapi/lib/model/asset_response_dto.dart
generated
18
mobile/openapi/lib/model/asset_response_dto.dart
generated
|
@ -26,8 +26,10 @@ class AssetResponseDto {
|
|||
required this.isFavorite,
|
||||
required this.isOffline,
|
||||
required this.isReadOnly,
|
||||
required this.isTrashed,
|
||||
required this.libraryId,
|
||||
this.livePhotoVideoId,
|
||||
required this.localDateTime,
|
||||
required this.originalFileName,
|
||||
required this.originalPath,
|
||||
this.owner,
|
||||
|
@ -74,10 +76,14 @@ class AssetResponseDto {
|
|||
|
||||
bool isReadOnly;
|
||||
|
||||
bool isTrashed;
|
||||
|
||||
String libraryId;
|
||||
|
||||
String? livePhotoVideoId;
|
||||
|
||||
DateTime localDateTime;
|
||||
|
||||
String originalFileName;
|
||||
|
||||
String originalPath;
|
||||
|
@ -128,8 +134,10 @@ class AssetResponseDto {
|
|||
other.isFavorite == isFavorite &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isReadOnly == isReadOnly &&
|
||||
other.isTrashed == isTrashed &&
|
||||
other.libraryId == libraryId &&
|
||||
other.livePhotoVideoId == livePhotoVideoId &&
|
||||
other.localDateTime == localDateTime &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalPath == originalPath &&
|
||||
other.owner == owner &&
|
||||
|
@ -158,8 +166,10 @@ class AssetResponseDto {
|
|||
(isFavorite.hashCode) +
|
||||
(isOffline.hashCode) +
|
||||
(isReadOnly.hashCode) +
|
||||
(isTrashed.hashCode) +
|
||||
(libraryId.hashCode) +
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
(localDateTime.hashCode) +
|
||||
(originalFileName.hashCode) +
|
||||
(originalPath.hashCode) +
|
||||
(owner == null ? 0 : owner!.hashCode) +
|
||||
|
@ -173,7 +183,7 @@ class AssetResponseDto {
|
|||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -194,12 +204,14 @@ class AssetResponseDto {
|
|||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isOffline'] = this.isOffline;
|
||||
json[r'isReadOnly'] = this.isReadOnly;
|
||||
json[r'isTrashed'] = this.isTrashed;
|
||||
json[r'libraryId'] = this.libraryId;
|
||||
if (this.livePhotoVideoId != null) {
|
||||
json[r'livePhotoVideoId'] = this.livePhotoVideoId;
|
||||
} else {
|
||||
// json[r'livePhotoVideoId'] = null;
|
||||
}
|
||||
json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String();
|
||||
json[r'originalFileName'] = this.originalFileName;
|
||||
json[r'originalPath'] = this.originalPath;
|
||||
if (this.owner != null) {
|
||||
|
@ -247,8 +259,10 @@ class AssetResponseDto {
|
|||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
|
||||
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly')!,
|
||||
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId')!,
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
localDateTime: mapDateTime(json, r'localDateTime', '')!,
|
||||
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||
owner: UserResponseDto.fromJson(json[r'owner']),
|
||||
|
@ -319,7 +333,9 @@ class AssetResponseDto {
|
|||
'isFavorite',
|
||||
'isOffline',
|
||||
'isReadOnly',
|
||||
'isTrashed',
|
||||
'libraryId',
|
||||
'localDateTime',
|
||||
'originalFileName',
|
||||
'originalPath',
|
||||
'ownerId',
|
||||
|
|
85
mobile/openapi/lib/model/delete_asset_status.dart
generated
85
mobile/openapi/lib/model/delete_asset_status.dart
generated
|
@ -1,85 +0,0 @@
|
|||
//
|
||||
// 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 DeleteAssetStatus {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const DeleteAssetStatus._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const SUCCESS = DeleteAssetStatus._(r'SUCCESS');
|
||||
static const FAILED = DeleteAssetStatus._(r'FAILED');
|
||||
|
||||
/// List of all possible values in this [enum][DeleteAssetStatus].
|
||||
static const values = <DeleteAssetStatus>[
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
];
|
||||
|
||||
static DeleteAssetStatus? fromJson(dynamic value) => DeleteAssetStatusTypeTransformer().decode(value);
|
||||
|
||||
static List<DeleteAssetStatus>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <DeleteAssetStatus>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = DeleteAssetStatus.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [DeleteAssetStatus] to String,
|
||||
/// and [decode] dynamic data back to [DeleteAssetStatus].
|
||||
class DeleteAssetStatusTypeTransformer {
|
||||
factory DeleteAssetStatusTypeTransformer() => _instance ??= const DeleteAssetStatusTypeTransformer._();
|
||||
|
||||
const DeleteAssetStatusTypeTransformer._();
|
||||
|
||||
String encode(DeleteAssetStatus data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a DeleteAssetStatus.
|
||||
///
|
||||
/// 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.
|
||||
DeleteAssetStatus? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'SUCCESS': return DeleteAssetStatus.SUCCESS;
|
||||
case r'FAILED': return DeleteAssetStatus.FAILED;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [DeleteAssetStatusTypeTransformer] instance.
|
||||
static DeleteAssetStatusTypeTransformer? _instance;
|
||||
}
|
||||
|
22
mobile/openapi/lib/model/server_config_dto.dart
generated
22
mobile/openapi/lib/model/server_config_dto.dart
generated
|
@ -13,38 +13,50 @@ part of openapi.api;
|
|||
class ServerConfigDto {
|
||||
/// Returns a new [ServerConfigDto] instance.
|
||||
ServerConfigDto({
|
||||
required this.isInitialized,
|
||||
required this.loginPageMessage,
|
||||
required this.mapTileUrl,
|
||||
required this.oauthButtonText,
|
||||
required this.trashDays,
|
||||
});
|
||||
|
||||
bool isInitialized;
|
||||
|
||||
String loginPageMessage;
|
||||
|
||||
String mapTileUrl;
|
||||
|
||||
String oauthButtonText;
|
||||
|
||||
int trashDays;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is ServerConfigDto &&
|
||||
other.isInitialized == isInitialized &&
|
||||
other.loginPageMessage == loginPageMessage &&
|
||||
other.mapTileUrl == mapTileUrl &&
|
||||
other.oauthButtonText == oauthButtonText;
|
||||
other.oauthButtonText == oauthButtonText &&
|
||||
other.trashDays == trashDays;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(isInitialized.hashCode) +
|
||||
(loginPageMessage.hashCode) +
|
||||
(mapTileUrl.hashCode) +
|
||||
(oauthButtonText.hashCode);
|
||||
(oauthButtonText.hashCode) +
|
||||
(trashDays.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'ServerConfigDto[loginPageMessage=$loginPageMessage, mapTileUrl=$mapTileUrl, oauthButtonText=$oauthButtonText]';
|
||||
String toString() => 'ServerConfigDto[isInitialized=$isInitialized, loginPageMessage=$loginPageMessage, mapTileUrl=$mapTileUrl, oauthButtonText=$oauthButtonText, trashDays=$trashDays]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'isInitialized'] = this.isInitialized;
|
||||
json[r'loginPageMessage'] = this.loginPageMessage;
|
||||
json[r'mapTileUrl'] = this.mapTileUrl;
|
||||
json[r'oauthButtonText'] = this.oauthButtonText;
|
||||
json[r'trashDays'] = this.trashDays;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -56,9 +68,11 @@ class ServerConfigDto {
|
|||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return ServerConfigDto(
|
||||
isInitialized: mapValueOfType<bool>(json, r'isInitialized')!,
|
||||
loginPageMessage: mapValueOfType<String>(json, r'loginPageMessage')!,
|
||||
mapTileUrl: mapValueOfType<String>(json, r'mapTileUrl')!,
|
||||
oauthButtonText: mapValueOfType<String>(json, r'oauthButtonText')!,
|
||||
trashDays: mapValueOfType<int>(json, r'trashDays')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -106,9 +120,11 @@ class ServerConfigDto {
|
|||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'isInitialized',
|
||||
'loginPageMessage',
|
||||
'mapTileUrl',
|
||||
'oauthButtonText',
|
||||
'trashDays',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
14
mobile/openapi/lib/model/server_features_dto.dart
generated
14
mobile/openapi/lib/model/server_features_dto.dart
generated
|
@ -24,6 +24,7 @@ class ServerFeaturesDto {
|
|||
required this.search,
|
||||
required this.sidecar,
|
||||
required this.tagImage,
|
||||
required this.trash,
|
||||
});
|
||||
|
||||
bool clipEncode;
|
||||
|
@ -48,6 +49,8 @@ class ServerFeaturesDto {
|
|||
|
||||
bool tagImage;
|
||||
|
||||
bool trash;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is ServerFeaturesDto &&
|
||||
other.clipEncode == clipEncode &&
|
||||
|
@ -60,7 +63,8 @@ class ServerFeaturesDto {
|
|||
other.reverseGeocoding == reverseGeocoding &&
|
||||
other.search == search &&
|
||||
other.sidecar == sidecar &&
|
||||
other.tagImage == tagImage;
|
||||
other.tagImage == tagImage &&
|
||||
other.trash == trash;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -75,10 +79,11 @@ class ServerFeaturesDto {
|
|||
(reverseGeocoding.hashCode) +
|
||||
(search.hashCode) +
|
||||
(sidecar.hashCode) +
|
||||
(tagImage.hashCode);
|
||||
(tagImage.hashCode) +
|
||||
(trash.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, tagImage=$tagImage]';
|
||||
String toString() => 'ServerFeaturesDto[clipEncode=$clipEncode, configFile=$configFile, facialRecognition=$facialRecognition, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, tagImage=$tagImage, trash=$trash]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -93,6 +98,7 @@ class ServerFeaturesDto {
|
|||
json[r'search'] = this.search;
|
||||
json[r'sidecar'] = this.sidecar;
|
||||
json[r'tagImage'] = this.tagImage;
|
||||
json[r'trash'] = this.trash;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -115,6 +121,7 @@ class ServerFeaturesDto {
|
|||
search: mapValueOfType<bool>(json, r'search')!,
|
||||
sidecar: mapValueOfType<bool>(json, r'sidecar')!,
|
||||
tagImage: mapValueOfType<bool>(json, r'tagImage')!,
|
||||
trash: mapValueOfType<bool>(json, r'trash')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -173,6 +180,7 @@ class ServerFeaturesDto {
|
|||
'search',
|
||||
'sidecar',
|
||||
'tagImage',
|
||||
'trash',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
14
mobile/openapi/lib/model/system_config_dto.dart
generated
14
mobile/openapi/lib/model/system_config_dto.dart
generated
|
@ -22,6 +22,7 @@ class SystemConfigDto {
|
|||
required this.reverseGeocoding,
|
||||
required this.storageTemplate,
|
||||
required this.thumbnail,
|
||||
required this.trash,
|
||||
});
|
||||
|
||||
SystemConfigFFmpegDto ffmpeg;
|
||||
|
@ -42,6 +43,8 @@ class SystemConfigDto {
|
|||
|
||||
SystemConfigThumbnailDto thumbnail;
|
||||
|
||||
SystemConfigTrashDto trash;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto &&
|
||||
other.ffmpeg == ffmpeg &&
|
||||
|
@ -52,7 +55,8 @@ class SystemConfigDto {
|
|||
other.passwordLogin == passwordLogin &&
|
||||
other.reverseGeocoding == reverseGeocoding &&
|
||||
other.storageTemplate == storageTemplate &&
|
||||
other.thumbnail == thumbnail;
|
||||
other.thumbnail == thumbnail &&
|
||||
other.trash == trash;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -65,10 +69,11 @@ class SystemConfigDto {
|
|||
(passwordLogin.hashCode) +
|
||||
(reverseGeocoding.hashCode) +
|
||||
(storageTemplate.hashCode) +
|
||||
(thumbnail.hashCode);
|
||||
(thumbnail.hashCode) +
|
||||
(trash.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, thumbnail=$thumbnail]';
|
||||
String toString() => 'SystemConfigDto[ffmpeg=$ffmpeg, job=$job, machineLearning=$machineLearning, map=$map, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, storageTemplate=$storageTemplate, thumbnail=$thumbnail, trash=$trash]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -81,6 +86,7 @@ class SystemConfigDto {
|
|||
json[r'reverseGeocoding'] = this.reverseGeocoding;
|
||||
json[r'storageTemplate'] = this.storageTemplate;
|
||||
json[r'thumbnail'] = this.thumbnail;
|
||||
json[r'trash'] = this.trash;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -101,6 +107,7 @@ class SystemConfigDto {
|
|||
reverseGeocoding: SystemConfigReverseGeocodingDto.fromJson(json[r'reverseGeocoding'])!,
|
||||
storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!,
|
||||
thumbnail: SystemConfigThumbnailDto.fromJson(json[r'thumbnail'])!,
|
||||
trash: SystemConfigTrashDto.fromJson(json[r'trash'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -157,6 +164,7 @@ class SystemConfigDto {
|
|||
'reverseGeocoding',
|
||||
'storageTemplate',
|
||||
'thumbnail',
|
||||
'trash',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,58 +10,58 @@
|
|||
|
||||
part of openapi.api;
|
||||
|
||||
class DeleteAssetResponseDto {
|
||||
/// Returns a new [DeleteAssetResponseDto] instance.
|
||||
DeleteAssetResponseDto({
|
||||
required this.id,
|
||||
required this.status,
|
||||
class SystemConfigTrashDto {
|
||||
/// Returns a new [SystemConfigTrashDto] instance.
|
||||
SystemConfigTrashDto({
|
||||
required this.days,
|
||||
required this.enabled,
|
||||
});
|
||||
|
||||
String id;
|
||||
int days;
|
||||
|
||||
DeleteAssetStatus status;
|
||||
bool enabled;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is DeleteAssetResponseDto &&
|
||||
other.id == id &&
|
||||
other.status == status;
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigTrashDto &&
|
||||
other.days == days &&
|
||||
other.enabled == enabled;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(status.hashCode);
|
||||
(days.hashCode) +
|
||||
(enabled.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'DeleteAssetResponseDto[id=$id, status=$status]';
|
||||
String toString() => 'SystemConfigTrashDto[days=$days, enabled=$enabled]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
json[r'status'] = this.status;
|
||||
json[r'days'] = this.days;
|
||||
json[r'enabled'] = this.enabled;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [DeleteAssetResponseDto] instance and imports its values from
|
||||
/// Returns a new [SystemConfigTrashDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static DeleteAssetResponseDto? fromJson(dynamic value) {
|
||||
static SystemConfigTrashDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return DeleteAssetResponseDto(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
status: DeleteAssetStatus.fromJson(json[r'status'])!,
|
||||
return SystemConfigTrashDto(
|
||||
days: mapValueOfType<int>(json, r'days')!,
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<DeleteAssetResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <DeleteAssetResponseDto>[];
|
||||
static List<SystemConfigTrashDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SystemConfigTrashDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = DeleteAssetResponseDto.fromJson(row);
|
||||
final value = SystemConfigTrashDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
|
@ -70,12 +70,12 @@ class DeleteAssetResponseDto {
|
|||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, DeleteAssetResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, DeleteAssetResponseDto>{};
|
||||
static Map<String, SystemConfigTrashDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SystemConfigTrashDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = DeleteAssetResponseDto.fromJson(entry.value);
|
||||
final value = SystemConfigTrashDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
|
@ -84,14 +84,14 @@ class DeleteAssetResponseDto {
|
|||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of DeleteAssetResponseDto-objects as value to a dart map
|
||||
static Map<String, List<DeleteAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<DeleteAssetResponseDto>>{};
|
||||
// maps a json object with a list of SystemConfigTrashDto-objects as value to a dart map
|
||||
static Map<String, List<SystemConfigTrashDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SystemConfigTrashDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = DeleteAssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
map[entry.key] = SystemConfigTrashDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
|
@ -99,8 +99,8 @@ class DeleteAssetResponseDto {
|
|||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'id',
|
||||
'status',
|
||||
'days',
|
||||
'enabled',
|
||||
};
|
||||
}
|
||||
|
27
mobile/openapi/test/asset_api_test.dart
generated
27
mobile/openapi/test/asset_api_test.dart
generated
|
@ -38,8 +38,8 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<DeleteAssetResponseDto>> deleteAsset(DeleteAssetDto deleteAssetDto) async
|
||||
test('test deleteAsset', () async {
|
||||
//Future deleteAssets(AssetBulkDeleteDto assetBulkDeleteDto) async
|
||||
test('test deleteAssets', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
@ -53,6 +53,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future emptyTrash() async
|
||||
test('test emptyTrash', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Get all AssetEntity belong to the user
|
||||
//
|
||||
//Future<List<AssetResponseDto>> getAllAssets({ String userId, bool isFavorite, bool isArchived, num skip, DateTime updatedAfter, String ifNoneMatch }) async
|
||||
|
@ -72,7 +77,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<AssetStatsResponseDto> getAssetStats({ bool isArchived, bool isFavorite }) async
|
||||
//Future<AssetStatsResponseDto> getAssetStats({ bool isArchived, bool isFavorite, bool isTrashed }) async
|
||||
test('test getAssetStats', () async {
|
||||
// TODO
|
||||
});
|
||||
|
@ -82,7 +87,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<AssetResponseDto>> getByTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, String key }) async
|
||||
//Future<List<AssetResponseDto>> getByTimeBucket(TimeBucketSize size, String timeBucket, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, String key }) async
|
||||
test('test getByTimeBucket', () async {
|
||||
// TODO
|
||||
});
|
||||
|
@ -107,7 +112,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<MemoryLaneResponseDto>> getMemoryLane(DateTime timestamp) async
|
||||
//Future<List<MemoryLaneResponseDto>> getMemoryLane(int day, int month) async
|
||||
test('test getMemoryLane', () async {
|
||||
// TODO
|
||||
});
|
||||
|
@ -117,7 +122,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, String key }) async
|
||||
//Future<List<TimeBucketResponseDto>> getTimeBuckets(TimeBucketSize size, { String userId, String albumId, String personId, bool isArchived, bool isFavorite, bool isTrashed, String key }) async
|
||||
test('test getTimeBuckets', () async {
|
||||
// TODO
|
||||
});
|
||||
|
@ -134,6 +139,16 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future restoreAssets(BulkIdsDto bulkIdsDto) async
|
||||
test('test restoreAssets', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future restoreTrash() async
|
||||
test('test restoreTrash', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future runAssetJobs(AssetJobsDto assetJobsDto) async
|
||||
test('test runAssetJobs', () async {
|
||||
// TODO
|
||||
|
|
|
@ -11,11 +11,16 @@
|
|||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for DeleteAssetDto
|
||||
// tests for AssetBulkDeleteDto
|
||||
void main() {
|
||||
// final instance = DeleteAssetDto();
|
||||
// final instance = AssetBulkDeleteDto();
|
||||
|
||||
group('test AssetBulkDeleteDto', () {
|
||||
// bool force
|
||||
test('to test the property `force`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
group('test DeleteAssetDto', () {
|
||||
// List<String> ids (default value: const [])
|
||||
test('to test the property `ids`', () async {
|
||||
// TODO
|
10
mobile/openapi/test/asset_response_dto_test.dart
generated
10
mobile/openapi/test/asset_response_dto_test.dart
generated
|
@ -82,6 +82,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool isTrashed
|
||||
test('to test the property `isTrashed`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String libraryId
|
||||
test('to test the property `libraryId`', () async {
|
||||
// TODO
|
||||
|
@ -92,6 +97,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// DateTime localDateTime
|
||||
test('to test the property `localDateTime`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String originalFileName
|
||||
test('to test the property `originalFileName`', () async {
|
||||
// TODO
|
||||
|
|
21
mobile/openapi/test/delete_asset_status_test.dart
generated
21
mobile/openapi/test/delete_asset_status_test.dart
generated
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// 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 DeleteAssetStatus
|
||||
void main() {
|
||||
|
||||
group('test DeleteAssetStatus', () {
|
||||
|
||||
});
|
||||
|
||||
}
|
5
mobile/openapi/test/search_api_test.dart
generated
5
mobile/openapi/test/search_api_test.dart
generated
|
@ -27,5 +27,10 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<List<PersonResponseDto>> searchPerson(String name) async
|
||||
test('test searchPerson', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
|
10
mobile/openapi/test/server_config_dto_test.dart
generated
10
mobile/openapi/test/server_config_dto_test.dart
generated
|
@ -16,6 +16,11 @@ void main() {
|
|||
// final instance = ServerConfigDto();
|
||||
|
||||
group('test ServerConfigDto', () {
|
||||
// bool isInitialized
|
||||
test('to test the property `isInitialized`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String loginPageMessage
|
||||
test('to test the property `loginPageMessage`', () async {
|
||||
// TODO
|
||||
|
@ -31,6 +36,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// int trashDays
|
||||
test('to test the property `trashDays`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -71,6 +71,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool trash
|
||||
test('to test the property `trash`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
5
mobile/openapi/test/system_config_dto_test.dart
generated
5
mobile/openapi/test/system_config_dto_test.dart
generated
|
@ -61,6 +61,11 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// SystemConfigTrashDto trash
|
||||
test('to test the property `trash`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -11,18 +11,18 @@
|
|||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for DeleteAssetResponseDto
|
||||
// tests for SystemConfigTrashDto
|
||||
void main() {
|
||||
// final instance = DeleteAssetResponseDto();
|
||||
// final instance = SystemConfigTrashDto();
|
||||
|
||||
group('test DeleteAssetResponseDto', () {
|
||||
// String id
|
||||
test('to test the property `id`', () async {
|
||||
group('test SystemConfigTrashDto', () {
|
||||
// int days
|
||||
test('to test the property `days`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// DeleteAssetStatus status
|
||||
test('to test the property `status`', () async {
|
||||
// bool enabled
|
||||
test('to test the property `enabled`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
|
@ -1071,10 +1071,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: photo_manager
|
||||
sha256: b2d81bd197323697d1b335e2e04cea2f67e11624ced77cfd02917a10afaeba73
|
||||
sha256: "41eaa1d1fa51bac1c8f2f6debfd34074edcc6b330aa96bb3d33c3bc2fc6c8a5c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.1"
|
||||
version: "2.7.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -13,7 +13,7 @@ dependencies:
|
|||
sdk: flutter
|
||||
|
||||
path_provider_ios:
|
||||
photo_manager: ^2.5.0
|
||||
photo_manager: ^2.7.2
|
||||
flutter_hooks: ^0.18.6
|
||||
hooks_riverpod: ^2.2.0
|
||||
cached_network_image: ^3.2.2
|
||||
|
|
|
@ -24,6 +24,7 @@ void main() {
|
|||
fileName: '',
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isTrashed: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ void main() {
|
|||
fileName: localId ?? remoteId ?? "",
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isTrashed: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -681,30 +681,20 @@
|
|||
},
|
||||
"/asset": {
|
||||
"delete": {
|
||||
"operationId": "deleteAsset",
|
||||
"operationId": "deleteAssets",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeleteAssetDto"
|
||||
"$ref": "#/components/schemas/AssetBulkDeleteDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeleteAssetResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
|
@ -1477,13 +1467,19 @@
|
|||
"operationId": "getMemoryLane",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"name": "day",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"description": "Get pictures for +24 hours from this time going back x years",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "month",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -1562,6 +1558,41 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/asset/restore": {
|
||||
"post": {
|
||||
"operationId": "restoreAssets",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BulkIdsDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Asset"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/search": {
|
||||
"post": {
|
||||
"operationId": "searchAsset",
|
||||
|
@ -1661,6 +1692,14 @@
|
|||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isTrashed",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -1811,6 +1850,14 @@
|
|||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isTrashed",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "timeBucket",
|
||||
"required": true,
|
||||
|
@ -1923,6 +1970,14 @@
|
|||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isTrashed",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"required": false,
|
||||
|
@ -1972,6 +2027,56 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/asset/trash/empty": {
|
||||
"post": {
|
||||
"operationId": "emptyTrash",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Asset"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/trash/restore": {
|
||||
"post": {
|
||||
"operationId": "restoreTrash",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Asset"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/asset/upload": {
|
||||
"post": {
|
||||
"operationId": "uploadFile",
|
||||
|
@ -3684,6 +3789,50 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/search/person": {
|
||||
"get": {
|
||||
"operationId": "searchPerson",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Search"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/server-info": {
|
||||
"get": {
|
||||
"operationId": "getServerInfo",
|
||||
|
@ -4855,6 +5004,17 @@
|
|||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
]
|
||||
|
@ -5392,6 +5552,24 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetBulkDeleteDto": {
|
||||
"properties": {
|
||||
"force": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ids": {
|
||||
"items": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ids"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AssetBulkUpdateDto": {
|
||||
"properties": {
|
||||
"ids": {
|
||||
|
@ -5610,6 +5788,9 @@
|
|||
"isReadOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isTrashed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"libraryId": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -5617,6 +5798,10 @@
|
|||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"localDateTime": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"originalFileName": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -5676,6 +5861,8 @@
|
|||
"updatedAt",
|
||||
"isFavorite",
|
||||
"isArchived",
|
||||
"isTrashed",
|
||||
"localDateTime",
|
||||
"isOffline",
|
||||
"isExternal",
|
||||
"isReadOnly",
|
||||
|
@ -6211,48 +6398,6 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeleteAssetDto": {
|
||||
"properties": {
|
||||
"ids": {
|
||||
"example": [
|
||||
"bf973405-3f2a-48d2-a687-2ed4167164be",
|
||||
"dd41870b-5d00-46d2-924e-1d8489a0aa0f",
|
||||
"fad77c3f-deef-4e7e-9608-14c1aa4e559a"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "Array of asset IDs to delete",
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ids"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeleteAssetResponseDto": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/DeleteAssetStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DeleteAssetStatus": {
|
||||
"enum": [
|
||||
"SUCCESS",
|
||||
"FAILED"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"DownloadArchiveInfo": {
|
||||
"properties": {
|
||||
"assetIds": {
|
||||
|
@ -7206,6 +7351,9 @@
|
|||
},
|
||||
"ServerConfigDto": {
|
||||
"properties": {
|
||||
"isInitialized": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"loginPageMessage": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -7214,12 +7362,17 @@
|
|||
},
|
||||
"oauthButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"trashDays": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"trashDays",
|
||||
"oauthButtonText",
|
||||
"loginPageMessage",
|
||||
"mapTileUrl"
|
||||
"mapTileUrl",
|
||||
"isInitialized"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -7257,6 +7410,9 @@
|
|||
},
|
||||
"tagImage": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"trash": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -7264,6 +7420,7 @@
|
|||
"configFile",
|
||||
"facialRecognition",
|
||||
"map",
|
||||
"trash",
|
||||
"reverseGeocoding",
|
||||
"oauth",
|
||||
"oauthAutoLaunch",
|
||||
|
@ -7619,6 +7776,9 @@
|
|||
},
|
||||
"thumbnail": {
|
||||
"$ref": "#/components/schemas/SystemConfigThumbnailDto"
|
||||
},
|
||||
"trash": {
|
||||
"$ref": "#/components/schemas/SystemConfigTrashDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -7630,7 +7790,8 @@
|
|||
"reverseGeocoding",
|
||||
"storageTemplate",
|
||||
"job",
|
||||
"thumbnail"
|
||||
"thumbnail",
|
||||
"trash"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
@ -7980,6 +8141,21 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SystemConfigTrashDto": {
|
||||
"properties": {
|
||||
"days": {
|
||||
"type": "integer"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"days",
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TagResponseDto": {
|
||||
"properties": {
|
||||
"id": {
|
||||
|
|
7768
server/package-lock.json
generated
7768
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -26,13 +26,13 @@
|
|||
"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": "jest --config test/e2e/jest-e2e.json --runInBand",
|
||||
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
|
||||
"typeorm:migrations:create": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:create",
|
||||
"typeorm:migrations:generate": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate -d ./src/infra/database.config.ts",
|
||||
"typeorm:migrations:run": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:run -d ./src/infra/database.config.ts",
|
||||
"typeorm:migrations:revert": "node --require ts-node/register ./node_modules/typeorm/cli.js migration:revert -d ./src/infra/database.config.ts",
|
||||
"typeorm:schema:drop": "node --require ts-node/register ./node_modules/typeorm/cli.js schema:drop -d ./src/infra/database.config.ts",
|
||||
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --max_old_space_size=4096' jest --config test/e2e/jest-e2e.json --runInBand --forceExit",
|
||||
"typeorm": "typeorm",
|
||||
"typeorm:migrations:create": "typeorm migration:create",
|
||||
"typeorm:migrations:generate": "typeorm migration:generate -d ./dist/infra/database.config.js",
|
||||
"typeorm:migrations:run": "typeorm migration:run -d ./dist/infra/database.config.js",
|
||||
"typeorm:migrations:revert": "typeorm migration:revert -d ./dist/infra/database.config.js",
|
||||
"typeorm:schema:drop": "typeorm schema:drop -d ./dist/infra/database.config.js",
|
||||
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
|
||||
"api:typescript": "bash ./bin/generate-open-api.sh web",
|
||||
"api:dart": "bash ./bin/generate-open-api.sh mobile",
|
||||
|
@ -126,7 +126,8 @@
|
|||
"ts-loader": "^9.4.4",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.2.2",
|
||||
"utimes": "^5.2.1"
|
||||
},
|
||||
"jest": {
|
||||
"clearMocks": true,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IAccessRepository } from './access.repository';
|
||||
import { IAccessRepository } from '../repositories';
|
||||
|
||||
export enum Permission {
|
||||
// ASSET_CREATE = 'asset.create',
|
||||
ASSET_READ = 'asset.read',
|
||||
ASSET_UPDATE = 'asset.update',
|
||||
ASSET_DELETE = 'asset.delete',
|
||||
ASSET_RESTORE = 'asset.restore',
|
||||
ASSET_SHARE = 'asset.share',
|
||||
ASSET_VIEW = 'asset.view',
|
||||
ASSET_DOWNLOAD = 'asset.download',
|
||||
|
@ -128,6 +129,9 @@ export class AccessCore {
|
|||
case Permission.ASSET_DELETE:
|
||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
||||
|
||||
case Permission.ASSET_RESTORE:
|
||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
||||
|
||||
case Permission.ASSET_SHARE:
|
||||
return (
|
||||
(await this.repository.asset.hasOwnerAccess(authUser.id, id)) ||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export * from './access.core';
|
||||
export * from './access.repository';
|
||||
|
|
|
@ -12,10 +12,9 @@ import {
|
|||
userStub,
|
||||
} from '@test';
|
||||
import _ from 'lodash';
|
||||
import { BulkIdErrorReason, IAssetRepository } from '../asset';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { IUserRepository } from '../user';
|
||||
import { IAlbumRepository } from './album.repository';
|
||||
import { BulkIdErrorReason } from '../asset';
|
||||
import { JobName } from '../job';
|
||||
import { IAlbumRepository, IAssetRepository, IJobRepository, IUserRepository } from '../repositories';
|
||||
import { AlbumService } from './album.service';
|
||||
|
||||
describe(AlbumService.name, () => {
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AccessCore, IAccessRepository, Permission } from '../access';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto, IAssetRepository } from '../asset';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from '../asset';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { IUserRepository } from '../user';
|
||||
import { JobName } from '../job';
|
||||
import {
|
||||
AlbumInfoOptions,
|
||||
IAccessRepository,
|
||||
IAlbumRepository,
|
||||
IAssetRepository,
|
||||
IJobRepository,
|
||||
IUserRepository,
|
||||
} from '../repositories';
|
||||
import {
|
||||
AlbumCountResponseDto,
|
||||
AlbumResponseDto,
|
||||
|
@ -12,7 +19,6 @@ import {
|
|||
mapAlbumWithAssets,
|
||||
mapAlbumWithoutAssets,
|
||||
} from './album-response.dto';
|
||||
import { AlbumInfoOptions, IAlbumRepository } from './album.repository';
|
||||
import { AddUsersDto, AlbumInfoDto, CreateAlbumDto, GetAlbumsDto, UpdateAlbumDto } from './dto';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './album-response.dto';
|
||||
export * from './album.repository';
|
||||
export * from './album.service';
|
||||
export * from './dto';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { authStub, keyStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '@test';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
import { ICryptoRepository, IKeyRepository } from '../repositories';
|
||||
import { APIKeyService } from './api-key.service';
|
||||
|
||||
describe(APIKeyService.name, () => {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { APIKeyEntity } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { ICryptoRepository, IKeyRepository } from '../repositories';
|
||||
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto } from './api-key.dto';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyService {
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './api-key.dto';
|
||||
export * from './api-key.repository';
|
||||
export * from './api-key.service';
|
||||
|
|
|
@ -1,21 +1,36 @@
|
|||
import { AssetType } from '@app/infra/entities';
|
||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
IAccessRepositoryMock,
|
||||
assetStub,
|
||||
authStub,
|
||||
faceStub,
|
||||
newAccessRepositoryMock,
|
||||
newAssetRepositoryMock,
|
||||
newCommunicationRepositoryMock,
|
||||
newCryptoRepositoryMock,
|
||||
newJobRepositoryMock,
|
||||
newMoveRepositoryMock,
|
||||
newPersonRepositoryMock,
|
||||
newStorageRepositoryMock,
|
||||
newSystemConfigRepositoryMock,
|
||||
} from '@test';
|
||||
import { when } from 'jest-when';
|
||||
import { Readable } from 'stream';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { IStorageRepository } from '../storage';
|
||||
import { AssetStats, IAssetRepository, TimeBucketSize } from './asset.repository';
|
||||
import { JobName } from '../job';
|
||||
import {
|
||||
AssetStats,
|
||||
IAssetRepository,
|
||||
ICommunicationRepository,
|
||||
ICryptoRepository,
|
||||
IJobRepository,
|
||||
IMoveRepository,
|
||||
IPersonRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
JobItem,
|
||||
TimeBucketSize,
|
||||
} from '../repositories';
|
||||
import { AssetService, UploadFieldName } from './asset.service';
|
||||
import { AssetJobName, AssetStatsResponseDto, DownloadResponseDto } from './dto';
|
||||
import { mapAsset } from './response-dto';
|
||||
|
@ -149,7 +164,11 @@ describe(AssetService.name, () => {
|
|||
let assetMock: jest.Mocked<IAssetRepository>;
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
let jobMock: jest.Mocked<IJobRepository>;
|
||||
let moveMock: jest.Mocked<IMoveRepository>;
|
||||
let personMock: jest.Mocked<IPersonRepository>;
|
||||
let storageMock: jest.Mocked<IStorageRepository>;
|
||||
let communicationMock: jest.Mocked<ICommunicationRepository>;
|
||||
let configMock: jest.Mocked<ISystemConfigRepository>;
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
|
@ -158,10 +177,31 @@ describe(AssetService.name, () => {
|
|||
beforeEach(async () => {
|
||||
accessMock = newAccessRepositoryMock();
|
||||
assetMock = newAssetRepositoryMock();
|
||||
communicationMock = newCommunicationRepositoryMock();
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
jobMock = newJobRepositoryMock();
|
||||
moveMock = newMoveRepositoryMock();
|
||||
personMock = newPersonRepositoryMock();
|
||||
storageMock = newStorageRepositoryMock();
|
||||
sut = new AssetService(accessMock, assetMock, cryptoMock, jobMock, storageMock);
|
||||
configMock = newSystemConfigRepositoryMock();
|
||||
sut = new AssetService(
|
||||
accessMock,
|
||||
assetMock,
|
||||
cryptoMock,
|
||||
jobMock,
|
||||
configMock,
|
||||
moveMock,
|
||||
personMock,
|
||||
storageMock,
|
||||
communicationMock,
|
||||
);
|
||||
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.livePhotoStillAsset.id)
|
||||
.mockResolvedValue(assetStub.livePhotoStillAsset as AssetEntity);
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.livePhotoMotionAsset.id)
|
||||
.mockResolvedValue(assetStub.livePhotoMotionAsset as AssetEntity);
|
||||
});
|
||||
|
||||
describe('canUpload', () => {
|
||||
|
@ -274,60 +314,24 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
describe('getMemoryLane', () => {
|
||||
it('should get pictures for each year', async () => {
|
||||
assetMock.getByDate.mockResolvedValue([]);
|
||||
|
||||
await expect(sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15), years: 10 })).resolves.toEqual(
|
||||
[],
|
||||
);
|
||||
|
||||
expect(assetMock.getByDate).toHaveBeenCalledTimes(10);
|
||||
expect(assetMock.getByDate.mock.calls).toEqual([
|
||||
[authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2020-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2019-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2018-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2017-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2016-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2015-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2014-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2013-06-15T00:00:00.000Z')],
|
||||
]);
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2024-01-15'));
|
||||
});
|
||||
|
||||
it('should keep hours from the date', async () => {
|
||||
assetMock.getByDate.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15, 5), years: 2 }),
|
||||
).resolves.toEqual([]);
|
||||
|
||||
expect(assetMock.getByDate).toHaveBeenCalledTimes(2);
|
||||
expect(assetMock.getByDate.mock.calls).toEqual([
|
||||
[authStub.admin.id, new Date('2022-06-15T05:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2021-06-15T05:00:00.000Z')],
|
||||
]);
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should set the title correctly', async () => {
|
||||
when(assetMock.getByDate)
|
||||
.calledWith(authStub.admin.id, new Date('2022-06-15T00:00:00.000Z'))
|
||||
.mockResolvedValue([assetStub.image]);
|
||||
when(assetMock.getByDate)
|
||||
.calledWith(authStub.admin.id, new Date('2021-06-15T00:00:00.000Z'))
|
||||
.mockResolvedValue([assetStub.video]);
|
||||
assetMock.getByDayOfYear.mockResolvedValue([assetStub.image, assetStub.imageFrom2015]);
|
||||
|
||||
await expect(sut.getMemoryLane(authStub.admin, { timestamp: new Date(2023, 5, 15), years: 2 })).resolves.toEqual([
|
||||
await expect(sut.getMemoryLane(authStub.admin, { day: 15, month: 1 })).resolves.toEqual([
|
||||
{ title: '1 year since...', assets: [mapAsset(assetStub.image)] },
|
||||
{ title: '2 years since...', assets: [mapAsset(assetStub.video)] },
|
||||
{ title: '9 years since...', assets: [mapAsset(assetStub.imageFrom2015)] },
|
||||
]);
|
||||
|
||||
expect(assetMock.getByDate).toHaveBeenCalledTimes(2);
|
||||
expect(assetMock.getByDate.mock.calls).toEqual([
|
||||
[authStub.admin.id, new Date('2022-06-15T00:00:00.000Z')],
|
||||
[authStub.admin.id, new Date('2021-06-15T00:00:00.000Z')],
|
||||
]);
|
||||
expect(assetMock.getByDayOfYear.mock.calls).toEqual([[authStub.admin.id, { day: 15, month: 1 }]]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -512,7 +516,9 @@ describe(AssetService.name, () => {
|
|||
downloadResponse,
|
||||
);
|
||||
|
||||
expect(assetMock.getByUserId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, authStub.admin.id);
|
||||
expect(assetMock.getByUserId).toHaveBeenCalledWith({ take: 2500, skip: 0 }, authStub.admin.id, {
|
||||
isVisible: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should split archives by size', async () => {
|
||||
|
@ -632,6 +638,203 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('deleteAll', () => {
|
||||
it('should required asset delete access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.deleteAll(authStub.user1, {
|
||||
ids: ['asset-1'],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('should force delete a batch of assets', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
|
||||
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: true });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.ASSET_DELETION, data: { id: 'asset1' } }],
|
||||
[{ name: JobName.ASSET_DELETION, data: { id: 'asset2' } }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should soft delete a batch of assets', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
|
||||
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: false });
|
||||
|
||||
expect(assetMock.softDeleteAll).toHaveBeenCalledWith(['asset1', 'asset2']);
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
name: JobName.SEARCH_REMOVE_ASSET,
|
||||
data: { ids: ['asset1', 'asset2'] },
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restoreAll', () => {
|
||||
it('should required asset restore access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.deleteAll(authStub.user1, {
|
||||
ids: ['asset-1'],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('should restore a batch of assets', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
|
||||
await sut.restoreAll(authStub.user1, { ids: ['asset1', 'asset2'] });
|
||||
|
||||
expect(assetMock.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']);
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
name: JobName.SEARCH_INDEX_ASSET,
|
||||
data: { ids: ['asset1', 'asset2'] },
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleAssetDeletion', () => {
|
||||
beforeEach(() => {
|
||||
when(jobMock.queue)
|
||||
.calledWith(
|
||||
expect.objectContaining({
|
||||
name: JobName.ASSET_DELETION,
|
||||
}),
|
||||
)
|
||||
.mockImplementation(async (item: JobItem) => {
|
||||
const jobData = (item as { data?: any })?.data || {};
|
||||
await sut.handleAssetDeletion(jobData);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove faces', async () => {
|
||||
const assetWithFace = { ...(assetStub.image as AssetEntity), faces: [faceStub.face1, faceStub.mergeFace1] };
|
||||
|
||||
when(assetMock.getById).calledWith(assetWithFace.id).mockResolvedValue(assetWithFace);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetWithFace.id });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
name: JobName.SEARCH_REMOVE_FACE,
|
||||
data: { assetId: faceStub.face1.assetId, personId: faceStub.face1.personId },
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: JobName.SEARCH_REMOVE_FACE,
|
||||
data: { assetId: faceStub.mergeFace1.assetId, personId: faceStub.mergeFace1.personId },
|
||||
},
|
||||
],
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [assetWithFace.id] } }],
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [
|
||||
assetWithFace.webpPath,
|
||||
assetWithFace.resizePath,
|
||||
assetWithFace.encodedVideoPath,
|
||||
assetWithFace.sidecarPath,
|
||||
assetWithFace.originalPath,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
expect(assetMock.remove).toHaveBeenCalledWith(assetWithFace);
|
||||
});
|
||||
|
||||
it('should not schedule delete-files job for readonly assets', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.readOnly.id)
|
||||
.mockResolvedValue(assetStub.readOnly as AssetEntity);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.readOnly.id });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [assetStub.readOnly.id] } }],
|
||||
]);
|
||||
|
||||
expect(assetMock.remove).toHaveBeenCalledWith(assetStub.readOnly);
|
||||
});
|
||||
|
||||
it('should not process assets from external library without fromExternal flag', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.external.id)
|
||||
.mockResolvedValue(assetStub.external as AssetEntity);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id });
|
||||
|
||||
expect(jobMock.queue).not.toBeCalled();
|
||||
expect(assetMock.remove).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should process assets from external library with fromExternal flag', async () => {
|
||||
when(assetMock.getById)
|
||||
.calledWith(assetStub.external.id)
|
||||
.mockResolvedValue(assetStub.external as AssetEntity);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true });
|
||||
|
||||
expect(assetMock.remove).toHaveBeenCalledWith(assetStub.external);
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [assetStub.external.id] } }],
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [
|
||||
assetStub.external.webpPath,
|
||||
assetStub.external.resizePath,
|
||||
assetStub.external.encodedVideoPath,
|
||||
assetStub.external.sidecarPath,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should delete a live photo', async () => {
|
||||
await sut.handleAssetDeletion({ id: assetStub.livePhotoStillAsset.id });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [assetStub.livePhotoStillAsset.id] } }],
|
||||
[{ name: JobName.ASSET_DELETION, data: { id: assetStub.livePhotoMotionAsset.id } }],
|
||||
[{ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [assetStub.livePhotoMotionAsset.id] } }],
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.mp4'],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [undefined, undefined, undefined, undefined, 'fake_path/asset_1.jpeg'],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', () => {
|
||||
it('should run the refresh metadata job', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
import { AssetEntity } from '@app/infra/entities';
|
||||
import { AssetEntity, LibraryType } from '@app/infra/entities';
|
||||
import { BadRequestException, Inject, Logger } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import _ from 'lodash';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import { extname } from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { AccessCore, IAccessRepository, Permission } from '../access';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { ICryptoRepository } from '../crypto';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { HumanReadableSize, usePagination } from '../domain.util';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { IStorageRepository, ImmichReadStream, StorageCore, StorageFolder } from '../storage';
|
||||
import { IAssetRepository } from './asset.repository';
|
||||
import { IAssetDeletionJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||
import {
|
||||
CommunicationEvent,
|
||||
IAccessRepository,
|
||||
IAssetRepository,
|
||||
ICommunicationRepository,
|
||||
ICryptoRepository,
|
||||
IJobRepository,
|
||||
IMoveRepository,
|
||||
IPersonRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
ImmichReadStream,
|
||||
} from '../repositories';
|
||||
import { StorageCore, StorageFolder } from '../storage';
|
||||
import { SystemConfigCore } from '../system-config';
|
||||
import {
|
||||
AssetBulkDeleteDto,
|
||||
AssetBulkUpdateDto,
|
||||
AssetIdsDto,
|
||||
AssetJobName,
|
||||
|
@ -24,11 +38,13 @@ import {
|
|||
MemoryLaneDto,
|
||||
TimeBucketAssetDto,
|
||||
TimeBucketDto,
|
||||
TrashAction,
|
||||
UpdateAssetDto,
|
||||
mapStats,
|
||||
} from './dto';
|
||||
import {
|
||||
AssetResponseDto,
|
||||
BulkIdsDto,
|
||||
MapMarkerResponseDto,
|
||||
MemoryLaneResponseDto,
|
||||
TimeBucketResponseDto,
|
||||
|
@ -57,6 +73,7 @@ export interface UploadFile {
|
|||
export class AssetService {
|
||||
private logger = new Logger(AssetService.name);
|
||||
private access: AccessCore;
|
||||
private configCore: SystemConfigCore;
|
||||
private storageCore: StorageCore;
|
||||
|
||||
constructor(
|
||||
|
@ -64,10 +81,15 @@ export class AssetService {
|
|||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
||||
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
||||
@Inject(IPersonRepository) personRepository: IPersonRepository,
|
||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
||||
) {
|
||||
this.access = new AccessCore(accessRepository);
|
||||
this.storageCore = new StorageCore(storageRepository);
|
||||
this.configCore = SystemConfigCore.create(configRepository);
|
||||
this.storageCore = new StorageCore(storageRepository, assetRepository, moveRepository, personRepository);
|
||||
}
|
||||
|
||||
canUploadFile({ authUser, fieldName, file }: UploadRequest): true {
|
||||
|
@ -138,34 +160,36 @@ export class AssetService {
|
|||
}
|
||||
|
||||
async getMemoryLane(authUser: AuthUserDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||
const target = DateTime.fromJSDate(dto.timestamp);
|
||||
const currentYear = new Date().getFullYear();
|
||||
const assets = await this.assetRepository.getByDayOfYear(authUser.id, dto);
|
||||
|
||||
const onRequest = async (yearsAgo: number): Promise<MemoryLaneResponseDto> => {
|
||||
const assets = await this.assetRepository.getByDate(authUser.id, target.minus({ years: yearsAgo }).toJSDate());
|
||||
return {
|
||||
title: `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} since...`,
|
||||
assets: assets.map((a) => mapAsset(a)),
|
||||
};
|
||||
};
|
||||
return _.chain(assets)
|
||||
.filter((asset) => asset.localDateTime.getFullYear() < currentYear)
|
||||
.map((asset) => {
|
||||
const years = currentYear - asset.localDateTime.getFullYear();
|
||||
|
||||
const requests: Promise<MemoryLaneResponseDto>[] = [];
|
||||
for (let i = 1; i <= dto.years; i++) {
|
||||
requests.push(onRequest(i));
|
||||
}
|
||||
|
||||
return Promise.all(requests).then((results) => results.filter((result) => result.assets.length > 0));
|
||||
return {
|
||||
title: `${years} year${years > 1 ? 's' : ''} since...`,
|
||||
asset: mapAsset(asset),
|
||||
};
|
||||
})
|
||||
.groupBy((asset) => asset.title)
|
||||
.map((items, title) => ({ title, assets: items.map(({ asset }) => asset) }))
|
||||
.value();
|
||||
}
|
||||
|
||||
private async timeBucketChecks(authUser: AuthUserDto, dto: TimeBucketDto) {
|
||||
if (dto.albumId) {
|
||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, [dto.albumId]);
|
||||
} else if (dto.userId) {
|
||||
} else {
|
||||
dto.userId = dto.userId || authUser.id;
|
||||
}
|
||||
|
||||
if (dto.userId) {
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, [dto.userId]);
|
||||
if (dto.isArchived !== false) {
|
||||
await this.access.requirePermission(authUser, Permission.ARCHIVE_READ, [dto.userId]);
|
||||
}
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_READ, [dto.userId]);
|
||||
} else {
|
||||
dto.userId = authUser.id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +298,9 @@ export class AssetService {
|
|||
if (dto.userId) {
|
||||
const userId = dto.userId;
|
||||
await this.access.requirePermission(authUser, Permission.TIMELINE_DOWNLOAD, userId);
|
||||
return usePagination(PAGINATION_SIZE, (pagination) => this.assetRepository.getByUserId(pagination, userId));
|
||||
return usePagination(PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getByUserId(pagination, userId, { isVisible: true }),
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequestException('assetIds, albumId, or userId is required');
|
||||
|
@ -303,13 +329,121 @@ export class AssetService {
|
|||
return mapAsset(asset);
|
||||
}
|
||||
|
||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto) {
|
||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||
const { ids, ...options } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
||||
await this.assetRepository.updateAll(ids, options);
|
||||
}
|
||||
|
||||
async handleAssetDeletionCheck() {
|
||||
const config = await this.configCore.getConfig();
|
||||
const trashedDays = config.trash.enabled ? config.trash.days : 0;
|
||||
const trashedBefore = DateTime.now()
|
||||
.minus(Duration.fromObject({ days: trashedDays }))
|
||||
.toJSDate();
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getAll(pagination, { trashedBefore }),
|
||||
);
|
||||
|
||||
for await (const assets of assetPagination) {
|
||||
for (const asset of assets) {
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.id } });
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async handleAssetDeletion(job: IAssetDeletionJob) {
|
||||
const { id, fromExternal } = job;
|
||||
|
||||
const asset = await this.assetRepository.getById(id);
|
||||
if (!asset) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore requests that are not from external library job but is for an external asset
|
||||
if (!fromExternal && (!asset.library || asset.library.type === LibraryType.EXTERNAL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (asset.faces) {
|
||||
await Promise.all(
|
||||
asset.faces.map(({ assetId, personId }) =>
|
||||
this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_FACE, data: { assetId, personId } }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await this.assetRepository.remove(asset);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { ids: [asset.id] } });
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_DELETE, asset.ownerId, id);
|
||||
|
||||
// TODO refactor this to use cascades
|
||||
if (asset.livePhotoVideoId) {
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } });
|
||||
}
|
||||
|
||||
const files = [asset.webpPath, asset.resizePath, asset.encodedVideoPath, asset.sidecarPath];
|
||||
if (!fromExternal) {
|
||||
files.push(asset.originalPath);
|
||||
}
|
||||
|
||||
if (!asset.isReadOnly) {
|
||||
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files } });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteAll(authUser: AuthUserDto, dto: AssetBulkDeleteDto): Promise<void> {
|
||||
const { ids, force } = dto;
|
||||
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_DELETE, ids);
|
||||
|
||||
if (force) {
|
||||
for (const id of ids) {
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id } });
|
||||
}
|
||||
} else {
|
||||
await this.assetRepository.softDeleteAll(ids);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_ASSET, data: { ids } });
|
||||
this.communicationRepository.send(CommunicationEvent.ASSET_TRASH, authUser.id, ids);
|
||||
}
|
||||
}
|
||||
|
||||
async handleTrashAction(authUser: AuthUserDto, action: TrashAction): Promise<void> {
|
||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||
this.assetRepository.getByUserId(pagination, authUser.id, { trashedBefore: DateTime.now().toJSDate() }),
|
||||
);
|
||||
|
||||
if (action == TrashAction.RESTORE_ALL) {
|
||||
for await (const assets of assetPagination) {
|
||||
const ids = assets.map((a) => a.id);
|
||||
await this.assetRepository.restoreAll(ids);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == TrashAction.EMPTY_ALL) {
|
||||
for await (const assets of assetPagination) {
|
||||
for (const asset of assets) {
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.id } });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async restoreAll(authUser: AuthUserDto, dto: BulkIdsDto): Promise<void> {
|
||||
const { ids } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_RESTORE, ids);
|
||||
await this.assetRepository.restoreAll(ids);
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
||||
}
|
||||
|
||||
async run(authUser: AuthUserDto, dto: AssetJobsDto) {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, dto.assetIds);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean } from 'class-validator';
|
||||
import { Optional, toBoolean } from '../../domain.util';
|
||||
import { AssetStats } from '../asset.repository';
|
||||
import { AssetStats } from '../../repositories';
|
||||
|
||||
export class AssetStatsDto {
|
||||
@IsBoolean()
|
||||
|
@ -15,6 +15,11 @@ export class AssetStatsDto {
|
|||
@Transform(toBoolean)
|
||||
@Optional()
|
||||
isFavorite?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
@Optional()
|
||||
isTrashed?: boolean;
|
||||
}
|
||||
|
||||
export class AssetStatsResponseDto {
|
||||
|
|
|
@ -34,3 +34,14 @@ export class RandomAssetsDto {
|
|||
@Type(() => Number)
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export enum TrashAction {
|
||||
EMPTY_ALL = 'empty-all',
|
||||
RESTORE_ALL = 'restore-all',
|
||||
}
|
||||
|
||||
export class AssetBulkDeleteDto extends BulkIdsDto {
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
force?: boolean;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDate, IsNumber, IsPositive } from 'class-validator';
|
||||
import { IsInt, Max, Min } from 'class-validator';
|
||||
|
||||
export class MemoryLaneDto {
|
||||
/** Get pictures for +24 hours from this time going back x years */
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
timestamp!: Date;
|
||||
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
years = 30;
|
||||
@Max(31)
|
||||
@Min(1)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
day!: number;
|
||||
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
@Max(12)
|
||||
@Min(1)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
month!: number;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional, ValidateUUID, toBoolean } from '../../domain.util';
|
||||
import { TimeBucketSize } from '../asset.repository';
|
||||
import { TimeBucketSize } from '../../repositories';
|
||||
|
||||
export class TimeBucketDto {
|
||||
@IsNotEmpty()
|
||||
|
@ -28,6 +28,11 @@ export class TimeBucketDto {
|
|||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
isFavorite?: boolean;
|
||||
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
isTrashed?: boolean;
|
||||
}
|
||||
|
||||
export class TimeBucketAssetDto extends TimeBucketDto {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './asset.repository';
|
||||
export * from './asset.service';
|
||||
export * from './dto';
|
||||
export * from './response-dto';
|
||||
|
|
|
@ -26,6 +26,8 @@ export class AssetResponseDto {
|
|||
updatedAt!: Date;
|
||||
isFavorite!: boolean;
|
||||
isArchived!: boolean;
|
||||
isTrashed!: boolean;
|
||||
localDateTime!: Date;
|
||||
isOffline!: boolean;
|
||||
isExternal!: boolean;
|
||||
isReadOnly!: boolean;
|
||||
|
@ -54,9 +56,11 @@ function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto {
|
|||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
localDateTime: entity.localDateTime,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: entity.isFavorite,
|
||||
isArchived: entity.isArchived,
|
||||
isTrashed: !!entity.deletedAt,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
exifInfo: withExif ? (entity.exifInfo ? mapExif(entity.exifInfo) : undefined) : undefined,
|
||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { DatabaseAction, EntityType } from '@app/infra/entities';
|
||||
import { IAccessRepositoryMock, auditStub, authStub, newAccessRepositoryMock, newAuditRepositoryMock } from '@test';
|
||||
import { IAuditRepository } from './audit.repository';
|
||||
import { IAuditRepository } from '../repositories';
|
||||
import { AuditService } from './audit.service';
|
||||
|
||||
describe(AuditService.name, () => {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { DatabaseAction } from '@app/infra/entities';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { AccessCore, IAccessRepository, Permission } from '../access';
|
||||
import { AccessCore, Permission } from '../access';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { AUDIT_LOG_MAX_DURATION } from '../domain.constant';
|
||||
import { IAccessRepository, IAuditRepository } from '../repositories';
|
||||
import { AuditDeletesDto, AuditDeletesResponseDto } from './audit.dto';
|
||||
import { IAuditRepository } from './audit.repository';
|
||||
|
||||
@Injectable()
|
||||
export class AuditService {
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './audit.dto';
|
||||
export * from './audit.repository';
|
||||
export * from './audit.service';
|
||||
|
|
|
@ -19,16 +19,18 @@ import {
|
|||
import { IncomingHttpHeaders } from 'http';
|
||||
import { Issuer, generators } from 'openid-client';
|
||||
import { Socket } from 'socket.io';
|
||||
import { IKeyRepository } from '../api-key';
|
||||
import { ICryptoRepository } from '../crypto/crypto.repository';
|
||||
import { ILibraryRepository } from '../library';
|
||||
import { ISharedLinkRepository } from '../shared-link';
|
||||
import { ISystemConfigRepository } from '../system-config';
|
||||
import { IUserRepository } from '../user';
|
||||
import {
|
||||
ICryptoRepository,
|
||||
IKeyRepository,
|
||||
ILibraryRepository,
|
||||
ISharedLinkRepository,
|
||||
ISystemConfigRepository,
|
||||
IUserRepository,
|
||||
IUserTokenRepository,
|
||||
} from '../repositories';
|
||||
import { AuthType } from './auth.constant';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthUserDto, SignUpDto } from './dto';
|
||||
import { IUserTokenRepository } from './user-token.repository';
|
||||
|
||||
// const token = Buffer.from('my-api-key', 'utf8').toString('base64');
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue