From f952bc0b64c9e6f7f2a8320bc12a31576040be4e Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Fri, 14 Jul 2023 09:30:17 -0400 Subject: [PATCH] refactor(server): asset stats (#3253) * refactor(server): asset stats * chore: open api --- cli/src/api/open-api/api.ts | 194 +++++++---------- mobile/openapi/.openapi-generator/FILES | 6 +- mobile/openapi/README.md | Bin 18099 -> 17931 bytes mobile/openapi/doc/AssetApi.md | Bin 55860 -> 54046 bytes ...esponseDto.md => AssetStatsResponseDto.md} | Bin 597 -> 467 bytes mobile/openapi/lib/api.dart | Bin 5824 -> 5813 bytes mobile/openapi/lib/api/asset_api.dart | Bin 50527 -> 49507 bytes mobile/openapi/lib/api_client.dart | Bin 18345 -> 18329 bytes .../asset_count_by_user_id_response_dto.dart | Bin 3829 -> 0 bytes .../lib/model/asset_stats_response_dto.dart | Bin 0 -> 3298 bytes mobile/openapi/test/asset_api_test.dart | Bin 5295 -> 5144 bytes ...art => asset_stats_response_dto_test.dart} | Bin 1069 -> 766 bytes server/immich-openapi-specs.json | 108 ++++------ server/src/domain/asset/asset.repository.ts | 8 + server/src/domain/asset/asset.service.spec.ts | 44 +++- server/src/domain/asset/asset.service.ts | 6 + .../domain/asset/dto/asset-statistics.dto.ts | 37 ++++ server/src/domain/asset/dto/index.ts | 1 + .../immich/api-v1/asset/asset-repository.ts | 57 +---- .../immich/api-v1/asset/asset.controller.ts | 10 - .../immich/api-v1/asset/asset.service.spec.ts | 35 ---- .../src/immich/api-v1/asset/asset.service.ts | 9 - .../asset-count-by-user-id-response.dto.ts | 18 -- .../immich/controllers/asset.controller.ts | 7 + .../infra/repositories/asset.repository.ts | 36 ++++ .../repositories/asset.repository.mock.ts | 1 + web/src/api/open-api/api.ts | 195 +++++++----------- .../side-bar/side-bar.svelte | 59 +----- web/src/routes/(user)/photos/+page.svelte | 10 +- 29 files changed, 351 insertions(+), 490 deletions(-) rename mobile/openapi/doc/{AssetCountByUserIdResponseDto.md => AssetStatsResponseDto.md} (58%) delete mode 100644 mobile/openapi/lib/model/asset_count_by_user_id_response_dto.dart create mode 100644 mobile/openapi/lib/model/asset_stats_response_dto.dart rename mobile/openapi/test/{asset_count_by_user_id_response_dto_test.dart => asset_stats_response_dto_test.dart} (50%) create mode 100644 server/src/domain/asset/dto/asset-statistics.dto.ts delete mode 100644 server/src/immich/api-v1/asset/response-dto/asset-count-by-user-id-response.dto.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 4f36e2a94..68552add3 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -486,43 +486,6 @@ export interface AssetCountByTimeBucketResponseDto { */ 'buckets': Array; } -/** - * - * @export - * @interface AssetCountByUserIdResponseDto - */ -export interface AssetCountByUserIdResponseDto { - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'audio': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'photos': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'videos': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'other': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'total': number; -} /** * * @export @@ -724,6 +687,31 @@ export interface AssetResponseDto { } +/** + * + * @export + * @interface AssetStatsResponseDto + */ +export interface AssetStatsResponseDto { + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'images': number; + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'videos': number; + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'total': number; +} /** * * @export @@ -4892,44 +4880,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getArchivedAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/stat/archive`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5079,8 +5029,8 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/count-by-user-id`; + getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/asset/search-terms`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -5114,11 +5064,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/search-terms`; + getAssetStats: async (isArchived?: boolean, isFavorite?: boolean, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/asset/statistics`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -5139,6 +5091,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -5887,15 +5847,6 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getArchivedAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getArchivedAssetCountByUserId(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * Get a single asset\'s information * @param {string} id @@ -5932,17 +5883,19 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByUserId(options); + async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options); + async getAssetStats(isArchived?: boolean, isFavorite?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetStats(isArchived, isFavorite, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -6160,14 +6113,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise> { return localVarFp.getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getArchivedAssetCountByUserId(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getArchivedAssetCountByUserId(options).then((request) => request(axios, basePath)); - }, /** * Get a single asset\'s information * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. @@ -6200,16 +6145,17 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByUserId(options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.getAssetCountByUserId(options).then((request) => request(axios, basePath)); + getAssetSearchTerms(options?: AxiosRequestConfig): AxiosPromise> { + return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath)); }, /** * + * @param {AssetApiGetAssetStatsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetSearchTerms(options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath)); + getAssetStats(requestParameters: AssetApiGetAssetStatsRequest = {}, options?: AxiosRequestConfig): AxiosPromise { + return localVarFp.getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, options).then((request) => request(axios, basePath)); }, /** * @@ -6523,6 +6469,27 @@ export interface AssetApiGetAssetCountByTimeBucketRequest { readonly getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto } +/** + * Request parameters for getAssetStats operation in AssetApi. + * @export + * @interface AssetApiGetAssetStatsRequest + */ +export interface AssetApiGetAssetStatsRequest { + /** + * + * @type {boolean} + * @memberof AssetApiGetAssetStats + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetAssetStats + */ + readonly isFavorite?: boolean +} + /** * Request parameters for getAssetThumbnail operation in AssetApi. * @export @@ -6915,16 +6882,6 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getArchivedAssetCountByUserId(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getArchivedAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); - } - /** * Get a single asset\'s information * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. @@ -6964,18 +6921,19 @@ export class AssetApi extends BaseAPI { * @throws {RequiredError} * @memberof AssetApi */ - public getAssetCountByUserId(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); + public getAssetSearchTerms(options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetSearchTerms(options).then((request) => request(this.axios, this.basePath)); } /** * + * @param {AssetApiGetAssetStatsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public getAssetSearchTerms(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetSearchTerms(options).then((request) => request(this.axios, this.basePath)); + public getAssetStats(requestParameters: AssetApiGetAssetStatsRequest = {}, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 86742e468..f098bf4ff 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -23,11 +23,11 @@ doc/AssetBulkUploadCheckResponseDto.md doc/AssetBulkUploadCheckResult.md doc/AssetCountByTimeBucket.md doc/AssetCountByTimeBucketResponseDto.md -doc/AssetCountByUserIdResponseDto.md doc/AssetFileUploadResponseDto.md doc/AssetIdsDto.md doc/AssetIdsResponseDto.md doc/AssetResponseDto.md +doc/AssetStatsResponseDto.md doc/AssetTypeEnum.md doc/AudioCodec.md doc/AuthDeviceResponseDto.md @@ -163,11 +163,11 @@ lib/model/asset_bulk_upload_check_response_dto.dart lib/model/asset_bulk_upload_check_result.dart lib/model/asset_count_by_time_bucket.dart lib/model/asset_count_by_time_bucket_response_dto.dart -lib/model/asset_count_by_user_id_response_dto.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_ids_dto.dart lib/model/asset_ids_response_dto.dart lib/model/asset_response_dto.dart +lib/model/asset_stats_response_dto.dart lib/model/asset_type_enum.dart lib/model/audio_codec.dart lib/model/auth_device_response_dto.dart @@ -272,11 +272,11 @@ test/asset_bulk_upload_check_response_dto_test.dart test/asset_bulk_upload_check_result_test.dart test/asset_count_by_time_bucket_response_dto_test.dart test/asset_count_by_time_bucket_test.dart -test/asset_count_by_user_id_response_dto_test.dart test/asset_file_upload_response_dto_test.dart test/asset_ids_dto_test.dart test/asset_ids_response_dto_test.dart test/asset_response_dto_test.dart +test/asset_stats_response_dto_test.dart test/asset_type_enum_test.dart test/audio_codec_test.dart test/auth_device_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index ef92b6a0f3b9fc41eac1e3ffcc2e17661df3dad5..7f3e9db5e6dc612f402e2cb0fc788c755441a083 100644 GIT binary patch delta 92 zcmdno%h=t+xZ#q(<`BV1#>vXUd91-Di6zC8`Nbr-5{rvdONzlAV{vh6iF1BwUWrp>XmM(hXUgPgSlO lEtI$Ur_fV*G&Aaq#V6+)v9m$l=4d7|`KHm;%`c3Vg#e1qO}+pC diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 998836d09f6650227b8491db5b1e2e8732a4bd82..644907d1e53f54315d84e10850928be3464076be 100644 GIT binary patch delta 249 zcmdn8g?ZjG<_#>&n@gCL87Kc{iD3yYNi3OskWFNAKQk+9F_2R{xsg?v6U53aF3C*Z z?9R&SFnL1`%jT6nmn48%cqe~eBE0!Z&S^Fmkltb~EiO9+$KvAD5->L?wYVTZuQ=7E zBwrytwFDudky-3ml$?=SmYSlYkXh`OSe9RuS(3V$ajgo=WWg({lkeEeOm3;rmjN4t zL-pj3<^hxWqc~W>iY5yNwN37F(wZFSq%mod=;Q}ktWb#!$s)`^C6fykw`|@})RGPW D|D9iW delta 521 zcmbQYjCsoz<_#>&;zh|BnPsUdj>W~PCC>S!c_mJjp~b01lOM8)S|&mz6G4*6Ajzc4 zQlMmJil#!1f|i!MYlxPXf<9PUzqll^L?5bS^G_x{Ms76YCd)IkPQK5~Zh+GWuob#V zmAW7mx|u0JOSq!7v?dEC2~Rd+37p)S!@c836y}#?q!xJ)VPZ*sNn#Emb(3`&mjM8g97i7j diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 099f5615c5eae76172ecbf479a686222405a33a6..ef9544c856e4fc6ce0d778dc9ebb84b1401b68a1 100644 GIT binary patch delta 24 gcmX@0yH$5X3H#*L?29MA6;PX;$IiCdkE4hW0D*D|FaQ7m delta 33 pcmdn0dq8(X3HxMjc21Vk;?$za>p4^=FJNbzT*AJ1vol8#9{|Va3rPR~ diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 1c609a7277d05ec9717ebdc548150f4ac9609a30..c570229aaf19b0cf6d60f820df3704461c808ec2 100644 GIT binary patch delta 262 zcmccL#r(L5c|(HbW*Pe}qMK`?HnVaSmn4>C7MElu7f*gD;W>HcX3fbDHgQb8U@yxO z43wJOSSP{mSd^TRS(Z9kP+xR%5SJe}LXZ071Bu5bdm3gD=jH4O0{t;E>0~08CpC!F%+jb>vf5<%R1Ph$ zn()k$43CnM0?)j(d<}%c6e}dQ&g6;BlAFIY-(#G7e^vl1#Fhyh;!u{sQQgTKid8o2 ICm5sx0O|W@WB>pF delta 430 zcmaFd#C*Stc|(Hb=ei1;?xr7{L;JOy@7lg$a1TSNttK@QhVs?;q7I$bw21?08K`5ruMaIZx3qbULV z2AeexBEVoqRJ;a&l~n&*TF$*fyW@ Q|H(3WdMy8Drx?Xl05(RX-~a#s diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 5855da8e82cf741317a5751c8740a92f759caa0d..824d4c9eb45553ba79e27c7d71bd237e443cc1a9 100644 GIT binary patch delta 35 rcmZ44&p5N6aRaZy=4yomM&{s>#L4y2N|OT=StkECG1z=f@vXmM(hXUgP8X@$uX6j->CL?<&U>rO6EVBO5CkifY4 IwBk8c0GeVIKmY&$ diff --git a/mobile/openapi/lib/model/asset_count_by_user_id_response_dto.dart b/mobile/openapi/lib/model/asset_count_by_user_id_response_dto.dart deleted file mode 100644 index 0e2b1cefc457fe80d22fc6e5a08046c7ff36d3e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3829 zcmbVPZExE)5dQ98aRGwb!Bn~Hry;GpW=%4rYhoZx2Mk6aFg6{llSPlDV;E`v`|gE$ z@ynE~0b+~P9eH=pa}Vi$ztiu-$=93V>)+3=&aO{x&Q9U{;`3Pqr$e|J-oWMX^y2)l z12iMc4-;j5@;3hYZHI>0ePOIlr^cq!EZ4`dES64BQl0C$E__suv-!eUzt=|5;`h-3v9x&xNx1?X`8dxOMy8$EI*z6+X=-xM4QUv}JdpC6W&V!kPS?EG7vKas0bvol*IR_`sY5&nI3;)~ttJC&ka;@D0x7MfL zz+(rXkJY%=erXE_3JSe~QLED}WE}Wd$LQT^+N1RW{48^^T23+p3A~Bwxy8(Q;#?Q3fGW5o)yqtSF!ju6+^T0b*BiW-G%IejS*R4jpB9A_p2W>4zf@|IxlT&T2 z@&;o_dc(VKdW;pPn2sPBfFL+ld5=OD1PxaEABBhdFU~4-B$XJIO61xzoJm@ zYWLqW@ZfzsuEhEXHHj?p{5ocE0@Nmn7)2&Sckw^qA zOANiV27|JIK6)2bC|igQ+$1u<(edP_3qD&?RjfApo}#6w4g@nVNT% zITo=7gBMm$QDIq}?Q)DA?^4o&6t&uC;}m6mW+udFnP?ZlUpXKKH~=nzmS|&aJr0v3 z0;4PE0XBHRCh=G~zr%>0=w743t(5Lz;PS`JIfl7f{7I!7PH)x={T_RK&?ELmP?GED zKopzYggfnCfp1MWVN<(XU|`dYF>3b`N;aLCa_vq+(%K12NXb3%XW%K3q~d9S`g4W= zw3c@OIog~f2{$m8fCps5w(KDC?*2lfAu;_X1KZ@Lv_ncm2-HXrF*L!wHrX=tX-E-$27{cD^nx9 zB)$I$Ij9a`|fy> zEH{dqEr1=9y!Yq1BL{=tU;vkYOvZ128{LlXE+?Zaxc>Ng6vEXQZpRb28DD+8{_6zA zNbOE+kQ8|oo+CUr!H1+)pLn)d*2OxiM6>*rM1Fr ziFHV%m9)xwv4ATV)#_*P1MhPz&-Z;4`|W2{y>~|COAMBO1ip~{PDV`GcYIp0pvpf! zW#1uZ9G`lZv%YtABZ~KIIfVHZJ4vNOl>20Lj;@$u3zdruzTg(b@J#w?cfsIziK*$c z_x=opTb%qxh(p>7@%kGfALw4D0*qNtJ>)%-&W?Ed-+K#0K1UAGu(6=<^C+@XT38sp zt_)877PjL81^U#qQ!LpmguKjgvjB}e;Yq( zJyZ=#s)B!3SdPwQhmhgfHB#W9@|@k1A972hH{v0~*#HZXSsCu?(sSXBv}NX~S-L$; zXB;H~NOlQ73J3?8r*x$3t#+ez;kLD5#1w~WP}$cToYG!&e_o@`jEK7_6%n<4UYJ5v zE2`I?N++?eSKBUKGL%px=5r=oOOq>ISNx#vm6p?#TTmD69O_@*if`Qtl=d*{t{f4s z6Ph|+E)Yt!M^(42dT_N-zzB|l;L(a1q?bAh2T}FXIfv#^k_c<@`9sefz9!4aHMwP`QR9hWpGeW^4* z0XE95aEls?-b|f%yk%Kbt>!RZL#RV9NHV#fl{c*`itS`*U8eEcCQat%Trzks^rBOXuPehX4 fEwL=WD6?epLq6Hb6S&z}z={~z95+AYKF0w7Ces?P delta 183 zcmbQCv0ih-DfY<^xY=zTi;GiBobyZbN}MV~i&Kj{Q-V^93-a@dQ(a2(?G(~eOB{=m zGcwCkQ!rI(Om5_rlEN)Nxsgd{vH?Hq<_qk8OhTCYC&zHgutO{b8#Vbq7b`D{TP7Fs PnNL>cs@!bNeU}3O$>Bog diff --git a/mobile/openapi/test/asset_count_by_user_id_response_dto_test.dart b/mobile/openapi/test/asset_stats_response_dto_test.dart similarity index 50% rename from mobile/openapi/test/asset_count_by_user_id_response_dto_test.dart rename to mobile/openapi/test/asset_stats_response_dto_test.dart index 6d0b97b6edbecf61b7e722aa733b936f78ab68dd..3e5d8b548f58118ea0bfa5cecde0f741afe81f4a 100644 GIT binary patch delta 78 zcmZ3>@sD*w5+iGHNn%OyMOH9#vK+I~a`?C;V_34ED5tyq9ng0F$agz$tRgi0s913pa1{> diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index a3ce03df0..739d00186 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -984,38 +984,6 @@ ] } }, - "/asset/count-by-user-id": { - "get": { - "operationId": "getAssetCountByUserId", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AssetCountByUserIdResponseDto" - } - } - } - } - }, - "tags": [ - "Asset" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - } - }, "/asset/curated-locations": { "get": { "operationId": "getCuratedLocations", @@ -1608,17 +1576,34 @@ ] } }, - "/asset/stat/archive": { + "/asset/statistics": { "get": { - "operationId": "getArchivedAssetCountByUserId", - "parameters": [], + "operationId": "getAssetStats", + "parameters": [ + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isFavorite", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AssetCountByUserIdResponseDto" + "$ref": "#/components/schemas/AssetStatsResponseDto" } } } @@ -4786,38 +4771,6 @@ "buckets" ] }, - "AssetCountByUserIdResponseDto": { - "type": "object", - "properties": { - "audio": { - "type": "integer", - "default": 0 - }, - "photos": { - "type": "integer", - "default": 0 - }, - "videos": { - "type": "integer", - "default": 0 - }, - "other": { - "type": "integer", - "default": 0 - }, - "total": { - "type": "integer", - "default": 0 - } - }, - "required": [ - "audio", - "photos", - "videos", - "other", - "total" - ] - }, "AssetFileUploadResponseDto": { "type": "object", "properties": { @@ -4970,6 +4923,25 @@ "checksum" ] }, + "AssetStatsResponseDto": { + "type": "object", + "properties": { + "images": { + "type": "integer" + }, + "videos": { + "type": "integer" + }, + "total": { + "type": "integer" + } + }, + "required": [ + "images", + "videos", + "total" + ] + }, "AssetTypeEnum": { "type": "string", "enum": [ diff --git a/server/src/domain/asset/asset.repository.ts b/server/src/domain/asset/asset.repository.ts index 9bd9c687a..ae8f64e64 100644 --- a/server/src/domain/asset/asset.repository.ts +++ b/server/src/domain/asset/asset.repository.ts @@ -1,6 +1,13 @@ import { AssetEntity, AssetType } from '@app/infra/entities'; import { Paginated, PaginationOptions } from '../domain.util'; +export type AssetStats = Record; + +export interface AssetStatsOptions { + isFavorite?: boolean; + isArchived?: boolean; +} + export interface AssetSearchOptions { isVisible?: boolean; type?: AssetType; @@ -55,4 +62,5 @@ export interface IAssetRepository { save(asset: Partial): Promise; findLivePhotoMatch(options: LivePhotoSearchOptions): Promise; getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise; + getStatistics(ownerId: string, options: AssetStatsOptions): Promise; } diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index ef51c8831..986e7d0b3 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -1,3 +1,4 @@ +import { AssetType } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; import { assetEntityStub, @@ -10,9 +11,9 @@ import { import { when } from 'jest-when'; import { Readable } from 'stream'; import { IStorageRepository } from '../storage'; -import { IAssetRepository } from './asset.repository'; +import { AssetStats, IAssetRepository } from './asset.repository'; import { AssetService } from './asset.service'; -import { DownloadResponseDto } from './index'; +import { AssetStatsResponseDto, DownloadResponseDto } from './dto'; import { mapAsset } from './response-dto'; const downloadResponse: DownloadResponseDto = { @@ -25,6 +26,19 @@ const downloadResponse: DownloadResponseDto = { ], }; +const stats: AssetStats = { + [AssetType.IMAGE]: 10, + [AssetType.VIDEO]: 23, + [AssetType.AUDIO]: 0, + [AssetType.OTHER]: 0, +}; + +const statResponse: AssetStatsResponseDto = { + images: 10, + videos: 23, + total: 33, +}; + describe(AssetService.name, () => { let sut: AssetService; let accessMock: IAccessRepositoryMock; @@ -287,4 +301,30 @@ describe(AssetService.name, () => { }); }); }); + + describe('getStatistics', () => { + it('should get the statistics for a user, excluding archived assets', async () => { + assetMock.getStatistics.mockResolvedValue(stats); + await expect(sut.getStatistics(authStub.admin, { isArchived: false })).resolves.toEqual(statResponse); + expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isArchived: false }); + }); + + it('should get the statistics for a user for archived assets', async () => { + assetMock.getStatistics.mockResolvedValue(stats); + await expect(sut.getStatistics(authStub.admin, { isArchived: true })).resolves.toEqual(statResponse); + expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isArchived: true }); + }); + + it('should get the statistics for a user for favorite assets', async () => { + assetMock.getStatistics.mockResolvedValue(stats); + await expect(sut.getStatistics(authStub.admin, { isFavorite: true })).resolves.toEqual(statResponse); + expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, { isFavorite: true }); + }); + + it('should get the statistics for a user for all assets', async () => { + assetMock.getStatistics.mockResolvedValue(stats); + await expect(sut.getStatistics(authStub.admin, {})).resolves.toEqual(statResponse); + expect(assetMock.getStatistics).toHaveBeenCalledWith(authStub.admin.id, {}); + }); + }); }); diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index 5a84a4a35..90595de69 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -9,6 +9,7 @@ import { HumanReadableSize, usePagination } from '../domain.util'; import { ImmichReadStream, IStorageRepository } from '../storage'; import { IAssetRepository } from './asset.repository'; import { AssetIdsDto, DownloadArchiveInfo, DownloadDto, DownloadResponseDto, MemoryLaneDto } from './dto'; +import { AssetStatsDto, mapStats } from './dto/asset-statistics.dto'; import { MapMarkerDto } from './dto/map-marker.dto'; import { mapAsset, MapMarkerResponseDto } from './response-dto'; import { MemoryLaneResponseDto } from './response-dto/memory-lane-response.dto'; @@ -155,4 +156,9 @@ export class AssetService { throw new BadRequestException('assetIds, albumId, or userId is required'); } + + async getStatistics(authUser: AuthUserDto, dto: AssetStatsDto) { + const stats = await this.assetRepository.getStatistics(authUser.id, dto); + return mapStats(stats); + } } diff --git a/server/src/domain/asset/dto/asset-statistics.dto.ts b/server/src/domain/asset/dto/asset-statistics.dto.ts new file mode 100644 index 000000000..ef9c0606f --- /dev/null +++ b/server/src/domain/asset/dto/asset-statistics.dto.ts @@ -0,0 +1,37 @@ +import { AssetType } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsOptional } from 'class-validator'; +import { toBoolean } from '../../domain.util'; +import { AssetStats } from '../asset.repository'; + +export class AssetStatsDto { + @IsBoolean() + @Transform(toBoolean) + @IsOptional() + isArchived?: boolean; + + @IsBoolean() + @Transform(toBoolean) + @IsOptional() + isFavorite?: boolean; +} + +export class AssetStatsResponseDto { + @ApiProperty({ type: 'integer' }) + images!: number; + + @ApiProperty({ type: 'integer' }) + videos!: number; + + @ApiProperty({ type: 'integer' }) + total!: number; +} + +export const mapStats = (stats: AssetStats): AssetStatsResponseDto => { + return { + images: stats[AssetType.IMAGE], + videos: stats[AssetType.VIDEO], + total: Object.values(stats).reduce((total, value) => total + value, 0), + }; +}; diff --git a/server/src/domain/asset/dto/index.ts b/server/src/domain/asset/dto/index.ts index 9778a9122..f22534d35 100644 --- a/server/src/domain/asset/dto/index.ts +++ b/server/src/domain/asset/dto/index.ts @@ -1,4 +1,5 @@ export * from './asset-ids.dto'; +export * from './asset-statistics.dto'; export * from './download.dto'; export * from './map-marker.dto'; export * from './memory-lane.dto'; diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index 7b3dfea4d..22c25d6ef 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -1,4 +1,4 @@ -import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; +import { AssetEntity, ExifEntity } from '@app/infra/entities'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { IsNull, Not } from 'typeorm'; @@ -11,7 +11,6 @@ import { GetAssetCountByTimeBucketDto, TimeGroupEnum } from './dto/get-asset-cou import { SearchPropertiesDto } from './dto/search-properties.dto'; import { UpdateAssetDto } from './dto/update-asset.dto'; import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto'; -import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; @@ -38,8 +37,6 @@ export interface IAssetRepository { getDetectedObjectsByUserId(userId: string): Promise; getSearchPropertiesByUserId(userId: string): Promise; getAssetCountByTimeBucket(userId: string, dto: GetAssetCountByTimeBucketDto): Promise; - getAssetCountByUserId(userId: string): Promise; - getArchivedAssetCountByUserId(userId: string): Promise; getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise; getAssetsByChecksums(userId: string, checksums: Buffer[]): Promise; getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise; @@ -55,35 +52,6 @@ export class AssetRepository implements IAssetRepository { @InjectRepository(ExifEntity) private exifRepository: Repository, ) {} - async getAssetCountByUserId(ownerId: string): Promise { - // Get asset count by AssetType - const items = await this.assetRepository - .createQueryBuilder('asset') - .select(`COUNT(asset.id)`, 'count') - .addSelect(`asset.type`, 'type') - .where('"ownerId" = :ownerId', { ownerId: ownerId }) - .andWhere('asset.isVisible = true') - .groupBy('asset.type') - .getRawMany(); - - return this.getAssetCount(items); - } - - async getArchivedAssetCountByUserId(ownerId: string): Promise { - // Get archived asset count by AssetType - const items = await this.assetRepository - .createQueryBuilder('asset') - .select(`COUNT(asset.id)`, 'count') - .addSelect(`asset.type`, 'type') - .where('"ownerId" = :ownerId', { ownerId: ownerId }) - .andWhere('asset.isVisible = true') - .andWhere('asset.isArchived = true') - .groupBy('asset.type') - .getRawMany(); - - return this.getAssetCount(items); - } - async getAssetByTimeBucket(userId: string, dto: GetAssetByTimeBucketDto): Promise { // Get asset entity from a list of time buckets let builder = this.assetRepository @@ -337,29 +305,6 @@ export class AssetRepository implements IAssetRepository { return assets.map((asset) => asset.deviceAssetId); } - private getAssetCount(items: any): AssetCountByUserIdResponseDto { - const assetCountByUserId = new AssetCountByUserIdResponseDto(); - - // asset type to dto property mapping - const map: Record = { - [AssetType.AUDIO]: 'audio', - [AssetType.IMAGE]: 'photos', - [AssetType.VIDEO]: 'videos', - [AssetType.OTHER]: 'other', - }; - - for (const item of items) { - const count = Number(item.count) || 0; - const assetType = item.type as AssetType; - const type = map[assetType]; - - assetCountByUserId[type] = count; - assetCountByUserId.total += count; - } - - return assetCountByUserId; - } - getByOriginalPath(originalPath: string): Promise { return this.assetRepository.findOne({ select: { diff --git a/server/src/immich/api-v1/asset/asset.controller.ts b/server/src/immich/api-v1/asset/asset.controller.ts index b59c97199..1e22bf3ba 100644 --- a/server/src/immich/api-v1/asset/asset.controller.ts +++ b/server/src/immich/api-v1/asset/asset.controller.ts @@ -38,7 +38,6 @@ import { ServeFileDto } from './dto/serve-file.dto'; import { UpdateAssetDto } from './dto/update-asset.dto'; import { AssetBulkUploadCheckResponseDto } from './response-dto/asset-check-response.dto'; import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by-time-group-response.dto'; -import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; @@ -173,15 +172,6 @@ export class AssetController { return this.assetService.getAssetCountByTimeBucket(authUser, dto); } - @Get('/count-by-user-id') - getAssetCountByUserId(@AuthUser() authUser: AuthUserDto): Promise { - return this.assetService.getAssetCountByUserId(authUser); - } - - @Get('/stat/archive') - getArchivedAssetCountByUserId(@AuthUser() authUser: AuthUserDto): Promise { - return this.assetService.getArchivedAssetCountByUserId(authUser); - } /** * Get all AssetEntity belong to the user */ diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index 97725f149..110d63f50 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -26,7 +26,6 @@ import { CreateAssetDto } from './dto/create-asset.dto'; import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto'; import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto'; -import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; const _getCreateAssetDto = (): CreateAssetDto => { const createAssetDto = new CreateAssetDto(); @@ -103,24 +102,6 @@ const _getAssetCountByTimeBucket = (): AssetCountByTimeBucket[] => { return [result1, result2]; }; -const _getAssetCountByUserId = (): AssetCountByUserIdResponseDto => { - const result = new AssetCountByUserIdResponseDto(); - - result.videos = 2; - result.photos = 2; - - return result; -}; - -const _getArchivedAssetsCountByUserId = (): AssetCountByUserIdResponseDto => { - const result = new AssetCountByUserIdResponseDto(); - - result.videos = 1; - result.photos = 2; - - return result; -}; - const uploadFile = { nullAuth: { authUser: null, @@ -197,8 +178,6 @@ describe('AssetService', () => { getSearchPropertiesByUserId: jest.fn(), getAssetByTimeBucket: jest.fn(), getAssetsByChecksums: jest.fn(), - getAssetCountByUserId: jest.fn(), - getArchivedAssetCountByUserId: jest.fn(), getExistingAssets: jest.fn(), getByOriginalPath: jest.fn(), }; @@ -467,20 +446,6 @@ describe('AssetService', () => { expect(result.buckets.length).toEqual(2); }); - it('get asset count by user id', async () => { - const assetCount = _getAssetCountByUserId(); - assetRepositoryMock.getAssetCountByUserId.mockResolvedValue(assetCount); - - await expect(sut.getAssetCountByUserId(authStub.user1)).resolves.toEqual(assetCount); - }); - - it('get archived asset count by user id', async () => { - const assetCount = _getArchivedAssetsCountByUserId(); - assetRepositoryMock.getArchivedAssetCountByUserId.mockResolvedValue(assetCount); - - await expect(sut.getArchivedAssetCountByUserId(authStub.user1)).resolves.toEqual(assetCount); - }); - describe('deleteAll', () => { it('should return failed status when an asset is missing', async () => { assetRepositoryMock.get.mockResolvedValue(null); diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index c08a24fe0..1aeac5ac0 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -58,7 +58,6 @@ import { AssetCountByTimeBucketResponseDto, mapAssetCountByTimeBucket, } from './response-dto/asset-count-by-time-group-response.dto'; -import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto'; import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; @@ -536,14 +535,6 @@ export class AssetService { return mapAssetCountByTimeBucket(result); } - getAssetCountByUserId(authUser: AuthUserDto): Promise { - return this._assetRepository.getAssetCountByUserId(authUser.id); - } - - getArchivedAssetCountByUserId(authUser: AuthUserDto): Promise { - return this._assetRepository.getArchivedAssetCountByUserId(authUser.id); - } - getExifPermission(authUser: AuthUserDto) { return !authUser.isPublicUser || authUser.isShowExif; } diff --git a/server/src/immich/api-v1/asset/response-dto/asset-count-by-user-id-response.dto.ts b/server/src/immich/api-v1/asset/response-dto/asset-count-by-user-id-response.dto.ts deleted file mode 100644 index cbee0eed5..000000000 --- a/server/src/immich/api-v1/asset/response-dto/asset-count-by-user-id-response.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class AssetCountByUserIdResponseDto { - @ApiProperty({ type: 'integer' }) - audio = 0; - - @ApiProperty({ type: 'integer' }) - photos = 0; - - @ApiProperty({ type: 'integer' }) - videos = 0; - - @ApiProperty({ type: 'integer' }) - other = 0; - - @ApiProperty({ type: 'integer' }) - total = 0; -} diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index 28e23c98e..5c4e19ccf 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -1,6 +1,8 @@ import { AssetIdsDto, AssetService, + AssetStatsDto, + AssetStatsResponseDto, AuthUserDto, DownloadDto, DownloadResponseDto, @@ -53,4 +55,9 @@ export class AssetController { downloadFile(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) { return this.service.downloadFile(authUser, id).then(asStreamableFile); } + + @Get('statistics') + getAssetStats(@AuthUser() authUser: AuthUserDto, @Query() dto: AssetStatsDto): Promise { + return this.service.getStatistics(authUser, dto); + } } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index a23787252..09fb3a17e 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -1,5 +1,7 @@ import { AssetSearchOptions, + AssetStats, + AssetStatsOptions, IAssetRepository, LivePhotoSearchOptions, MapMarker, @@ -321,4 +323,38 @@ export class AssetRepository implements IAssetRepository { lon: asset.exifInfo!.longitude!, })); } + + async getStatistics(ownerId: string, options: AssetStatsOptions): Promise { + let builder = await this.repository + .createQueryBuilder('asset') + .select(`COUNT(asset.id)`, 'count') + .addSelect(`asset.type`, 'type') + .where('"ownerId" = :ownerId', { ownerId }) + .andWhere('asset.isVisible = true') + .groupBy('asset.type'); + + const { isArchived, isFavorite } = options; + if (isArchived !== undefined) { + builder = builder.andWhere(`asset.isArchived = :isArchived`, { isArchived }); + } + + if (isFavorite !== undefined) { + builder = builder.andWhere(`asset.isFavorite = :isFavorite`, { isFavorite }); + } + + const items = await builder.getRawMany(); + + const result: AssetStats = { + [AssetType.AUDIO]: 0, + [AssetType.IMAGE]: 0, + [AssetType.VIDEO]: 0, + [AssetType.OTHER]: 0, + }; + + for (const item of items) { + result[item.type as AssetType] = Number(item.count) || 0; + } + + return result; + } } diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 7e8a52262..5eb69a9e5 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -18,5 +18,6 @@ export const newAssetRepositoryMock = (): jest.Mocked => { save: jest.fn(), findLivePhotoMatch: jest.fn(), getMapMarkers: jest.fn(), + getStatistics: jest.fn(), }; }; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 484dc4e4d..67434662d 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -486,43 +486,6 @@ export interface AssetCountByTimeBucketResponseDto { */ 'buckets': Array; } -/** - * - * @export - * @interface AssetCountByUserIdResponseDto - */ -export interface AssetCountByUserIdResponseDto { - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'audio': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'photos': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'videos': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'other': number; - /** - * - * @type {number} - * @memberof AssetCountByUserIdResponseDto - */ - 'total': number; -} /** * * @export @@ -724,6 +687,31 @@ export interface AssetResponseDto { } +/** + * + * @export + * @interface AssetStatsResponseDto + */ +export interface AssetStatsResponseDto { + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'images': number; + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'videos': number; + /** + * + * @type {number} + * @memberof AssetStatsResponseDto + */ + 'total': number; +} /** * * @export @@ -4901,44 +4889,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getArchivedAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/stat/archive`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -5088,8 +5038,8 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/count-by-user-id`; + getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/asset/search-terms`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -5123,11 +5073,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration }, /** * + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetSearchTerms: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/asset/search-terms`; + getAssetStats: async (isArchived?: boolean, isFavorite?: boolean, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/asset/statistics`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -5148,6 +5100,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -5896,15 +5856,6 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async getArchivedAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getArchivedAssetCountByUserId(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * Get a single asset\'s information * @param {string} id @@ -5941,17 +5892,19 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByUserId(options); + async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** * + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options); + async getAssetStats(isArchived?: boolean, isFavorite?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetStats(isArchived, isFavorite, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -6177,14 +6130,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getAllAssets(userId?: string, isFavorite?: boolean, isArchived?: boolean, withoutThumbs?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise> { return localVarFp.getAllAssets(userId, isFavorite, isArchived, withoutThumbs, skip, ifNoneMatch, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - getArchivedAssetCountByUserId(options?: any): AxiosPromise { - return localVarFp.getArchivedAssetCountByUserId(options).then((request) => request(axios, basePath)); - }, /** * Get a single asset\'s information * @param {string} id @@ -6218,16 +6163,18 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetCountByUserId(options?: any): AxiosPromise { - return localVarFp.getAssetCountByUserId(options).then((request) => request(axios, basePath)); + getAssetSearchTerms(options?: any): AxiosPromise> { + return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath)); }, /** * + * @param {boolean} [isArchived] + * @param {boolean} [isFavorite] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAssetSearchTerms(options?: any): AxiosPromise> { - return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath)); + getAssetStats(isArchived?: boolean, isFavorite?: boolean, options?: any): AxiosPromise { + return localVarFp.getAssetStats(isArchived, isFavorite, options).then((request) => request(axios, basePath)); }, /** * @@ -6565,6 +6512,27 @@ export interface AssetApiGetAssetCountByTimeBucketRequest { readonly getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto } +/** + * Request parameters for getAssetStats operation in AssetApi. + * @export + * @interface AssetApiGetAssetStatsRequest + */ +export interface AssetApiGetAssetStatsRequest { + /** + * + * @type {boolean} + * @memberof AssetApiGetAssetStats + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof AssetApiGetAssetStats + */ + readonly isFavorite?: boolean +} + /** * Request parameters for getAssetThumbnail operation in AssetApi. * @export @@ -6957,16 +6925,6 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getAllAssets(requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.withoutThumbs, requestParameters.skip, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public getArchivedAssetCountByUserId(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getArchivedAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); - } - /** * Get a single asset\'s information * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. @@ -7006,18 +6964,19 @@ export class AssetApi extends BaseAPI { * @throws {RequiredError} * @memberof AssetApi */ - public getAssetCountByUserId(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); + public getAssetSearchTerms(options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetSearchTerms(options).then((request) => request(this.axios, this.basePath)); } /** * + * @param {AssetApiGetAssetStatsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public getAssetSearchTerms(options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAssetSearchTerms(options).then((request) => request(this.axios, this.basePath)); + public getAssetStats(requestParameters: AssetApiGetAssetStatsRequest = {}, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAssetStats(requestParameters.isArchived, requestParameters.isFavorite, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index 3d985661a..9cc966b46 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -1,6 +1,6 @@