Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
608543da0b | ||
|
|
b4fa60d4fd | ||
|
|
b1467bd1da | ||
|
|
3cf0f5f11b | ||
|
|
454737ca79 | ||
|
|
26bc889f8d | ||
|
|
54775b896f | ||
|
|
9217fb4094 | ||
|
|
04d4a30471 | ||
|
|
90f9501902 | ||
|
|
f8d26bd865 | ||
|
|
816d040d81 | ||
|
|
2069293cc1 | ||
|
|
4bd77d5899 | ||
|
|
f8ff342852 | ||
|
|
67ac686704 | ||
|
|
4e5bf7ae2e | ||
|
|
b7fd5dcb4a | ||
|
|
bea287c5b3 | ||
|
|
46c716d450 | ||
|
|
9539a361e4 | ||
|
|
ca35e5557b | ||
|
|
a26ed3d1a6 | ||
|
|
c7d53a5006 | ||
|
|
41461e0d5d | ||
|
|
c0a48d7357 | ||
|
|
66cc744c22 | ||
|
|
58ae734fc2 | ||
|
|
54b2779b79 | ||
|
|
df26e12db6 | ||
|
|
343d89c032 |
2
.github/workflows/build-mobile.yml
vendored
2
.github/workflows/build-mobile.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
ref="${input_ref:-$github_ref}"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.get-ref.outputs.ref }}
|
||||
|
||||
|
||||
2
.github/workflows/cache-cleanup.yml
vendored
2
.github/workflows/cache-cleanup.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
platforms: "linux/arm64,linux/amd64"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.2.0
|
||||
|
||||
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
||||
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@v2
|
||||
|
||||
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run npm install
|
||||
run: npm ci
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
name: Run mobile unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Flutter SDK
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
run:
|
||||
working-directory: ./machine-learning
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry
|
||||
run: pipx install poetry
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
name: Check generated files are up-to-date
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run API generation
|
||||
run: npm --prefix server run api:generate
|
||||
- name: Find file changes
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install server dependencies
|
||||
run: npm --prefix server ci
|
||||
- name: Run existing migrations
|
||||
@@ -249,7 +249,7 @@ jobs:
|
||||
# name: Run mobile end-to-end integration tests
|
||||
# runs-on: macos-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/setup-java@v3
|
||||
# with:
|
||||
# distribution: 'zulu'
|
||||
|
||||
209
cli/src/api/open-api/api.ts
generated
209
cli/src/api/open-api/api.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.76.0
|
||||
* The version of the OpenAPI document: 1.77.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
@@ -645,6 +645,12 @@ export interface AssetResponseDto {
|
||||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'originalPath': string;
|
||||
/**
|
||||
*
|
||||
* @type {UserResponseDto}
|
||||
* @memberof AssetResponseDto
|
||||
*/
|
||||
'owner'?: UserResponseDto;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@@ -909,6 +915,21 @@ export const CLIPMode = {
|
||||
export type CLIPMode = typeof CLIPMode[keyof typeof CLIPMode];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @enum {string}
|
||||
*/
|
||||
|
||||
export const CQMode = {
|
||||
Auto: 'auto',
|
||||
Cqp: 'cqp',
|
||||
Icq: 'icq'
|
||||
} as const;
|
||||
|
||||
export type CQMode = typeof CQMode[keyof typeof CQMode];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -1031,6 +1052,20 @@ export interface ClassificationConfig {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @enum {string}
|
||||
*/
|
||||
|
||||
export const Colorspace = {
|
||||
Srgb: 'srgb',
|
||||
P3: 'p3'
|
||||
} as const;
|
||||
|
||||
export type Colorspace = typeof Colorspace[keyof typeof Colorspace];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -1861,6 +1896,19 @@ export const ModelType = {
|
||||
export type ModelType = typeof ModelType[keyof typeof ModelType];
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface OAuthAuthorizeResponseDto
|
||||
*/
|
||||
export interface OAuthAuthorizeResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof OAuthAuthorizeResponseDto
|
||||
*/
|
||||
'url': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -1969,7 +2017,7 @@ export interface PeopleUpdateDto {
|
||||
*/
|
||||
export interface PeopleUpdateItem {
|
||||
/**
|
||||
* Person date of birth.
|
||||
* Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
* @type {string}
|
||||
* @memberof PeopleUpdateItem
|
||||
*/
|
||||
@@ -2043,7 +2091,7 @@ export interface PersonResponseDto {
|
||||
*/
|
||||
export interface PersonUpdateDto {
|
||||
/**
|
||||
* Person date of birth.
|
||||
* Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
* @type {string}
|
||||
* @memberof PersonUpdateDto
|
||||
*/
|
||||
@@ -2799,24 +2847,54 @@ export interface SystemConfigFFmpegDto {
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'accel': TranscodeHWAccel;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'bframes': number;
|
||||
/**
|
||||
*
|
||||
* @type {CQMode}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'cqMode': CQMode;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'crf': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'gopSize': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'maxBitrate': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'npl': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'preset': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'refs': number;
|
||||
/**
|
||||
*
|
||||
* @type {AudioCodec}
|
||||
@@ -2835,6 +2913,12 @@ export interface SystemConfigFFmpegDto {
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'targetVideoCodec': VideoCodec;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof SystemConfigFFmpegDto
|
||||
*/
|
||||
'temporalAQ': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
@@ -3120,12 +3204,24 @@ export interface SystemConfigTemplateStorageOptionDto {
|
||||
* @interface SystemConfigThumbnailDto
|
||||
*/
|
||||
export interface SystemConfigThumbnailDto {
|
||||
/**
|
||||
*
|
||||
* @type {Colorspace}
|
||||
* @memberof SystemConfigThumbnailDto
|
||||
*/
|
||||
'colorspace': Colorspace;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigThumbnailDto
|
||||
*/
|
||||
'jpegSize': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof SystemConfigThumbnailDto
|
||||
*/
|
||||
'quality': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
@@ -3133,6 +3229,8 @@ export interface SystemConfigThumbnailDto {
|
||||
*/
|
||||
'webpSize': number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -3325,12 +3423,6 @@ export interface UpdateAssetDto {
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'isFavorite'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'tagIds'?: Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -6207,7 +6299,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Update an asset
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {UpdateAssetDto} updateAssetDto
|
||||
* @param {*} [options] Override http request option.
|
||||
@@ -6686,7 +6778,7 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* Update an asset
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {UpdateAssetDto} updateAssetDto
|
||||
* @param {*} [options] Override http request option.
|
||||
@@ -6943,7 +7035,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||
return localVarFp.serveFile(requestParameters.id, requestParameters.isThumb, requestParameters.isWeb, requestParameters.key, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Update an asset
|
||||
*
|
||||
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
@@ -7860,7 +7952,7 @@ export class AssetApi extends BaseAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an asset
|
||||
*
|
||||
* @param {AssetApiUpdateAssetRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
@@ -8890,6 +8982,41 @@ export class JobApi extends BaseAPI {
|
||||
*/
|
||||
export const OAuthApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {OAuthConfigDto} oAuthConfigDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
authorizeOAuth: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'oAuthConfigDto' is not null or undefined
|
||||
assertParamExists('authorizeOAuth', 'oAuthConfigDto', oAuthConfigDto)
|
||||
const localVarPath = `/oauth/authorize`;
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(oAuthConfigDto, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {OAuthCallbackDto} oAuthCallbackDto
|
||||
@@ -8926,9 +9053,10 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @deprecated use feature flags and /oauth/authorize
|
||||
* @param {OAuthConfigDto} oAuthConfigDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @deprecated
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
generateConfig: async (oAuthConfigDto: OAuthConfigDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
@@ -9081,6 +9209,16 @@ export const OAuthApiAxiosParamCreator = function (configuration?: Configuration
|
||||
export const OAuthApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = OAuthApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {OAuthConfigDto} oAuthConfigDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async authorizeOAuth(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthAuthorizeResponseDto>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.authorizeOAuth(oAuthConfigDto, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {OAuthCallbackDto} oAuthCallbackDto
|
||||
@@ -9092,9 +9230,10 @@ export const OAuthApiFp = function(configuration?: Configuration) {
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @deprecated use feature flags and /oauth/authorize
|
||||
* @param {OAuthConfigDto} oAuthConfigDto
|
||||
* @param {*} [options] Override http request option.
|
||||
* @deprecated
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async generateConfig(oAuthConfigDto: OAuthConfigDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<OAuthConfigResponseDto>> {
|
||||
@@ -9139,6 +9278,15 @@ export const OAuthApiFp = function(configuration?: Configuration) {
|
||||
export const OAuthApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = OAuthApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthAuthorizeResponseDto> {
|
||||
return localVarFp.authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
||||
@@ -9149,9 +9297,10 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath
|
||||
return localVarFp.callback(requestParameters.oAuthCallbackDto, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @deprecated use feature flags and /oauth/authorize
|
||||
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @deprecated
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
generateConfig(requestParameters: OAuthApiGenerateConfigRequest, options?: AxiosRequestConfig): AxiosPromise<OAuthConfigResponseDto> {
|
||||
@@ -9185,6 +9334,20 @@ export const OAuthApiFactory = function (configuration?: Configuration, basePath
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Request parameters for authorizeOAuth operation in OAuthApi.
|
||||
* @export
|
||||
* @interface OAuthApiAuthorizeOAuthRequest
|
||||
*/
|
||||
export interface OAuthApiAuthorizeOAuthRequest {
|
||||
/**
|
||||
*
|
||||
* @type {OAuthConfigDto}
|
||||
* @memberof OAuthApiAuthorizeOAuth
|
||||
*/
|
||||
readonly oAuthConfigDto: OAuthConfigDto
|
||||
}
|
||||
|
||||
/**
|
||||
* Request parameters for callback operation in OAuthApi.
|
||||
* @export
|
||||
@@ -9234,6 +9397,17 @@ export interface OAuthApiLinkRequest {
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class OAuthApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @param {OAuthApiAuthorizeOAuthRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof OAuthApi
|
||||
*/
|
||||
public authorizeOAuth(requestParameters: OAuthApiAuthorizeOAuthRequest, options?: AxiosRequestConfig) {
|
||||
return OAuthApiFp(this.configuration).authorizeOAuth(requestParameters.oAuthConfigDto, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {OAuthApiCallbackRequest} requestParameters Request parameters.
|
||||
@@ -9246,9 +9420,10 @@ export class OAuthApi extends BaseAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated use feature flags and /oauth/authorize
|
||||
* @param {OAuthApiGenerateConfigRequest} requestParameters Request parameters.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @deprecated
|
||||
* @throws {RequiredError}
|
||||
* @memberof OAuthApi
|
||||
*/
|
||||
|
||||
2
cli/src/api/open-api/base.ts
generated
2
cli/src/api/open-api/base.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.76.0
|
||||
* The version of the OpenAPI document: 1.77.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
cli/src/api/open-api/common.ts
generated
2
cli/src/api/open-api/common.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.76.0
|
||||
* The version of the OpenAPI document: 1.77.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
cli/src/api/open-api/configuration.ts
generated
2
cli/src/api/open-api/configuration.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.76.0
|
||||
* The version of the OpenAPI document: 1.77.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
2
cli/src/api/open-api/index.ts
generated
2
cli/src/api/open-api/index.ts
generated
@@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.76.0
|
||||
* The version of the OpenAPI document: 1.77.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
ports:
|
||||
- 3003:3003
|
||||
volumes:
|
||||
- ../machine-learning/app:/usr/src/app
|
||||
- ../machine-learning:/usr/src/app
|
||||
- model-cache:/cache
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
@@ -68,16 +68,16 @@ Be aware that as this runs inside a container, you need to mount the folder from
|
||||
|
||||
```bash title="Upload current directory"
|
||||
cd /DIRECTORY/WITH/IMAGES
|
||||
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
```
|
||||
|
||||
```bash title="Upload target directory"
|
||||
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
docker run -it --rm -v "/DIRECTORY/WITH/IMAGES:/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
```
|
||||
|
||||
```bash title="Create an alias"
|
||||
alias immich='docker run -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest'
|
||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
immich upload --recursive --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api
|
||||
```
|
||||
|
||||
:::tip Internal networking
|
||||
@@ -88,7 +88,7 @@ If you are running the CLI container on the same machine as your Immich server,
|
||||
3. Use `--server http://immich-server:3001` for the upload command instead of the external address.
|
||||
|
||||
```bash title="Upload to internal address"
|
||||
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://immich-server:3001
|
||||
docker run --network immich_default -it --rm -v "$(pwd):/import" ghcr.io/immich-app/immich-cli:latest upload --recursive --key HFEJ38DNSDUEG --server http://immich-server:3001
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -46,23 +46,22 @@ These environment variables are used by the `docker-compose.yml` file and do **N
|
||||
|
||||
## Ports
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :---------------------- | :-------------------- | :-----: | :--------------- |
|
||||
| `PORT` | Web Port | `3000` | web |
|
||||
| `SERVER_PORT` | Server Port | `3001` | server |
|
||||
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
||||
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
||||
| Variable | Description | Default | Services |
|
||||
| :---------------------- | :-------------------- | :-------: | :--------------- |
|
||||
| `PORT` | Web Port | `3000` | web |
|
||||
| `SERVER_PORT` | Server Port | `3001` | server |
|
||||
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
|
||||
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
|
||||
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
|
||||
|
||||
## URLs
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :-------------------------------- | :--------------------------- | :-----------------------------------: | :-------------------- |
|
||||
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
||||
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
||||
| `IMMICH_MACHINE_LEARNING_ENABLED` | Enabled machine learning | `true` | server, microservices |
|
||||
| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, | `http://immich-machine-learning:3003` | server, microservices |
|
||||
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
||||
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
||||
| Variable | Description | Default | Services |
|
||||
| :------------------------- | :---------------------- | :-------------------------: | :--------- |
|
||||
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
|
||||
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
|
||||
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
|
||||
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
|
||||
|
||||
:::info
|
||||
|
||||
@@ -178,18 +177,21 @@ Typesense URL example JSON before encoding:
|
||||
|
||||
## Machine Learning
|
||||
|
||||
| Variable | Description | Default | Services |
|
||||
| :------------------------------------------ | :----------------------------- | :-------------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MIN_FACE_SCORE` | Minimum Face Score | `0.7` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_TTL` | Model TTL | `300` | machine learning |
|
||||
| `MACHINE_LEARNING_EAGER_STARTUP` | Eager Startup | `true` | machine learning |
|
||||
| `MACHINE_LEARNING_MIN_TAG_SCORE` | Minimum Tag Score | `0.9` | machine learning |
|
||||
| `MACHINE_LEARNING_FACIAL_RECOGNITION_MODEL` | Facial Recognition Model | `buffalo_l` | machine learning |
|
||||
| `MACHINE_LEARNING_CLIP_TEXT_MODEL` | Clip Text Model | `clip-ViT-B-32` | machine learning |
|
||||
| `MACHINE_LEARNING_CLIP_IMAGE_MODEL` | Clip Image Model | `clip-ViT-B-32` | machine learning |
|
||||
| `MACHINE_LEARNING_CLASSIFICATION_MODEL` | Classification Model | `microsoft/resnet-50` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | ML Cache Location | `/cache` | machine learning |
|
||||
| `TRANSFORMERS_CACHE` | ML Transformers Cache Location | `/cache` | machine learning |
|
||||
| Variable | Description | Default | Services |
|
||||
| :----------------------------------------------- | :----------------------------------------- | :-----------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MODEL_TTL` | Model TTL | `300` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | ML Cache Location | `/cache` | machine learning |
|
||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Request thread pool size | number of CPU cores | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||
|
||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||
|
||||
:::info
|
||||
|
||||
Other machine learning parameters can be tuned from the admin UI.
|
||||
|
||||
:::
|
||||
|
||||
## Docker Secrets
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11.4-bullseye@sha256:5b401676aff858495a5c9c726c60b8b73fe52833e9e16eccdb59e93d52741727 as builder
|
||||
FROM python:3.11-bookworm as builder
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
@@ -14,9 +14,9 @@ COPY poetry.lock pyproject.toml requirements.txt ./
|
||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
||||
RUN pip install --no-deps -r requirements.txt
|
||||
|
||||
FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd
|
||||
FROM python:3.11-slim-bookworm
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
@@ -27,6 +27,7 @@ ENV NODE_ENV=production \
|
||||
PYTHONPATH=/usr/src
|
||||
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
COPY start.sh log_conf.json ./
|
||||
COPY app .
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["python", "-m", "app.main"]
|
||||
CMD ["./start.sh"]
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import gunicorn
|
||||
import starlette
|
||||
from pydantic import BaseSettings
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from .schemas import ModelType
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
cache_folder: str = "/cache"
|
||||
eager_startup: bool = False
|
||||
model_ttl: int = 0
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 3003
|
||||
@@ -23,6 +27,14 @@ class Settings(BaseSettings):
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
log_level: str = "info"
|
||||
no_color: bool = False
|
||||
|
||||
class Config:
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
_clean_name = str.maketrans(":\\/", "___", ".")
|
||||
|
||||
|
||||
@@ -30,4 +42,28 @@ def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
|
||||
return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name)
|
||||
|
||||
|
||||
LOG_LEVELS: dict[str, int] = {
|
||||
"critical": logging.ERROR,
|
||||
"error": logging.ERROR,
|
||||
"warning": logging.WARNING,
|
||||
"warn": logging.WARNING,
|
||||
"info": logging.INFO,
|
||||
"log": logging.INFO,
|
||||
"debug": logging.DEBUG,
|
||||
"verbose": logging.DEBUG,
|
||||
}
|
||||
|
||||
settings = Settings()
|
||||
log_settings = LogSettings()
|
||||
|
||||
|
||||
class CustomRichHandler(RichHandler):
|
||||
def __init__(self) -> None:
|
||||
console = Console(color_system="standard", no_color=log_settings.no_color)
|
||||
super().__init__(
|
||||
show_path=False, omit_repeated_times=False, console=console, tracebacks_suppress=[gunicorn, starlette]
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger("gunicorn.access")
|
||||
log.setLevel(LOG_LEVELS.get(log_settings.log_level.lower(), logging.INFO))
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import asyncio
|
||||
import os
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Any
|
||||
|
||||
import orjson
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from starlette.formparsers import MultiPartParser
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
|
||||
from .config import settings
|
||||
from .config import log, settings
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import (
|
||||
MessageResponse,
|
||||
@@ -20,14 +18,20 @@ from .schemas import (
|
||||
)
|
||||
|
||||
MultiPartParser.max_file_size = 2**24 # spools to disk if payload is 16 MiB or larger
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def init_state() -> None:
|
||||
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
|
||||
log.info(
|
||||
(
|
||||
"Created in-memory cache with unloading "
|
||||
f"{f'after {settings.model_ttl}s of inactivity' if settings.model_ttl > 0 else 'disabled'}."
|
||||
)
|
||||
)
|
||||
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
|
||||
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads)
|
||||
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
|
||||
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
@@ -66,15 +70,7 @@ async def predict(
|
||||
|
||||
|
||||
async def run(model: InferenceModel, inputs: Any) -> Any:
|
||||
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
is_dev = os.getenv("NODE_ENV") == "development"
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
reload=is_dev,
|
||||
workers=settings.workers,
|
||||
)
|
||||
if app.state.thread_pool is not None:
|
||||
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
|
||||
else:
|
||||
return model.predict(inputs)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pickle
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
@@ -9,9 +8,9 @@ from typing import Any
|
||||
from zipfile import BadZipFile
|
||||
|
||||
import onnxruntime as ort
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile # type: ignore
|
||||
|
||||
from ..config import get_cache_dir, settings
|
||||
from ..config import get_cache_dir, log, settings
|
||||
from ..schemas import ModelType
|
||||
|
||||
|
||||
@@ -37,22 +36,42 @@ class InferenceModel(ABC):
|
||||
self.provider_options = model_kwargs.pop(
|
||||
"provider_options", [{"arena_extend_strategy": "kSameAsRequested"}] * len(self.providers)
|
||||
)
|
||||
log.debug(
|
||||
(
|
||||
f"Setting '{self.model_name}' execution providers to {self.providers}"
|
||||
"in descending order of preference"
|
||||
),
|
||||
)
|
||||
log.debug(f"Setting execution provider options to {self.provider_options}")
|
||||
self.sess_options = PicklableSessionOptions()
|
||||
# avoid thread contention between models
|
||||
if inter_op_num_threads > 1:
|
||||
self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
|
||||
|
||||
log.debug(f"Setting execution_mode to {self.sess_options.execution_mode.name}")
|
||||
log.debug(f"Setting inter_op_num_threads to {inter_op_num_threads}")
|
||||
log.debug(f"Setting intra_op_num_threads to {intra_op_num_threads}")
|
||||
self.sess_options.inter_op_num_threads = inter_op_num_threads
|
||||
self.sess_options.intra_op_num_threads = intra_op_num_threads
|
||||
self.sess_options.enable_cpu_mem_arena = False
|
||||
|
||||
try:
|
||||
loader(**model_kwargs)
|
||||
except (OSError, InvalidProtobuf, BadZipFile):
|
||||
except (OSError, InvalidProtobuf, BadZipFile, NoSuchFile):
|
||||
log.warn(
|
||||
(
|
||||
f"Failed to load {self.model_type.replace('_', ' ')} model '{self.model_name}'."
|
||||
"Clearing cache and retrying."
|
||||
)
|
||||
)
|
||||
self.clear_cache()
|
||||
loader(**model_kwargs)
|
||||
|
||||
def download(self, **model_kwargs: Any) -> None:
|
||||
if not self.cached:
|
||||
print(f"Downloading {self.model_type.value.replace('_', ' ')} model. This may take a while...")
|
||||
log.info(
|
||||
(f"Downloading {self.model_type.replace('_', ' ')} model '{self.model_name}'." "This may take a while.")
|
||||
)
|
||||
self._download(**model_kwargs)
|
||||
|
||||
def load(self, **model_kwargs: Any) -> None:
|
||||
@@ -62,7 +81,7 @@ class InferenceModel(ABC):
|
||||
|
||||
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
||||
if not self._loaded:
|
||||
print(f"Loading {self.model_type.value.replace('_', ' ')} model...")
|
||||
log.info(f"Loading {self.model_type.replace('_', ' ')} model '{self.model_name}'")
|
||||
self.load()
|
||||
if model_kwargs:
|
||||
self.configure(**model_kwargs)
|
||||
@@ -109,13 +128,23 @@ class InferenceModel(ABC):
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
if not self.cache_dir.exists():
|
||||
log.warn(
|
||||
f"Attempted to clear cache for model '{self.model_name}' but cache directory does not exist.",
|
||||
)
|
||||
return
|
||||
if not rmtree.avoids_symlink_attacks:
|
||||
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
|
||||
|
||||
if self.cache_dir.is_dir():
|
||||
log.info(f"Cleared cache directory for model '{self.model_name}'.")
|
||||
rmtree(self.cache_dir)
|
||||
else:
|
||||
log.warn(
|
||||
(
|
||||
f"Encountered file instead of directory at cache path "
|
||||
f"for '{self.model_name}'. Removing file and replacing with a directory."
|
||||
),
|
||||
)
|
||||
self.cache_dir.unlink()
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from clip_server.model.tokenization import Tokenizer
|
||||
from PIL import Image
|
||||
from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor
|
||||
|
||||
from ..config import log
|
||||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
@@ -105,9 +106,11 @@ class CLIPEncoder(InferenceModel):
|
||||
if model_name in _MODELS:
|
||||
return model_name
|
||||
elif model_name in _ST_TO_JINA_MODEL_NAME:
|
||||
print(
|
||||
(f"Warning: Sentence-Transformer model names such as '{model_name}' are no longer supported."),
|
||||
(f"Using '{_ST_TO_JINA_MODEL_NAME[model_name]}' instead as it is the best match for '{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:
|
||||
@@ -128,6 +131,10 @@ class CLIPEncoder(InferenceModel):
|
||||
os.remove(file)
|
||||
return True
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return (self.cache_dir / "textual.onnx").is_file() and (self.cache_dir / "visual.onnx").is_file()
|
||||
|
||||
|
||||
# same as `_transform_blob` without `_blob2image`
|
||||
def _transform_pil_image(n_px: int) -> Compose:
|
||||
|
||||
@@ -23,7 +23,7 @@ class FaceRecognizer(InferenceModel):
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = min_score
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
@@ -105,4 +105,4 @@ class FaceRecognizer(InferenceModel):
|
||||
return self.cache_dir.is_dir() and any(self.cache_dir.glob("*.onnx"))
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.det_model.det_thresh = model_kwargs.get("min_score", self.det_model.det_thresh)
|
||||
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)
|
||||
|
||||
@@ -8,6 +8,7 @@ from optimum.pipelines import pipeline
|
||||
from PIL import Image
|
||||
from transformers import AutoImageProcessor
|
||||
|
||||
from ..config import log
|
||||
from ..schemas import ModelType
|
||||
from .base import InferenceModel
|
||||
|
||||
@@ -22,7 +23,7 @@ class ImageClassifier(InferenceModel):
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = min_score
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _download(self, **model_kwargs: Any) -> None:
|
||||
@@ -35,19 +36,25 @@ class ImageClassifier(InferenceModel):
|
||||
)
|
||||
|
||||
def _load(self, **model_kwargs: Any) -> None:
|
||||
processor = AutoImageProcessor.from_pretrained(self.cache_dir)
|
||||
processor = AutoImageProcessor.from_pretrained(self.cache_dir, cache_dir=self.cache_dir)
|
||||
model_path = self.cache_dir / "model.onnx"
|
||||
model_kwargs |= {
|
||||
"cache_dir": self.cache_dir,
|
||||
"provider": self.providers[0],
|
||||
"provider_options": self.provider_options[0],
|
||||
"session_options": self.sess_options,
|
||||
}
|
||||
model_path = self.cache_dir / "model.onnx"
|
||||
|
||||
if model_path.exists():
|
||||
model = ORTModelForImageClassification.from_pretrained(self.cache_dir, **model_kwargs)
|
||||
self.model = pipeline(self.model_type.value, model, feature_extractor=processor)
|
||||
else:
|
||||
log.info(
|
||||
(
|
||||
f"ONNX model not found in cache directory for '{self.model_name}'."
|
||||
"Exporting optimized model for future use."
|
||||
),
|
||||
)
|
||||
self.sess_options.optimized_model_filepath = model_path.as_posix()
|
||||
self.model = pipeline(
|
||||
self.model_type.value,
|
||||
@@ -65,4 +72,4 @@ class ImageClassifier(InferenceModel):
|
||||
return tags
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.min_score = model_kwargs.get("min_score", self.min_score)
|
||||
self.min_score = model_kwargs.pop("minScore", self.min_score)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
export MACHINE_LEARNING_CACHE_FOLDER=/tmp/model_cache
|
||||
export MACHINE_LEARNING_MIN_FACE_SCORE=0.034 # returns 1 face per request; setting this to 0 blows up the number of faces to the thousands
|
||||
export MACHINE_LEARNING_MIN_TAG_SCORE=0.0
|
||||
export PID_FILE=/tmp/locust_pid
|
||||
export LOG_FILE=/tmp/gunicorn.log
|
||||
export HEADLESS=false
|
||||
export HOST=127.0.0.1:3003
|
||||
export CONCURRENCY=4
|
||||
export NUM_ENDPOINTS=3
|
||||
export PYTHONPATH=app
|
||||
|
||||
gunicorn app.main:app --worker-class uvicorn.workers.UvicornWorker \
|
||||
--bind $HOST --daemon --error-logfile $LOG_FILE --pid $PID_FILE
|
||||
while true ; do
|
||||
echo "Loading models..."
|
||||
sleep 5
|
||||
if cat $LOG_FILE | grep -q -E "startup complete"; then break; fi
|
||||
done
|
||||
|
||||
# "users" are assigned only one task, so multiply concurrency by the number of tasks
|
||||
locust --host http://$HOST --web-host 127.0.0.1 \
|
||||
--run-time 120s --users $(($CONCURRENCY * $NUM_ENDPOINTS)) $(if $HEADLESS; then echo "--headless"; fi)
|
||||
|
||||
if [[ -e $PID_FILE ]]; then kill $(cat $PID_FILE); fi
|
||||
@@ -1,13 +1,32 @@
|
||||
from io import BytesIO
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from locust import HttpUser, events, task
|
||||
from locust.env import Environment
|
||||
from PIL import Image
|
||||
from argparse import ArgumentParser
|
||||
byte_image = BytesIO()
|
||||
|
||||
|
||||
@events.init_command_line_parser.add_listener
|
||||
def _(parser: ArgumentParser) -> None:
|
||||
parser.add_argument("--tag-model", type=str, default="microsoft/resnet-50")
|
||||
parser.add_argument("--clip-model", type=str, default="ViT-B-32::openai")
|
||||
parser.add_argument("--face-model", type=str, default="buffalo_l")
|
||||
parser.add_argument("--tag-min-score", type=int, default=0.0,
|
||||
help="Returns all tags at or above this score. The default returns all tags.")
|
||||
parser.add_argument("--face-min-score", type=int, default=0.034,
|
||||
help=("Returns all faces at or above this score. The default returns 1 face per request; "
|
||||
"setting this to 0 blows up the number of faces to the thousands."))
|
||||
parser.add_argument("--image-size", type=int, default=1000)
|
||||
|
||||
|
||||
@events.test_start.add_listener
|
||||
def on_test_start(environment, **kwargs):
|
||||
def on_test_start(environment: Environment, **kwargs: Any) -> None:
|
||||
global byte_image
|
||||
image = Image.new("RGB", (1000, 1000))
|
||||
assert environment.parsed_options is not None
|
||||
image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size))
|
||||
byte_image = BytesIO()
|
||||
image.save(byte_image, format="jpeg")
|
||||
|
||||
@@ -19,34 +38,55 @@ class InferenceLoadTest(HttpUser):
|
||||
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
||||
|
||||
# re-use the image across all instances in a process
|
||||
def on_start(self):
|
||||
def on_start(self) -> None:
|
||||
global byte_image
|
||||
self.data = byte_image.getvalue()
|
||||
|
||||
|
||||
class ClassificationLoadTest(InferenceLoadTest):
|
||||
class ClassificationFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def classify(self):
|
||||
self.client.post(
|
||||
"/image-classifier/tag-image", data=self.data, headers=self.headers
|
||||
)
|
||||
def classify(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"minScore": self.environment.parsed_options.tag_min_score})),
|
||||
]
|
||||
files = {"image": self.data}
|
||||
self.client.post("/predict", data=data, files=files)
|
||||
|
||||
|
||||
class CLIPLoadTest(InferenceLoadTest):
|
||||
class CLIPTextFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_image(self):
|
||||
self.client.post(
|
||||
"/sentence-transformer/encode-image",
|
||||
data=self.data,
|
||||
headers=self.headers,
|
||||
)
|
||||
def encode_text(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"mode": "text"})),
|
||||
("text", "test search query")
|
||||
]
|
||||
self.client.post("/predict", data=data)
|
||||
|
||||
|
||||
class RecognitionLoadTest(InferenceLoadTest):
|
||||
class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def recognize(self):
|
||||
self.client.post(
|
||||
"/facial-recognition/detect-faces",
|
||||
data=self.data,
|
||||
headers=self.headers,
|
||||
)
|
||||
def encode_image(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"mode": "vision"})),
|
||||
]
|
||||
files = {"image": self.data}
|
||||
self.client.post("/predict", data=data, files=files)
|
||||
|
||||
|
||||
class RecognitionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def recognize(self) -> None:
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.face_model),
|
||||
("modelType", "facial-recognition"),
|
||||
("options", json.dumps({"minScore": self.environment.parsed_options.face_min_score})),
|
||||
]
|
||||
files = {"image": self.data}
|
||||
|
||||
self.client.post("/predict", data=data, files=files)
|
||||
|
||||
17
machine-learning/log_conf.json
Normal file
17
machine-learning/log_conf.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"disable_existing_loggers": true,
|
||||
"formatters": { "rich": { "show_path": false, "omit_repeated_times": false } },
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "app.config.CustomRichHandler",
|
||||
"formatter": "rich",
|
||||
"level": "INFO"
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"gunicorn.access": { "propagate": true },
|
||||
"gunicorn.error": { "propagate": true }
|
||||
},
|
||||
"root": { "handlers": ["console"] }
|
||||
}
|
||||
144
machine-learning/poetry.lock
generated
144
machine-learning/poetry.lock
generated
@@ -164,13 +164,13 @@ tests = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.7.1"
|
||||
version = "4.0.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
|
||||
{file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
|
||||
{file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"},
|
||||
{file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -178,9 +178,9 @@ idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (<0.22)"]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "async-timeout"
|
||||
@@ -874,18 +874,18 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.12.2"
|
||||
version = "3.12.3"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"},
|
||||
{file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"},
|
||||
{file = "filelock-3.12.3-py3-none-any.whl", hash = "sha256:f067e40ccc40f2b48395a80fcbd4728262fab54e232e090a4063ab804179efeb"},
|
||||
{file = "filelock-3.12.3.tar.gz", hash = "sha256:0ecc1dd2ec4672a10c8550a8182f1bd0c0a5088470ecd5a125e45f49472fac3d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"]
|
||||
docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
@@ -1453,17 +1453,17 @@ test = ["objgraph", "psutil"]
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "20.1.0"
|
||||
version = "21.2.0"
|
||||
description = "WSGI HTTP Server for UNIX"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
||||
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
|
||||
{file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
|
||||
{file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
setuptools = ">=3.0"
|
||||
packaging = "*"
|
||||
|
||||
[package.extras]
|
||||
eventlet = ["eventlet (>=0.24.1)"]
|
||||
@@ -2619,69 +2619,61 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.0.3"
|
||||
version = "2.1.0"
|
||||
description = "Powerful data structures for data analysis, time series, and statistics"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"},
|
||||
{file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"},
|
||||
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"},
|
||||
{file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"},
|
||||
{file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"},
|
||||
{file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"},
|
||||
{file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"},
|
||||
{file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
|
||||
{file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
|
||||
{file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"},
|
||||
{file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"},
|
||||
{file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"},
|
||||
{file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"},
|
||||
{file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = [
|
||||
{version = ">=1.21.0", markers = "python_version >= \"3.10\""},
|
||||
{version = ">=1.23.2", markers = "python_version >= \"3.11\""},
|
||||
]
|
||||
numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""}
|
||||
python-dateutil = ">=2.8.2"
|
||||
pytz = ">=2020.1"
|
||||
tzdata = ">=2022.1"
|
||||
|
||||
[package.extras]
|
||||
all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"]
|
||||
aws = ["s3fs (>=2021.08.0)"]
|
||||
clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"]
|
||||
compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"]
|
||||
computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"]
|
||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"]
|
||||
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
|
||||
aws = ["s3fs (>=2022.05.0)"]
|
||||
clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
|
||||
compression = ["zstandard (>=0.17.0)"]
|
||||
computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
|
||||
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
|
||||
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
|
||||
feather = ["pyarrow (>=7.0.0)"]
|
||||
fss = ["fsspec (>=2021.07.0)"]
|
||||
gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"]
|
||||
hdf5 = ["tables (>=3.6.1)"]
|
||||
html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"]
|
||||
mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"]
|
||||
output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"]
|
||||
fss = ["fsspec (>=2022.05.0)"]
|
||||
gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
|
||||
hdf5 = ["tables (>=3.7.0)"]
|
||||
html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
|
||||
mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
|
||||
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
|
||||
parquet = ["pyarrow (>=7.0.0)"]
|
||||
performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"]
|
||||
performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
|
||||
plot = ["matplotlib (>=3.6.1)"]
|
||||
postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"]
|
||||
spss = ["pyreadstat (>=1.1.2)"]
|
||||
sql-other = ["SQLAlchemy (>=1.4.16)"]
|
||||
test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
|
||||
xml = ["lxml (>=4.6.3)"]
|
||||
postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
|
||||
spss = ["pyreadstat (>=1.1.5)"]
|
||||
sql-other = ["SQLAlchemy (>=1.4.36)"]
|
||||
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
|
||||
xml = ["lxml (>=4.8.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
@@ -3877,13 +3869,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tifffile"
|
||||
version = "2023.8.25"
|
||||
version = "2023.8.30"
|
||||
description = "Read and write TIFF files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "tifffile-2023.8.25-py3-none-any.whl", hash = "sha256:40318485b59e9acb62e7139f22bd46e6760f92daea562b79900bfce3ee2613b7"},
|
||||
{file = "tifffile-2023.8.25.tar.gz", hash = "sha256:0a3ebcdfe71eb61a487dd22eaf21ed8962c511e6eb692153c7ac15f81798dfa4"},
|
||||
{file = "tifffile-2023.8.30-py3-none-any.whl", hash = "sha256:62364eef35a6fdcc7bc2ad6f97dd270f577efb01b31260ff800af76a66c1e145"},
|
||||
{file = "tifffile-2023.8.30.tar.gz", hash = "sha256:6a8c53b012a286b75d09a1498ab32f202f24cc6270a105b5d5911dc4426f162a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3894,13 +3886,13 @@ all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib"
|
||||
|
||||
[[package]]
|
||||
name = "timm"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
description = "PyTorch Image Models"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "timm-0.9.5-py3-none-any.whl", hash = "sha256:6e70af3a347bddb4167db46c3252a83c59165332ecf6b3df480d49c22866fa46"},
|
||||
{file = "timm-0.9.5.tar.gz", hash = "sha256:669835f0030cfb2412c464b7b563bb240d4d41a141226afbbf1b457e4f18cff1"},
|
||||
{file = "timm-0.9.6-py3-none-any.whl", hash = "sha256:7549a924b86a6151d4083a880c27ae86ce729e1b5c8c6099657217d0a0526a4e"},
|
||||
{file = "timm-0.9.6.tar.gz", hash = "sha256:6c3c0451b69431de0290eed5662e66b134caf916f1cb9b4aa3b9a13c3d61fd03"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4126,13 +4118,13 @@ telegram = ["requests"]
|
||||
|
||||
[[package]]
|
||||
name = "transformers"
|
||||
version = "4.32.0"
|
||||
version = "4.32.1"
|
||||
description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "transformers-4.32.0-py3-none-any.whl", hash = "sha256:32d8adf0ed76285508e7fd66657b4448ec1f882599ae6bf6f9c36bd7bf798402"},
|
||||
{file = "transformers-4.32.0.tar.gz", hash = "sha256:ca510f9688d2fe7347abbbfbd13f2f6dcd3c8349870c8d0ed98beed5f579b354"},
|
||||
{file = "transformers-4.32.1-py3-none-any.whl", hash = "sha256:b930d3dbd907a3f300cf49e54d63a56f8a0ab16b01a2c2a61ecff37c6de1da08"},
|
||||
{file = "transformers-4.32.1.tar.gz", hash = "sha256:1edc8ae1de357d97c3d36b04412aa63d55e6fc0c4b39b419a7d380ed947d2252"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4693,4 +4685,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "6d200d3ea1ccf9fb89f44043e3e0845e70f19aac374b96227559375f44508dc5"
|
||||
content-hash = "4e97a32e7525cfedbf23892b8c1191b3fe7b4d09b9f043cdb285ed9772862d67"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "machine-learning"
|
||||
version = "1.76.0"
|
||||
version = "1.77.0"
|
||||
description = ""
|
||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||
readme = "README.md"
|
||||
@@ -33,13 +33,13 @@ open-clip-torch = "^2.20.0"
|
||||
python-multipart = "^0.0.6"
|
||||
orjson = "^3.9.5"
|
||||
safetensors = "0.3.2"
|
||||
gunicorn = "^21.1.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = "^1.3.0"
|
||||
black = "^23.3.0"
|
||||
pytest = "^7.3.1"
|
||||
locust = "^2.15.1"
|
||||
gunicorn = "^20.1.0"
|
||||
httpx = "^0.24.1"
|
||||
pytest-asyncio = "^0.21.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
@@ -74,6 +74,7 @@ warn_untyped_fields = true
|
||||
module = [
|
||||
"huggingface_hub",
|
||||
"transformers",
|
||||
"gunicorn",
|
||||
"cv2",
|
||||
"insightface.model_zoo",
|
||||
"insightface.utils.face_align",
|
||||
|
||||
13
machine-learning/start.sh
Executable file
13
machine-learning/start.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
|
||||
|
||||
: "${MACHINE_LEARNING_HOST:=0.0.0.0}"
|
||||
: "${MACHINE_LEARNING_PORT:=3003}"
|
||||
: "${MACHINE_LEARNING_WORKERS:=1}"
|
||||
|
||||
gunicorn app.main:app \
|
||||
-k uvicorn.workers.UvicornWorker \
|
||||
-w $MACHINE_LEARNING_WORKERS \
|
||||
-b $MACHINE_LEARNING_HOST:$MACHINE_LEARNING_PORT \
|
||||
--log-config-json log_conf.json
|
||||
@@ -96,3 +96,8 @@ dependencies {
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
}
|
||||
|
||||
// This is uncommented in F-Droid build script
|
||||
//f configurations.all {
|
||||
//f exclude group: 'com.google.android.gms'
|
||||
//f }
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 99,
|
||||
"android.injected.version.name" => "1.76.0",
|
||||
"android.injected.version.code" => 100,
|
||||
"android.injected.version.name" => "1.77.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="67.877631">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.585931">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="23.895222">
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="24.755096">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -193,6 +193,8 @@
|
||||
"login_form_save_login": "Stay logged in",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Could not connect to server.",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"notification_permission_dialog_cancel": "Cancel",
|
||||
|
||||
@@ -169,4 +169,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -515,7 +515,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -543,7 +543,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 113;
|
||||
CURRENT_PROJECT_VERSION = 116;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -59,11 +59,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.73.0</string>
|
||||
<string>1.76.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>113</string>
|
||||
<string>116</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<true />
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.76.0"
|
||||
version_number: "1.77.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -5,32 +5,32 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000187">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000243">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.403882">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.611762">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.068392">
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="6.937008">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.988079">
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.740416">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="96.47923">
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="93.625943">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="57.517755">
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="62.107671">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -113,7 +113,17 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
Store.delete(StoreKey.accessToken),
|
||||
]);
|
||||
|
||||
state = state.copyWith(isAuthenticated: false);
|
||||
state = state.copyWith(
|
||||
deviceId: "",
|
||||
userId: "",
|
||||
userEmail: "",
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
profileImagePath: '',
|
||||
isAdmin: false,
|
||||
shouldChangePassword: false,
|
||||
isAuthenticated: false,
|
||||
);
|
||||
} catch (e) {
|
||||
log.severe("Error logging out $e");
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:auto_route/auto_route.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/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||
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/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
|
||||
class ChangePasswordForm extends HookConsumerWidget {
|
||||
const ChangePasswordForm({Key? key}) : super(key: key);
|
||||
@@ -84,14 +85,35 @@ class ChangePasswordForm extends HookConsumerWidget {
|
||||
.read(manualUploadProvider.notifier)
|
||||
.cancelBackup();
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
ref.read(assetProvider.notifier).clearAllAsset();
|
||||
await ref
|
||||
.read(assetProvider.notifier)
|
||||
.clearAllAsset();
|
||||
ref.read(websocketProvider.notifier).disconnect();
|
||||
|
||||
AutoRouter.of(context).replace(const LoginRoute());
|
||||
AutoRouter.of(context).navigateBack();
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "login_password_changed_success".tr(),
|
||||
toastType: ToastType.success,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "login_password_changed_error".tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => AutoRouter.of(context).navigateBack(),
|
||||
label: const Text('Back'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,10 +5,10 @@ import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
class AssetMarkerIcon extends StatelessWidget {
|
||||
const AssetMarkerIcon({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.id,
|
||||
this.isDarkTheme = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final String id;
|
||||
final bool isDarkTheme;
|
||||
|
||||
@@ -122,7 +122,7 @@ class MapAppBar extends HookWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 30),
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -41,7 +42,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
// Non-State variables
|
||||
bool userTappedOnMap = false;
|
||||
RenderList? _cachedRenderList;
|
||||
int lastAssetOffsetInSheet = -1;
|
||||
int assetOffsetInSheet = -1;
|
||||
late final DraggableScrollableController bottomSheetController;
|
||||
late final Debounce debounce;
|
||||
|
||||
@@ -50,14 +51,16 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
super.initState();
|
||||
bottomSheetController = DraggableScrollableController();
|
||||
debounce = Debounce(
|
||||
const Duration(milliseconds: 200),
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
double maxHeight = MediaQuery.of(context).size.height;
|
||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
final bottomPadding =
|
||||
Platform.isAndroid ? MediaQuery.of(context).padding.bottom - 10 : 0.0;
|
||||
final maxHeight = MediaQuery.of(context).size.height - bottomPadding;
|
||||
final isSheetScrolled = useState(false);
|
||||
final isSheetExpanded = useState(false);
|
||||
final assetsInBound = useState(<Asset>[]);
|
||||
@@ -68,7 +71,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
assetsInBound.value = event.assets;
|
||||
} else if (event is MapPageOnTapEvent) {
|
||||
userTappedOnMap = true;
|
||||
lastAssetOffsetInSheet = -1;
|
||||
assetOffsetInSheet = -1;
|
||||
bottomSheetController.animateTo(
|
||||
0.1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -98,8 +101,8 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
columnOffset = columnOffset < renderElement.totalCount
|
||||
? columnOffset
|
||||
: renderElement.totalCount - 1;
|
||||
lastAssetOffsetInSheet = rowOffset + columnOffset;
|
||||
final asset = _cachedRenderList?.allAssets?[lastAssetOffsetInSheet];
|
||||
assetOffsetInSheet = rowOffset + columnOffset;
|
||||
final asset = _cachedRenderList?.allAssets?[assetOffsetInSheet];
|
||||
userTappedOnMap = false;
|
||||
if (!userTappedOnMap && isSheetExpanded.value) {
|
||||
widget.bottomSheetEventSC.add(
|
||||
@@ -162,10 +165,10 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
}
|
||||
|
||||
void onTapMapButton() {
|
||||
if (lastAssetOffsetInSheet != -1) {
|
||||
if (assetOffsetInSheet != -1) {
|
||||
widget.bottomSheetEventSC.add(
|
||||
MapPageZoomToAsset(
|
||||
_cachedRenderList?.allAssets?[lastAssetOffsetInSheet],
|
||||
_cachedRenderList?.allAssets?[assetOffsetInSheet],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -176,7 +179,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
? "${assetsInBound.value.length} photo${assetsInBound.value.length > 1 ? "s" : ""}"
|
||||
: "map_no_assets_in_bounds".tr();
|
||||
final dragHandle = Container(
|
||||
height: 75,
|
||||
height: 60,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||
@@ -187,9 +190,9 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 5),
|
||||
const CustomDraggingHandle(),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
textToDisplay,
|
||||
style: TextStyle(
|
||||
@@ -199,6 +202,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 10,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.displayLarge
|
||||
@@ -226,6 +230,7 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: dragHandle,
|
||||
);
|
||||
}
|
||||
@@ -238,118 +243,125 @@ class AssetsInBoundBottomSheetState extends ConsumerState<MapPageBottomSheet> {
|
||||
if (!sheetExtended) {
|
||||
// reset state
|
||||
userTappedOnMap = false;
|
||||
lastAssetOffsetInSheet = -1;
|
||||
assetOffsetInSheet = -1;
|
||||
isSheetScrolled.value = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
DraggableScrollableSheet(
|
||||
controller: bottomSheetController,
|
||||
initialChildSize: 0.1,
|
||||
minChildSize: 0.1,
|
||||
maxChildSize: 0.55,
|
||||
snap: true,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
ScrollController scrollController,
|
||||
) {
|
||||
return Card(
|
||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 18.0,
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
children: [
|
||||
buildDragHandle(scrollController),
|
||||
if (isSheetExpanded.value && assetsInBound.value.isNotEmpty)
|
||||
ref
|
||||
.watch(
|
||||
renderListProvider(
|
||||
assetsInBound.value,
|
||||
),
|
||||
)
|
||||
.when(
|
||||
data: (renderList) {
|
||||
_cachedRenderList = renderList;
|
||||
final assetGrid = ImmichAssetGrid(
|
||||
shrinkWrap: true,
|
||||
renderList: renderList,
|
||||
showDragScroll: false,
|
||||
selectionActive: widget.selectionEnabled,
|
||||
showMultiSelectIndicator: false,
|
||||
listener: widget.selectionlistener,
|
||||
visibleItemsListener: visibleItemsListener,
|
||||
);
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: bottomPadding,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
DraggableScrollableSheet(
|
||||
controller: bottomSheetController,
|
||||
initialChildSize: 0.1,
|
||||
minChildSize: 0.1,
|
||||
maxChildSize: 0.55,
|
||||
snap: true,
|
||||
builder: (
|
||||
BuildContext context,
|
||||
ScrollController scrollController,
|
||||
) {
|
||||
return Card(
|
||||
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 18.0,
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
children: [
|
||||
buildDragHandle(scrollController),
|
||||
if (isSheetExpanded.value &&
|
||||
assetsInBound.value.isNotEmpty)
|
||||
ref
|
||||
.watch(
|
||||
renderListProvider(
|
||||
assetsInBound.value,
|
||||
),
|
||||
)
|
||||
.when(
|
||||
data: (renderList) {
|
||||
_cachedRenderList = renderList;
|
||||
final assetGrid = ImmichAssetGrid(
|
||||
shrinkWrap: true,
|
||||
renderList: renderList,
|
||||
showDragScroll: false,
|
||||
selectionActive: widget.selectionEnabled,
|
||||
showMultiSelectIndicator: false,
|
||||
listener: widget.selectionlistener,
|
||||
visibleItemsListener: visibleItemsListener,
|
||||
);
|
||||
|
||||
return Expanded(child: assetGrid);
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
log.warning(
|
||||
"Cannot get assets in the current map bounds ${error.toString()}",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
loading: () => const SizedBox.shrink(),
|
||||
return Expanded(child: assetGrid);
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
log.warning(
|
||||
"Cannot get assets in the current map bounds ${error.toString()}",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
if (isSheetExpanded.value && assetsInBound.value.isEmpty)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: buildNoPhotosWidget(),
|
||||
),
|
||||
if (isSheetExpanded.value && assetsInBound.value.isEmpty)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: buildNoPhotosWidget(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: maxHeight * currentExtend.value,
|
||||
left: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse('https://openstreetmap.org/copyright'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: maxHeight * currentExtend.value,
|
||||
left: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse('https://openstreetmap.org/copyright'),
|
||||
),
|
||||
child: ColoredBox(
|
||||
color:
|
||||
(widget.isDarkTheme ? Colors.grey[900] : Colors.grey[100])!,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: Text(
|
||||
'© OpenStreetMap contributors',
|
||||
style: TextStyle(
|
||||
fontSize: 6,
|
||||
color: !widget.isDarkTheme
|
||||
? Colors.grey[900]
|
||||
: Colors.grey[100],
|
||||
child: ColoredBox(
|
||||
color: (widget.isDarkTheme
|
||||
? Colors.grey[900]
|
||||
: Colors.grey[100])!,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: Text(
|
||||
'© OpenStreetMap contributors',
|
||||
style: TextStyle(
|
||||
fontSize: 6,
|
||||
color: !widget.isDarkTheme
|
||||
? Colors.grey[900]
|
||||
: Colors.grey[100],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: maxHeight * (0.14 + (currentExtend.value - 0.1)),
|
||||
right: 15,
|
||||
child: ElevatedButton(
|
||||
onPressed: () =>
|
||||
widget.bottomSheetEventSC.add(const MapPageZoomToLocation()),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
padding: const EdgeInsets.all(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.my_location,
|
||||
size: 22,
|
||||
fill: 1,
|
||||
Positioned(
|
||||
bottom: maxHeight * (0.14 + (currentExtend.value - 0.1)),
|
||||
right: 15,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => widget.bottomSheetEventSC
|
||||
.add(const MapPageZoomToLocation()),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
padding: const EdgeInsets.all(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.my_location,
|
||||
size: 22,
|
||||
fill: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -166,14 +166,15 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||
final mapMarker = mapMarkerData.value
|
||||
.firstWhereOrNull((e) => e.asset.id == assetInBottomSheet.id);
|
||||
if (mapMarker != null) {
|
||||
const zoomLevel = 16.0;
|
||||
LatLng? newCenter = mapController.centerBoundsWithPadding(
|
||||
mapMarker.point,
|
||||
const Offset(0, -120),
|
||||
zoomLevel: 6,
|
||||
zoomLevel: zoomLevel,
|
||||
);
|
||||
if (newCenter != null) {
|
||||
forceAssetUpdate = true;
|
||||
mapController.move(newCenter, 6);
|
||||
mapController.move(newCenter, zoomLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,6 +386,7 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||
builder: (ctx) => GestureDetector(
|
||||
onTap: () => openAssetInViewer(closestAssetMarker.value!.asset),
|
||||
child: AssetMarkerIcon(
|
||||
key: Key(closestAssetMarker.value!.asset.remoteId!),
|
||||
isDarkTheme: isDarkTheme,
|
||||
id: closestAssetMarker.value!.asset.remoteId!,
|
||||
),
|
||||
@@ -421,8 +423,15 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.black.withOpacity(0.5),
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarColor:
|
||||
(isDarkTheme ? Colors.black : Colors.white).withOpacity(0.5),
|
||||
statusBarIconBrightness:
|
||||
isDarkTheme ? Brightness.light : Brightness.dark,
|
||||
systemNavigationBarColor:
|
||||
isDarkTheme ? Colors.grey[900] : Colors.grey[100],
|
||||
systemNavigationBarIconBrightness:
|
||||
isDarkTheme ? Brightness.light : Brightness.dark,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
),
|
||||
child: Theme(
|
||||
// Override app theme based on map theme
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -27,7 +28,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
||||
final curatedPeople = ref.watch(getCuratedPeopleProvider);
|
||||
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
double imageSize = MediaQuery.of(context).size.width / 3;
|
||||
double imageSize = math.min(MediaQuery.of(context).size.width / 3, 150);
|
||||
|
||||
TextStyle categoryTitleStyle = const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
9
mobile/openapi/.openapi-generator/FILES
generated
9
mobile/openapi/.openapi-generator/FILES
generated
@@ -37,12 +37,14 @@ doc/BulkIdResponseDto.md
|
||||
doc/BulkIdsDto.md
|
||||
doc/CLIPConfig.md
|
||||
doc/CLIPMode.md
|
||||
doc/CQMode.md
|
||||
doc/ChangePasswordDto.md
|
||||
doc/CheckDuplicateAssetDto.md
|
||||
doc/CheckDuplicateAssetResponseDto.md
|
||||
doc/CheckExistingAssetsDto.md
|
||||
doc/CheckExistingAssetsResponseDto.md
|
||||
doc/ClassificationConfig.md
|
||||
doc/Colorspace.md
|
||||
doc/CreateAlbumDto.md
|
||||
doc/CreateProfileImageResponseDto.md
|
||||
doc/CreateTagDto.md
|
||||
@@ -73,6 +75,7 @@ doc/MemoryLaneResponseDto.md
|
||||
doc/MergePersonDto.md
|
||||
doc/ModelType.md
|
||||
doc/OAuthApi.md
|
||||
doc/OAuthAuthorizeResponseDto.md
|
||||
doc/OAuthCallbackDto.md
|
||||
doc/OAuthConfigDto.md
|
||||
doc/OAuthConfigResponseDto.md
|
||||
@@ -197,6 +200,8 @@ lib/model/check_existing_assets_response_dto.dart
|
||||
lib/model/classification_config.dart
|
||||
lib/model/clip_config.dart
|
||||
lib/model/clip_mode.dart
|
||||
lib/model/colorspace.dart
|
||||
lib/model/cq_mode.dart
|
||||
lib/model/create_album_dto.dart
|
||||
lib/model/create_profile_image_response_dto.dart
|
||||
lib/model/create_tag_dto.dart
|
||||
@@ -225,6 +230,7 @@ lib/model/map_marker_response_dto.dart
|
||||
lib/model/memory_lane_response_dto.dart
|
||||
lib/model/merge_person_dto.dart
|
||||
lib/model/model_type.dart
|
||||
lib/model/o_auth_authorize_response_dto.dart
|
||||
lib/model/o_auth_callback_dto.dart
|
||||
lib/model/o_auth_config_dto.dart
|
||||
lib/model/o_auth_config_response_dto.dart
|
||||
@@ -322,6 +328,8 @@ test/check_existing_assets_response_dto_test.dart
|
||||
test/classification_config_test.dart
|
||||
test/clip_config_test.dart
|
||||
test/clip_mode_test.dart
|
||||
test/colorspace_test.dart
|
||||
test/cq_mode_test.dart
|
||||
test/create_album_dto_test.dart
|
||||
test/create_profile_image_response_dto_test.dart
|
||||
test/create_tag_dto_test.dart
|
||||
@@ -352,6 +360,7 @@ test/memory_lane_response_dto_test.dart
|
||||
test/merge_person_dto_test.dart
|
||||
test/model_type_test.dart
|
||||
test/o_auth_api_test.dart
|
||||
test/o_auth_authorize_response_dto_test.dart
|
||||
test/o_auth_callback_dto_test.dart
|
||||
test/o_auth_config_dto_test.dart
|
||||
test/o_auth_config_response_dto_test.dart
|
||||
|
||||
6
mobile/openapi/README.md
generated
6
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.76.0
|
||||
- API version: 1.77.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
## Requirements
|
||||
@@ -124,6 +124,7 @@ Class | Method | HTTP request | Description
|
||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
|
||||
*OAuthApi* | [**authorizeOAuth**](doc//OAuthApi.md#authorizeoauth) | **POST** /oauth/authorize |
|
||||
*OAuthApi* | [**callback**](doc//OAuthApi.md#callback) | **POST** /oauth/callback |
|
||||
*OAuthApi* | [**generateConfig**](doc//OAuthApi.md#generateconfig) | **POST** /oauth/config |
|
||||
*OAuthApi* | [**link**](doc//OAuthApi.md#link) | **POST** /oauth/link |
|
||||
@@ -210,12 +211,14 @@ Class | Method | HTTP request | Description
|
||||
- [BulkIdsDto](doc//BulkIdsDto.md)
|
||||
- [CLIPConfig](doc//CLIPConfig.md)
|
||||
- [CLIPMode](doc//CLIPMode.md)
|
||||
- [CQMode](doc//CQMode.md)
|
||||
- [ChangePasswordDto](doc//ChangePasswordDto.md)
|
||||
- [CheckDuplicateAssetDto](doc//CheckDuplicateAssetDto.md)
|
||||
- [CheckDuplicateAssetResponseDto](doc//CheckDuplicateAssetResponseDto.md)
|
||||
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
||||
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
||||
- [ClassificationConfig](doc//ClassificationConfig.md)
|
||||
- [Colorspace](doc//Colorspace.md)
|
||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||
- [CreateTagDto](doc//CreateTagDto.md)
|
||||
@@ -244,6 +247,7 @@ Class | Method | HTTP request | Description
|
||||
- [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md)
|
||||
- [MergePersonDto](doc//MergePersonDto.md)
|
||||
- [ModelType](doc//ModelType.md)
|
||||
- [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
|
||||
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
|
||||
- [OAuthConfigDto](doc//OAuthConfigDto.md)
|
||||
- [OAuthConfigResponseDto](doc//OAuthConfigResponseDto.md)
|
||||
|
||||
2
mobile/openapi/doc/AssetApi.md
generated
2
mobile/openapi/doc/AssetApi.md
generated
@@ -1368,8 +1368,6 @@ Name | Type | Description | Notes
|
||||
|
||||
|
||||
|
||||
Update an asset
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
1
mobile/openapi/doc/AssetResponseDto.md
generated
1
mobile/openapi/doc/AssetResponseDto.md
generated
@@ -21,6 +21,7 @@ Name | Type | Description | Notes
|
||||
**livePhotoVideoId** | **String** | | [optional]
|
||||
**originalFileName** | **String** | |
|
||||
**originalPath** | **String** | |
|
||||
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
|
||||
**ownerId** | **String** | |
|
||||
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
|
||||
**resized** | **bool** | |
|
||||
|
||||
14
mobile/openapi/doc/CQMode.md
generated
Normal file
14
mobile/openapi/doc/CQMode.md
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
# openapi.model.CQMode
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
14
mobile/openapi/doc/Colorspace.md
generated
Normal file
14
mobile/openapi/doc/Colorspace.md
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
# openapi.model.Colorspace
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
44
mobile/openapi/doc/OAuthApi.md
generated
44
mobile/openapi/doc/OAuthApi.md
generated
@@ -9,6 +9,7 @@ All URIs are relative to */api*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**authorizeOAuth**](OAuthApi.md#authorizeoauth) | **POST** /oauth/authorize |
|
||||
[**callback**](OAuthApi.md#callback) | **POST** /oauth/callback |
|
||||
[**generateConfig**](OAuthApi.md#generateconfig) | **POST** /oauth/config |
|
||||
[**link**](OAuthApi.md#link) | **POST** /oauth/link |
|
||||
@@ -16,6 +17,47 @@ Method | HTTP request | Description
|
||||
[**unlink**](OAuthApi.md#unlink) | **POST** /oauth/unlink |
|
||||
|
||||
|
||||
# **authorizeOAuth**
|
||||
> OAuthAuthorizeResponseDto authorizeOAuth(oAuthConfigDto)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final api_instance = OAuthApi();
|
||||
final oAuthConfigDto = OAuthConfigDto(); // OAuthConfigDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.authorizeOAuth(oAuthConfigDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling OAuthApi->authorizeOAuth: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**oAuthConfigDto** | [**OAuthConfigDto**](OAuthConfigDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**OAuthAuthorizeResponseDto**](OAuthAuthorizeResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **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)
|
||||
|
||||
# **callback**
|
||||
> LoginResponseDto callback(oAuthCallbackDto)
|
||||
|
||||
@@ -62,6 +104,8 @@ No authorization required
|
||||
|
||||
|
||||
|
||||
@deprecated use feature flags and /oauth/authorize
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
15
mobile/openapi/doc/OAuthAuthorizeResponseDto.md
generated
Normal file
15
mobile/openapi/doc/OAuthAuthorizeResponseDto.md
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
# openapi.model.OAuthAuthorizeResponseDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**url** | **String** | |
|
||||
|
||||
[[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/PeopleUpdateItem.md
generated
2
mobile/openapi/doc/PeopleUpdateItem.md
generated
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional]
|
||||
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. Note: the mobile app cannot currently set the birth date to null. | [optional]
|
||||
**featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional]
|
||||
**id** | **String** | Person id. |
|
||||
**isHidden** | **bool** | Person visibility | [optional]
|
||||
|
||||
2
mobile/openapi/doc/PersonUpdateDto.md
generated
2
mobile/openapi/doc/PersonUpdateDto.md
generated
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional]
|
||||
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. Note: the mobile app cannot currently set the birth date to null. | [optional]
|
||||
**featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional]
|
||||
**isHidden** | **bool** | Person visibility | [optional]
|
||||
**name** | **String** | Person name. | [optional]
|
||||
|
||||
6
mobile/openapi/doc/SystemConfigFFmpegDto.md
generated
6
mobile/openapi/doc/SystemConfigFFmpegDto.md
generated
@@ -9,12 +9,18 @@ import 'package:openapi/api.dart';
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**accel** | [**TranscodeHWAccel**](TranscodeHWAccel.md) | |
|
||||
**bframes** | **int** | |
|
||||
**cqMode** | [**CQMode**](CQMode.md) | |
|
||||
**crf** | **int** | |
|
||||
**gopSize** | **int** | |
|
||||
**maxBitrate** | **String** | |
|
||||
**npl** | **int** | |
|
||||
**preset** | **String** | |
|
||||
**refs** | **int** | |
|
||||
**targetAudioCodec** | [**AudioCodec**](AudioCodec.md) | |
|
||||
**targetResolution** | **String** | |
|
||||
**targetVideoCodec** | [**VideoCodec**](VideoCodec.md) | |
|
||||
**temporalAQ** | **bool** | |
|
||||
**threads** | **int** | |
|
||||
**tonemap** | [**ToneMapping**](ToneMapping.md) | |
|
||||
**transcode** | [**TranscodePolicy**](TranscodePolicy.md) | |
|
||||
|
||||
2
mobile/openapi/doc/SystemConfigThumbnailDto.md
generated
2
mobile/openapi/doc/SystemConfigThumbnailDto.md
generated
@@ -8,7 +8,9 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**colorspace** | [**Colorspace**](Colorspace.md) | |
|
||||
**jpegSize** | **int** | |
|
||||
**quality** | **int** | |
|
||||
**webpSize** | **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/UpdateAssetDto.md
generated
1
mobile/openapi/doc/UpdateAssetDto.md
generated
@@ -11,7 +11,6 @@ Name | Type | Description | Notes
|
||||
**description** | **String** | | [optional]
|
||||
**isArchived** | **bool** | | [optional]
|
||||
**isFavorite** | **bool** | | [optional]
|
||||
**tagIds** | **List<String>** | | [optional] [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)
|
||||
|
||||
|
||||
3
mobile/openapi/lib/api.dart
generated
3
mobile/openapi/lib/api.dart
generated
@@ -73,12 +73,14 @@ part 'model/bulk_id_response_dto.dart';
|
||||
part 'model/bulk_ids_dto.dart';
|
||||
part 'model/clip_config.dart';
|
||||
part 'model/clip_mode.dart';
|
||||
part 'model/cq_mode.dart';
|
||||
part 'model/change_password_dto.dart';
|
||||
part 'model/check_duplicate_asset_dto.dart';
|
||||
part 'model/check_duplicate_asset_response_dto.dart';
|
||||
part 'model/check_existing_assets_dto.dart';
|
||||
part 'model/check_existing_assets_response_dto.dart';
|
||||
part 'model/classification_config.dart';
|
||||
part 'model/colorspace.dart';
|
||||
part 'model/create_album_dto.dart';
|
||||
part 'model/create_profile_image_response_dto.dart';
|
||||
part 'model/create_tag_dto.dart';
|
||||
@@ -107,6 +109,7 @@ part 'model/map_marker_response_dto.dart';
|
||||
part 'model/memory_lane_response_dto.dart';
|
||||
part 'model/merge_person_dto.dart';
|
||||
part 'model/model_type.dart';
|
||||
part 'model/o_auth_authorize_response_dto.dart';
|
||||
part 'model/o_auth_callback_dto.dart';
|
||||
part 'model/o_auth_config_dto.dart';
|
||||
part 'model/o_auth_config_response_dto.dart';
|
||||
|
||||
7
mobile/openapi/lib/api/asset_api.dart
generated
7
mobile/openapi/lib/api/asset_api.dart
generated
@@ -1384,10 +1384,7 @@ class AssetApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update an asset
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Performs an HTTP 'PUT /asset/{id}' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
@@ -1419,8 +1416,6 @@ class AssetApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// Update an asset
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] id (required):
|
||||
|
||||
54
mobile/openapi/lib/api/o_auth_api.dart
generated
54
mobile/openapi/lib/api/o_auth_api.dart
generated
@@ -16,6 +16,53 @@ class OAuthApi {
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'POST /oauth/authorize' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [OAuthConfigDto] oAuthConfigDto (required):
|
||||
Future<Response> authorizeOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/oauth/authorize';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = oAuthConfigDto;
|
||||
|
||||
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:
|
||||
///
|
||||
/// * [OAuthConfigDto] oAuthConfigDto (required):
|
||||
Future<OAuthAuthorizeResponseDto?> authorizeOAuth(OAuthConfigDto oAuthConfigDto,) async {
|
||||
final response = await authorizeOAuthWithHttpInfo(oAuthConfigDto,);
|
||||
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) {
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /oauth/callback' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
@@ -63,7 +110,10 @@ class OAuthApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /oauth/config' operation and returns the [Response].
|
||||
/// @deprecated use feature flags and /oauth/authorize
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [OAuthConfigDto] oAuthConfigDto (required):
|
||||
@@ -92,6 +142,8 @@ class OAuthApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// @deprecated use feature flags and /oauth/authorize
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [OAuthConfigDto] oAuthConfigDto (required):
|
||||
|
||||
6
mobile/openapi/lib/api_client.dart
generated
6
mobile/openapi/lib/api_client.dart
generated
@@ -239,6 +239,8 @@ class ApiClient {
|
||||
return CLIPConfig.fromJson(value);
|
||||
case 'CLIPMode':
|
||||
return CLIPModeTypeTransformer().decode(value);
|
||||
case 'CQMode':
|
||||
return CQModeTypeTransformer().decode(value);
|
||||
case 'ChangePasswordDto':
|
||||
return ChangePasswordDto.fromJson(value);
|
||||
case 'CheckDuplicateAssetDto':
|
||||
@@ -251,6 +253,8 @@ class ApiClient {
|
||||
return CheckExistingAssetsResponseDto.fromJson(value);
|
||||
case 'ClassificationConfig':
|
||||
return ClassificationConfig.fromJson(value);
|
||||
case 'Colorspace':
|
||||
return ColorspaceTypeTransformer().decode(value);
|
||||
case 'CreateAlbumDto':
|
||||
return CreateAlbumDto.fromJson(value);
|
||||
case 'CreateProfileImageResponseDto':
|
||||
@@ -307,6 +311,8 @@ class ApiClient {
|
||||
return MergePersonDto.fromJson(value);
|
||||
case 'ModelType':
|
||||
return ModelTypeTypeTransformer().decode(value);
|
||||
case 'OAuthAuthorizeResponseDto':
|
||||
return OAuthAuthorizeResponseDto.fromJson(value);
|
||||
case 'OAuthCallbackDto':
|
||||
return OAuthCallbackDto.fromJson(value);
|
||||
case 'OAuthConfigDto':
|
||||
|
||||
6
mobile/openapi/lib/api_helper.dart
generated
6
mobile/openapi/lib/api_helper.dart
generated
@@ -67,6 +67,12 @@ String parameterToString(dynamic value) {
|
||||
if (value is CLIPMode) {
|
||||
return CLIPModeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is CQMode) {
|
||||
return CQModeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is Colorspace) {
|
||||
return ColorspaceTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is DeleteAssetStatus) {
|
||||
return DeleteAssetStatusTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
19
mobile/openapi/lib/model/asset_response_dto.dart
generated
19
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -26,6 +26,7 @@ class AssetResponseDto {
|
||||
this.livePhotoVideoId,
|
||||
required this.originalFileName,
|
||||
required this.originalPath,
|
||||
this.owner,
|
||||
required this.ownerId,
|
||||
this.people = const [],
|
||||
required this.resized,
|
||||
@@ -69,6 +70,14 @@ class AssetResponseDto {
|
||||
|
||||
String originalPath;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
UserResponseDto? owner;
|
||||
|
||||
String ownerId;
|
||||
|
||||
List<PersonResponseDto> people;
|
||||
@@ -107,6 +116,7 @@ class AssetResponseDto {
|
||||
other.livePhotoVideoId == livePhotoVideoId &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalPath == originalPath &&
|
||||
other.owner == owner &&
|
||||
other.ownerId == ownerId &&
|
||||
other.people == people &&
|
||||
other.resized == resized &&
|
||||
@@ -132,6 +142,7 @@ class AssetResponseDto {
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
(originalFileName.hashCode) +
|
||||
(originalPath.hashCode) +
|
||||
(owner == null ? 0 : owner!.hashCode) +
|
||||
(ownerId.hashCode) +
|
||||
(people.hashCode) +
|
||||
(resized.hashCode) +
|
||||
@@ -142,7 +153,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, isFavorite=$isFavorite, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, 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, isFavorite=$isFavorite, livePhotoVideoId=$livePhotoVideoId, 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>{};
|
||||
@@ -167,6 +178,11 @@ class AssetResponseDto {
|
||||
}
|
||||
json[r'originalFileName'] = this.originalFileName;
|
||||
json[r'originalPath'] = this.originalPath;
|
||||
if (this.owner != null) {
|
||||
json[r'owner'] = this.owner;
|
||||
} else {
|
||||
// json[r'owner'] = null;
|
||||
}
|
||||
json[r'ownerId'] = this.ownerId;
|
||||
json[r'people'] = this.people;
|
||||
json[r'resized'] = this.resized;
|
||||
@@ -207,6 +223,7 @@ class AssetResponseDto {
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||
owner: UserResponseDto.fromJson(json[r'owner']),
|
||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||
people: PersonResponseDto.listFromJson(json[r'people']),
|
||||
resized: mapValueOfType<bool>(json, r'resized')!,
|
||||
|
||||
85
mobile/openapi/lib/model/colorspace.dart
generated
Normal file
85
mobile/openapi/lib/model/colorspace.dart
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class Colorspace {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const Colorspace._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const srgb = Colorspace._(r'srgb');
|
||||
static const p3 = Colorspace._(r'p3');
|
||||
|
||||
/// List of all possible values in this [enum][Colorspace].
|
||||
static const values = <Colorspace>[
|
||||
srgb,
|
||||
p3,
|
||||
];
|
||||
|
||||
static Colorspace? fromJson(dynamic value) => ColorspaceTypeTransformer().decode(value);
|
||||
|
||||
static List<Colorspace>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <Colorspace>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = Colorspace.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [Colorspace] to String,
|
||||
/// and [decode] dynamic data back to [Colorspace].
|
||||
class ColorspaceTypeTransformer {
|
||||
factory ColorspaceTypeTransformer() => _instance ??= const ColorspaceTypeTransformer._();
|
||||
|
||||
const ColorspaceTypeTransformer._();
|
||||
|
||||
String encode(Colorspace data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a Colorspace.
|
||||
///
|
||||
/// 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.
|
||||
Colorspace? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'srgb': return Colorspace.srgb;
|
||||
case r'p3': return Colorspace.p3;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [ColorspaceTypeTransformer] instance.
|
||||
static ColorspaceTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
88
mobile/openapi/lib/model/cq_mode.dart
generated
Normal file
88
mobile/openapi/lib/model/cq_mode.dart
generated
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// 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 CQMode {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const CQMode._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const auto = CQMode._(r'auto');
|
||||
static const cqp = CQMode._(r'cqp');
|
||||
static const icq = CQMode._(r'icq');
|
||||
|
||||
/// List of all possible values in this [enum][CQMode].
|
||||
static const values = <CQMode>[
|
||||
auto,
|
||||
cqp,
|
||||
icq,
|
||||
];
|
||||
|
||||
static CQMode? fromJson(dynamic value) => CQModeTypeTransformer().decode(value);
|
||||
|
||||
static List<CQMode>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <CQMode>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = CQMode.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [CQMode] to String,
|
||||
/// and [decode] dynamic data back to [CQMode].
|
||||
class CQModeTypeTransformer {
|
||||
factory CQModeTypeTransformer() => _instance ??= const CQModeTypeTransformer._();
|
||||
|
||||
const CQModeTypeTransformer._();
|
||||
|
||||
String encode(CQMode data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a CQMode.
|
||||
///
|
||||
/// 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.
|
||||
CQMode? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'auto': return CQMode.auto;
|
||||
case r'cqp': return CQMode.cqp;
|
||||
case r'icq': return CQMode.icq;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [CQModeTypeTransformer] instance.
|
||||
static CQModeTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ class MapMarkerResponseDto {
|
||||
|
||||
return MapMarkerResponseDto(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
lat: mapValueOfType<double>(json, r'lat')!,
|
||||
lon: mapValueOfType<double>(json, r'lon')!,
|
||||
lat: (mapValueOfType<num>(json, r'lat')!).toDouble(),
|
||||
lon: (mapValueOfType<num>(json, r'lon')!).toDouble(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
98
mobile/openapi/lib/model/o_auth_authorize_response_dto.dart
generated
Normal file
98
mobile/openapi/lib/model/o_auth_authorize_response_dto.dart
generated
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// 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 OAuthAuthorizeResponseDto {
|
||||
/// Returns a new [OAuthAuthorizeResponseDto] instance.
|
||||
OAuthAuthorizeResponseDto({
|
||||
required this.url,
|
||||
});
|
||||
|
||||
String url;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is OAuthAuthorizeResponseDto &&
|
||||
other.url == url;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(url.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'OAuthAuthorizeResponseDto[url=$url]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'url'] = this.url;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [OAuthAuthorizeResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static OAuthAuthorizeResponseDto? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return OAuthAuthorizeResponseDto(
|
||||
url: mapValueOfType<String>(json, r'url')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<OAuthAuthorizeResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <OAuthAuthorizeResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = OAuthAuthorizeResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, OAuthAuthorizeResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, OAuthAuthorizeResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = OAuthAuthorizeResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of OAuthAuthorizeResponseDto-objects as value to a dart map
|
||||
static Map<String, List<OAuthAuthorizeResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<OAuthAuthorizeResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = OAuthAuthorizeResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'url',
|
||||
};
|
||||
}
|
||||
|
||||
2
mobile/openapi/lib/model/people_update_item.dart
generated
2
mobile/openapi/lib/model/people_update_item.dart
generated
@@ -20,7 +20,7 @@ class PeopleUpdateItem {
|
||||
this.name,
|
||||
});
|
||||
|
||||
/// Person date of birth.
|
||||
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
DateTime? birthDate;
|
||||
|
||||
/// Asset is used to get the feature face thumbnail.
|
||||
|
||||
2
mobile/openapi/lib/model/person_update_dto.dart
generated
2
mobile/openapi/lib/model/person_update_dto.dart
generated
@@ -19,7 +19,7 @@ class PersonUpdateDto {
|
||||
this.name,
|
||||
});
|
||||
|
||||
/// Person date of birth.
|
||||
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
DateTime? birthDate;
|
||||
|
||||
/// Asset is used to get the feature face thumbnail.
|
||||
|
||||
@@ -14,12 +14,18 @@ class SystemConfigFFmpegDto {
|
||||
/// Returns a new [SystemConfigFFmpegDto] instance.
|
||||
SystemConfigFFmpegDto({
|
||||
required this.accel,
|
||||
required this.bframes,
|
||||
required this.cqMode,
|
||||
required this.crf,
|
||||
required this.gopSize,
|
||||
required this.maxBitrate,
|
||||
required this.npl,
|
||||
required this.preset,
|
||||
required this.refs,
|
||||
required this.targetAudioCodec,
|
||||
required this.targetResolution,
|
||||
required this.targetVideoCodec,
|
||||
required this.temporalAQ,
|
||||
required this.threads,
|
||||
required this.tonemap,
|
||||
required this.transcode,
|
||||
@@ -28,18 +34,30 @@ class SystemConfigFFmpegDto {
|
||||
|
||||
TranscodeHWAccel accel;
|
||||
|
||||
int bframes;
|
||||
|
||||
CQMode cqMode;
|
||||
|
||||
int crf;
|
||||
|
||||
int gopSize;
|
||||
|
||||
String maxBitrate;
|
||||
|
||||
int npl;
|
||||
|
||||
String preset;
|
||||
|
||||
int refs;
|
||||
|
||||
AudioCodec targetAudioCodec;
|
||||
|
||||
String targetResolution;
|
||||
|
||||
VideoCodec targetVideoCodec;
|
||||
|
||||
bool temporalAQ;
|
||||
|
||||
int threads;
|
||||
|
||||
ToneMapping tonemap;
|
||||
@@ -51,12 +69,18 @@ class SystemConfigFFmpegDto {
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto &&
|
||||
other.accel == accel &&
|
||||
other.bframes == bframes &&
|
||||
other.cqMode == cqMode &&
|
||||
other.crf == crf &&
|
||||
other.gopSize == gopSize &&
|
||||
other.maxBitrate == maxBitrate &&
|
||||
other.npl == npl &&
|
||||
other.preset == preset &&
|
||||
other.refs == refs &&
|
||||
other.targetAudioCodec == targetAudioCodec &&
|
||||
other.targetResolution == targetResolution &&
|
||||
other.targetVideoCodec == targetVideoCodec &&
|
||||
other.temporalAQ == temporalAQ &&
|
||||
other.threads == threads &&
|
||||
other.tonemap == tonemap &&
|
||||
other.transcode == transcode &&
|
||||
@@ -66,29 +90,41 @@ class SystemConfigFFmpegDto {
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(accel.hashCode) +
|
||||
(bframes.hashCode) +
|
||||
(cqMode.hashCode) +
|
||||
(crf.hashCode) +
|
||||
(gopSize.hashCode) +
|
||||
(maxBitrate.hashCode) +
|
||||
(npl.hashCode) +
|
||||
(preset.hashCode) +
|
||||
(refs.hashCode) +
|
||||
(targetAudioCodec.hashCode) +
|
||||
(targetResolution.hashCode) +
|
||||
(targetVideoCodec.hashCode) +
|
||||
(temporalAQ.hashCode) +
|
||||
(threads.hashCode) +
|
||||
(tonemap.hashCode) +
|
||||
(transcode.hashCode) +
|
||||
(twoPass.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigFFmpegDto[accel=$accel, crf=$crf, maxBitrate=$maxBitrate, preset=$preset, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
|
||||
String toString() => 'SystemConfigFFmpegDto[accel=$accel, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, npl=$npl, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'accel'] = this.accel;
|
||||
json[r'bframes'] = this.bframes;
|
||||
json[r'cqMode'] = this.cqMode;
|
||||
json[r'crf'] = this.crf;
|
||||
json[r'gopSize'] = this.gopSize;
|
||||
json[r'maxBitrate'] = this.maxBitrate;
|
||||
json[r'npl'] = this.npl;
|
||||
json[r'preset'] = this.preset;
|
||||
json[r'refs'] = this.refs;
|
||||
json[r'targetAudioCodec'] = this.targetAudioCodec;
|
||||
json[r'targetResolution'] = this.targetResolution;
|
||||
json[r'targetVideoCodec'] = this.targetVideoCodec;
|
||||
json[r'temporalAQ'] = this.temporalAQ;
|
||||
json[r'threads'] = this.threads;
|
||||
json[r'tonemap'] = this.tonemap;
|
||||
json[r'transcode'] = this.transcode;
|
||||
@@ -105,12 +141,18 @@ class SystemConfigFFmpegDto {
|
||||
|
||||
return SystemConfigFFmpegDto(
|
||||
accel: TranscodeHWAccel.fromJson(json[r'accel'])!,
|
||||
bframes: mapValueOfType<int>(json, r'bframes')!,
|
||||
cqMode: CQMode.fromJson(json[r'cqMode'])!,
|
||||
crf: mapValueOfType<int>(json, r'crf')!,
|
||||
gopSize: mapValueOfType<int>(json, r'gopSize')!,
|
||||
maxBitrate: mapValueOfType<String>(json, r'maxBitrate')!,
|
||||
npl: mapValueOfType<int>(json, r'npl')!,
|
||||
preset: mapValueOfType<String>(json, r'preset')!,
|
||||
refs: mapValueOfType<int>(json, r'refs')!,
|
||||
targetAudioCodec: AudioCodec.fromJson(json[r'targetAudioCodec'])!,
|
||||
targetResolution: mapValueOfType<String>(json, r'targetResolution')!,
|
||||
targetVideoCodec: VideoCodec.fromJson(json[r'targetVideoCodec'])!,
|
||||
temporalAQ: mapValueOfType<bool>(json, r'temporalAQ')!,
|
||||
threads: mapValueOfType<int>(json, r'threads')!,
|
||||
tonemap: ToneMapping.fromJson(json[r'tonemap'])!,
|
||||
transcode: TranscodePolicy.fromJson(json[r'transcode'])!,
|
||||
@@ -163,12 +205,18 @@ class SystemConfigFFmpegDto {
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'accel',
|
||||
'bframes',
|
||||
'cqMode',
|
||||
'crf',
|
||||
'gopSize',
|
||||
'maxBitrate',
|
||||
'npl',
|
||||
'preset',
|
||||
'refs',
|
||||
'targetAudioCodec',
|
||||
'targetResolution',
|
||||
'targetVideoCodec',
|
||||
'temporalAQ',
|
||||
'threads',
|
||||
'tonemap',
|
||||
'transcode',
|
||||
|
||||
@@ -13,31 +13,43 @@ part of openapi.api;
|
||||
class SystemConfigThumbnailDto {
|
||||
/// Returns a new [SystemConfigThumbnailDto] instance.
|
||||
SystemConfigThumbnailDto({
|
||||
required this.colorspace,
|
||||
required this.jpegSize,
|
||||
required this.quality,
|
||||
required this.webpSize,
|
||||
});
|
||||
|
||||
Colorspace colorspace;
|
||||
|
||||
int jpegSize;
|
||||
|
||||
int quality;
|
||||
|
||||
int webpSize;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigThumbnailDto &&
|
||||
other.colorspace == colorspace &&
|
||||
other.jpegSize == jpegSize &&
|
||||
other.quality == quality &&
|
||||
other.webpSize == webpSize;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(colorspace.hashCode) +
|
||||
(jpegSize.hashCode) +
|
||||
(quality.hashCode) +
|
||||
(webpSize.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigThumbnailDto[jpegSize=$jpegSize, webpSize=$webpSize]';
|
||||
String toString() => 'SystemConfigThumbnailDto[colorspace=$colorspace, jpegSize=$jpegSize, quality=$quality, webpSize=$webpSize]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'colorspace'] = this.colorspace;
|
||||
json[r'jpegSize'] = this.jpegSize;
|
||||
json[r'quality'] = this.quality;
|
||||
json[r'webpSize'] = this.webpSize;
|
||||
return json;
|
||||
}
|
||||
@@ -50,7 +62,9 @@ class SystemConfigThumbnailDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SystemConfigThumbnailDto(
|
||||
colorspace: Colorspace.fromJson(json[r'colorspace'])!,
|
||||
jpegSize: mapValueOfType<int>(json, r'jpegSize')!,
|
||||
quality: mapValueOfType<int>(json, r'quality')!,
|
||||
webpSize: mapValueOfType<int>(json, r'webpSize')!,
|
||||
);
|
||||
}
|
||||
@@ -99,7 +113,9 @@ class SystemConfigThumbnailDto {
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'colorspace',
|
||||
'jpegSize',
|
||||
'quality',
|
||||
'webpSize',
|
||||
};
|
||||
}
|
||||
|
||||
15
mobile/openapi/lib/model/update_asset_dto.dart
generated
15
mobile/openapi/lib/model/update_asset_dto.dart
generated
@@ -16,7 +16,6 @@ class UpdateAssetDto {
|
||||
this.description,
|
||||
this.isArchived,
|
||||
this.isFavorite,
|
||||
this.tagIds = const [],
|
||||
});
|
||||
|
||||
///
|
||||
@@ -43,25 +42,21 @@ class UpdateAssetDto {
|
||||
///
|
||||
bool? isFavorite;
|
||||
|
||||
List<String> tagIds;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
||||
other.description == description &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.tagIds == tagIds;
|
||||
other.isFavorite == isFavorite;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(tagIds.hashCode);
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UpdateAssetDto[description=$description, isArchived=$isArchived, isFavorite=$isFavorite, tagIds=$tagIds]';
|
||||
String toString() => 'UpdateAssetDto[description=$description, isArchived=$isArchived, isFavorite=$isFavorite]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -80,7 +75,6 @@ class UpdateAssetDto {
|
||||
} else {
|
||||
// json[r'isFavorite'] = null;
|
||||
}
|
||||
json[r'tagIds'] = this.tagIds;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -95,9 +89,6 @@ class UpdateAssetDto {
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
tagIds: json[r'tagIds'] is List
|
||||
? (json[r'tagIds'] as List).cast<String>()
|
||||
: const [],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
2
mobile/openapi/test/asset_api_test.dart
generated
2
mobile/openapi/test/asset_api_test.dart
generated
@@ -144,8 +144,6 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Update an asset
|
||||
//
|
||||
//Future<AssetResponseDto> updateAsset(String id, UpdateAssetDto updateAssetDto) async
|
||||
test('test updateAsset', () async {
|
||||
// TODO
|
||||
|
||||
5
mobile/openapi/test/asset_response_dto_test.dart
generated
5
mobile/openapi/test/asset_response_dto_test.dart
generated
@@ -82,6 +82,11 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// UserResponseDto owner
|
||||
test('to test the property `owner`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String ownerId
|
||||
test('to test the property `ownerId`', () async {
|
||||
// TODO
|
||||
|
||||
21
mobile/openapi/test/colorspace_test.dart
generated
Normal file
21
mobile/openapi/test/colorspace_test.dart
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for Colorspace
|
||||
void main() {
|
||||
|
||||
group('test Colorspace', () {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
21
mobile/openapi/test/cq_mode_test.dart
generated
Normal file
21
mobile/openapi/test/cq_mode_test.dart
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for CQMode
|
||||
void main() {
|
||||
|
||||
group('test CQMode', () {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
7
mobile/openapi/test/o_auth_api_test.dart
generated
7
mobile/openapi/test/o_auth_api_test.dart
generated
@@ -17,11 +17,18 @@ void main() {
|
||||
// final instance = OAuthApi();
|
||||
|
||||
group('tests for OAuthApi', () {
|
||||
//Future<OAuthAuthorizeResponseDto> authorizeOAuth(OAuthConfigDto oAuthConfigDto) async
|
||||
test('test authorizeOAuth', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
//Future<LoginResponseDto> callback(OAuthCallbackDto oAuthCallbackDto) async
|
||||
test('test callback', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// @deprecated use feature flags and /oauth/authorize
|
||||
//
|
||||
//Future<OAuthConfigResponseDto> generateConfig(OAuthConfigDto oAuthConfigDto) async
|
||||
test('test generateConfig', () async {
|
||||
// TODO
|
||||
|
||||
27
mobile/openapi/test/o_auth_authorize_response_dto_test.dart
generated
Normal file
27
mobile/openapi/test/o_auth_authorize_response_dto_test.dart
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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 OAuthAuthorizeResponseDto
|
||||
void main() {
|
||||
// final instance = OAuthAuthorizeResponseDto();
|
||||
|
||||
group('test OAuthAuthorizeResponseDto', () {
|
||||
// String url
|
||||
test('to test the property `url`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
2
mobile/openapi/test/people_update_item_test.dart
generated
2
mobile/openapi/test/people_update_item_test.dart
generated
@@ -16,7 +16,7 @@ void main() {
|
||||
// final instance = PeopleUpdateItem();
|
||||
|
||||
group('test PeopleUpdateItem', () {
|
||||
// Person date of birth.
|
||||
// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
// DateTime birthDate
|
||||
test('to test the property `birthDate`', () async {
|
||||
// TODO
|
||||
|
||||
2
mobile/openapi/test/person_update_dto_test.dart
generated
2
mobile/openapi/test/person_update_dto_test.dart
generated
@@ -16,7 +16,7 @@ void main() {
|
||||
// final instance = PersonUpdateDto();
|
||||
|
||||
group('test PersonUpdateDto', () {
|
||||
// Person date of birth.
|
||||
// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
|
||||
// DateTime birthDate
|
||||
test('to test the property `birthDate`', () async {
|
||||
// TODO
|
||||
|
||||
@@ -21,21 +21,46 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int bframes
|
||||
test('to test the property `bframes`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// CQMode cqMode
|
||||
test('to test the property `cqMode`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int crf
|
||||
test('to test the property `crf`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int gopSize
|
||||
test('to test the property `gopSize`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String maxBitrate
|
||||
test('to test the property `maxBitrate`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int npl
|
||||
test('to test the property `npl`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String preset
|
||||
test('to test the property `preset`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int refs
|
||||
test('to test the property `refs`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// AudioCodec targetAudioCodec
|
||||
test('to test the property `targetAudioCodec`', () async {
|
||||
// TODO
|
||||
@@ -51,6 +76,11 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool temporalAQ
|
||||
test('to test the property `temporalAQ`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int threads
|
||||
test('to test the property `threads`', () async {
|
||||
// TODO
|
||||
|
||||
@@ -16,11 +16,21 @@ void main() {
|
||||
// final instance = SystemConfigThumbnailDto();
|
||||
|
||||
group('test SystemConfigThumbnailDto', () {
|
||||
// Colorspace colorspace
|
||||
test('to test the property `colorspace`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int jpegSize
|
||||
test('to test the property `jpegSize`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int quality
|
||||
test('to test the property `quality`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// int webpSize
|
||||
test('to test the property `webpSize`', () async {
|
||||
// TODO
|
||||
|
||||
5
mobile/openapi/test/update_asset_dto_test.dart
generated
5
mobile/openapi/test/update_asset_dto_test.dart
generated
@@ -31,11 +31,6 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// List<String> tagIds (default value: const [])
|
||||
test('to test the property `tagIds`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: "none"
|
||||
version: 1.76.0+99
|
||||
version: 1.77.0+100
|
||||
isar_version: &isar_version 3.1.0+1
|
||||
|
||||
environment:
|
||||
@@ -60,6 +60,15 @@ dependencies:
|
||||
image_picker: ^0.8.5+3 # only used to select user profile image from system gallery -> we can simply select an image from within immich?
|
||||
logging: ^1.1.0
|
||||
|
||||
# This is uncommented in F-Droid build script
|
||||
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105
|
||||
#fdependency_overrides:
|
||||
#f geolocator_android:
|
||||
#f git:
|
||||
#f url: https://github.com/Zverik/flutter-geolocator.git
|
||||
#f ref: floss
|
||||
#f path: geolocator_android
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"semi": true,
|
||||
"organizeImportsSkipDestructiveCodeActions": true
|
||||
"organizeImportsSkipDestructiveCodeActions": true,
|
||||
"plugins": ["prettier-plugin-organize-imports"]
|
||||
}
|
||||
|
||||
@@ -2020,7 +2020,6 @@
|
||||
},
|
||||
"/asset/{id}": {
|
||||
"put": {
|
||||
"description": "Update an asset",
|
||||
"operationId": "updateAsset",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2477,6 +2476,37 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/oauth/authorize": {
|
||||
"post": {
|
||||
"operationId": "authorizeOAuth",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OAuthConfigDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OAuthAuthorizeResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"OAuth"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/oauth/callback": {
|
||||
"post": {
|
||||
"operationId": "callback",
|
||||
@@ -2510,6 +2540,8 @@
|
||||
},
|
||||
"/oauth/config": {
|
||||
"post": {
|
||||
"deprecated": true,
|
||||
"description": "@deprecated use feature flags and /oauth/authorize",
|
||||
"operationId": "generateConfig",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
@@ -4680,7 +4712,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.76.0",
|
||||
"version": "1.77.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
@@ -5176,6 +5208,9 @@
|
||||
"originalPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/UserResponseDto"
|
||||
},
|
||||
"ownerId": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5214,8 +5249,8 @@
|
||||
"type",
|
||||
"id",
|
||||
"deviceAssetId",
|
||||
"ownerId",
|
||||
"deviceId",
|
||||
"ownerId",
|
||||
"originalPath",
|
||||
"originalFileName",
|
||||
"resized",
|
||||
@@ -5382,6 +5417,14 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CQMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
"cqp",
|
||||
"icq"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ChangePasswordDto": {
|
||||
"properties": {
|
||||
"newPassword": {
|
||||
@@ -5482,6 +5525,13 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Colorspace": {
|
||||
"enum": [
|
||||
"srgb",
|
||||
"p3"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CreateAlbumDto": {
|
||||
"properties": {
|
||||
"albumName": {
|
||||
@@ -6202,6 +6252,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"OAuthAuthorizeResponseDto": {
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"OAuthCallbackDto": {
|
||||
"properties": {
|
||||
"url": {
|
||||
@@ -6287,7 +6348,7 @@
|
||||
"PeopleUpdateItem": {
|
||||
"properties": {
|
||||
"birthDate": {
|
||||
"description": "Person date of birth.",
|
||||
"description": "Person date of birth.\nNote: the mobile app cannot currently set the birth date to null.",
|
||||
"format": "date",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
@@ -6346,7 +6407,7 @@
|
||||
"PersonUpdateDto": {
|
||||
"properties": {
|
||||
"birthDate": {
|
||||
"description": "Person date of birth.",
|
||||
"description": "Person date of birth.\nNote: the mobile app cannot currently set the birth date to null.",
|
||||
"format": "date",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
@@ -6957,15 +7018,30 @@
|
||||
"accel": {
|
||||
"$ref": "#/components/schemas/TranscodeHWAccel"
|
||||
},
|
||||
"bframes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cqMode": {
|
||||
"$ref": "#/components/schemas/CQMode"
|
||||
},
|
||||
"crf": {
|
||||
"type": "integer"
|
||||
},
|
||||
"gopSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"maxBitrate": {
|
||||
"type": "string"
|
||||
},
|
||||
"npl": {
|
||||
"type": "integer"
|
||||
},
|
||||
"preset": {
|
||||
"type": "string"
|
||||
},
|
||||
"refs": {
|
||||
"type": "integer"
|
||||
},
|
||||
"targetAudioCodec": {
|
||||
"$ref": "#/components/schemas/AudioCodec"
|
||||
},
|
||||
@@ -6975,6 +7051,9 @@
|
||||
"targetVideoCodec": {
|
||||
"$ref": "#/components/schemas/VideoCodec"
|
||||
},
|
||||
"temporalAQ": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"threads": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -6993,12 +7072,18 @@
|
||||
"threads",
|
||||
"targetVideoCodec",
|
||||
"targetAudioCodec",
|
||||
"bframes",
|
||||
"refs",
|
||||
"gopSize",
|
||||
"npl",
|
||||
"cqMode",
|
||||
"transcode",
|
||||
"accel",
|
||||
"tonemap",
|
||||
"preset",
|
||||
"targetResolution",
|
||||
"maxBitrate",
|
||||
"temporalAQ",
|
||||
"twoPass"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -7208,16 +7293,24 @@
|
||||
},
|
||||
"SystemConfigThumbnailDto": {
|
||||
"properties": {
|
||||
"colorspace": {
|
||||
"$ref": "#/components/schemas/Colorspace"
|
||||
},
|
||||
"jpegSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"quality": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webpSize": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"webpSize",
|
||||
"jpegSize"
|
||||
"jpegSize",
|
||||
"quality",
|
||||
"colorspace"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -7333,18 +7426,6 @@
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tagIds": {
|
||||
"example": [
|
||||
"bf973405-3f2a-48d2-a687-2ed4167164be",
|
||||
"dd41870b-5d00-46d2-924e-1d8489a0aa0f",
|
||||
"fad77c3f-deef-4e7e-9608-14c1aa4e559a"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": "Array of tag IDs to add to the asset",
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -204,6 +204,10 @@ class {{{classname}}} {
|
||||
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
|
||||
: {{{datatypeWithEnum}}}.parse(json[r'{{{baseName}}}'].toString()),
|
||||
{{/isNumber}}
|
||||
{{#isDouble}}
|
||||
{{{name}}}: (mapValueOfType<num>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}).toDouble(),
|
||||
{{/isDouble}}
|
||||
{{^isDouble}}
|
||||
{{^isNumber}}
|
||||
{{^isEnum}}
|
||||
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||
@@ -212,6 +216,7 @@ class {{{classname}}} {
|
||||
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||
{{/isEnum}}
|
||||
{{/isNumber}}
|
||||
{{/isDouble}}
|
||||
{{/isMap}}
|
||||
{{/isArray}}
|
||||
{{/complexType}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- native_class.mustache 2023-06-22 12:56:11.090350406 -0500
|
||||
+++ native_class1.mustache 2023-06-22 12:57:14.498184792 -0500
|
||||
--- native_class.mustache 2023-08-31 23:09:59.584269162 +0200
|
||||
+++ native_class1.mustache 2023-08-31 22:59:53.633083270 +0200
|
||||
@@ -91,14 +91,14 @@
|
||||
{{/isDateTime}}
|
||||
{{#isNullable}}
|
||||
@@ -35,3 +35,22 @@
|
||||
return {{{classname}}}(
|
||||
{{#vars}}
|
||||
{{#isDateTime}}
|
||||
@@ -215,6 +204,10 @@
|
||||
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
|
||||
: {{{datatypeWithEnum}}}.parse(json[r'{{{baseName}}}'].toString()),
|
||||
{{/isNumber}}
|
||||
+ {{#isDouble}}
|
||||
+ {{{name}}}: (mapValueOfType<num>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}).toDouble(),
|
||||
+ {{/isDouble}}
|
||||
+ {{^isDouble}}
|
||||
{{^isNumber}}
|
||||
{{^isEnum}}
|
||||
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||
@@ -223,6 +216,7 @@
|
||||
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||
{{/isEnum}}
|
||||
{{/isNumber}}
|
||||
+ {{/isDouble}}
|
||||
{{/isMap}}
|
||||
{{/isArray}}
|
||||
{{/complexType}}
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.76.0",
|
||||
"version": "1.77.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.76.0",
|
||||
"version": "1.77.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.22.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.76.0",
|
||||
"version": "1.77.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AlbumEntity } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { AssetResponseDto, mapAsset } from '../asset';
|
||||
import { mapUser, UserResponseDto } from '../user';
|
||||
import { UserResponseDto, mapUser } from '../user';
|
||||
|
||||
export class AlbumResponseDto {
|
||||
id!: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { ValidateUUID } from '../../domain.util';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from '../../domain.util';
|
||||
|
||||
export class CreateAlbumDto {
|
||||
@IsNotEmpty()
|
||||
@@ -9,7 +9,7 @@ export class CreateAlbumDto {
|
||||
albumName!: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
description?: string;
|
||||
|
||||
@ValidateUUID({ optional: true, each: true })
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { ValidateUUID } from '../../domain.util';
|
||||
import { IsString } from 'class-validator';
|
||||
import { Optional, ValidateUUID } from '../../domain.util';
|
||||
|
||||
export class UpdateAlbumDto {
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString()
|
||||
albumName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { toBoolean } from '../../domain.util';
|
||||
import { IsBoolean } from 'class-validator';
|
||||
import { Optional, toBoolean } from '../../domain.util';
|
||||
|
||||
export class AlbumInfoDto {
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
withoutAssets?: boolean;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { toBoolean, ValidateUUID } from '../../domain.util';
|
||||
import { IsBoolean } from 'class-validator';
|
||||
import { Optional, ValidateUUID, toBoolean } from '../../domain.util';
|
||||
|
||||
export class GetAlbumsDto {
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
@ApiProperty()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { Optional } from '../domain.util';
|
||||
export class APIKeyCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@Optional()
|
||||
name?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||
import { Paginated, PaginationOptions } from '../domain.util';
|
||||
|
||||
export type AssetStats = Record<AssetType, number>;
|
||||
@@ -86,4 +86,5 @@ export interface IAssetRepository {
|
||||
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
||||
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
||||
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
||||
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AssetType } from '@app/infra/entities';
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
IAccessRepositoryMock,
|
||||
assetStub,
|
||||
authStub,
|
||||
IAccessRepositoryMock,
|
||||
newAccessRepositoryMock,
|
||||
newAssetRepositoryMock,
|
||||
newCryptoRepositoryMock,
|
||||
@@ -519,6 +519,30 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should require asset write access for the id', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(assetMock.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update the asset', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
assetMock.save.mockResolvedValue(assetStub.image);
|
||||
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
|
||||
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
|
||||
});
|
||||
|
||||
it('should update the exif description', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
assetMock.save.mockResolvedValue(assetStub.image);
|
||||
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
|
||||
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAll', () => {
|
||||
it('should require asset write access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ICryptoRepository } from '../crypto';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
import { HumanReadableSize, usePagination } from '../domain.util';
|
||||
import { IJobRepository, JobName } from '../job';
|
||||
import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage';
|
||||
import { IStorageRepository, ImmichReadStream, StorageCore, StorageFolder } from '../storage';
|
||||
import { IAssetRepository } from './asset.repository';
|
||||
import {
|
||||
AssetBulkUpdateDto,
|
||||
@@ -21,17 +21,18 @@ import {
|
||||
DownloadInfoDto,
|
||||
DownloadResponseDto,
|
||||
MapMarkerDto,
|
||||
mapStats,
|
||||
MemoryLaneDto,
|
||||
TimeBucketAssetDto,
|
||||
TimeBucketDto,
|
||||
UpdateAssetDto,
|
||||
mapStats,
|
||||
} from './dto';
|
||||
import {
|
||||
AssetResponseDto,
|
||||
mapAsset,
|
||||
MapMarkerResponseDto,
|
||||
MemoryLaneResponseDto,
|
||||
TimeBucketResponseDto,
|
||||
mapAsset,
|
||||
} from './response-dto';
|
||||
|
||||
export enum UploadFieldName {
|
||||
@@ -279,6 +280,19 @@ export class AssetService {
|
||||
return mapStats(stats);
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
||||
|
||||
const { description, ...rest } = dto;
|
||||
if (description !== undefined) {
|
||||
await this.assetRepository.upsertExif({ assetId: id, description });
|
||||
}
|
||||
|
||||
const asset = await this.assetRepository.save({ id, ...rest });
|
||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
|
||||
return mapAsset(asset);
|
||||
}
|
||||
|
||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto) {
|
||||
const { ids, ...options } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user