diff --git a/e2e/src/specs/server/api/integrity.e2e-spec.ts b/e2e/src/specs/server/api/integrity.e2e-spec.ts index e28f64f72d..8b2b4364ae 100644 --- a/e2e/src/specs/server/api/integrity.e2e-spec.ts +++ b/e2e/src/specs/server/api/integrity.e2e-spec.ts @@ -50,7 +50,7 @@ describe('/admin/integrity', () => { command: QueueCommand.Pause, }); } - + asset = await utils.createAsset(admin.accessToken, { assetData: { filename: 'asset.jpg', @@ -397,9 +397,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status, body } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=untracked_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'untracked_file' }); + .send(); expect(status).toBe(200); expect(body).toEqual({ @@ -427,9 +427,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status, body } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=missing_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'missing_file' }); + .send(); expect(status).toBe(200); expect(body).toEqual({ @@ -457,9 +457,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status, body } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=checksum_mismatch') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'checksum_mismatch' }); + .send(); expect(status).toBe(200); expect(body).toEqual({ @@ -489,9 +489,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus, body: listBody } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=untracked_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'untracked_file' }); + .send(); expect(listStatus).toBe(200); @@ -513,9 +513,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus2, body: listBody2 } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=untracked_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'untracked_file' }); + .send(); expect(listStatus2).toBe(200); expect(listBody2).not.toBe( @@ -539,9 +539,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus, body: listBody } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=missing_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'missing_file' }); + .send(); expect(listStatus).toBe(200); expect(listBody.items.length).toBe(1); @@ -562,9 +562,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus2, body: listBody2 } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=missing_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'missing_file' }); + .send(); expect(listStatus2).toBe(200); expect(listBody2.items.length).toBe(0); @@ -580,9 +580,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus, body: listBody } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=checksum_mismatch') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'checksum_mismatch' }); + .send(); expect(listStatus).toBe(200); expect(listBody.items.length).toBe(1); @@ -603,9 +603,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { status: listStatus2, body: listBody2 } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=checksum_mismatch') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'checksum_mismatch' }); + .send(); expect(listStatus2).toBe(200); expect(listBody2.items.length).toBe(0); @@ -647,9 +647,9 @@ describe('/admin/integrity', () => { await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); const { body: listBody } = await request(app) - .post('/admin/integrity/report') + .get('/admin/integrity/report?type=untracked_file') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ type: 'untracked_file' }); + .send(); const report = (listBody as IntegrityReportResponseDto).items.find( (item) => item.path === `/data/upload/${admin.userId}/untracked1.png`, diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index f170c706a0..9cc220f498 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -173,7 +173,7 @@ Class | Method | HTTP request | Description *LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings *MaintenanceAdminApi* | [**deleteIntegrityReport**](doc//MaintenanceAdminApi.md#deleteintegrityreport) | **DELETE** /admin/integrity/report/{id} | Delete integrity report item *MaintenanceAdminApi* | [**detectPriorInstall**](doc//MaintenanceAdminApi.md#detectpriorinstall) | **GET** /admin/maintenance/detect-install | Detect existing install -*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **POST** /admin/integrity/report | Get integrity report by type +*MaintenanceAdminApi* | [**getIntegrityReport**](doc//MaintenanceAdminApi.md#getintegrityreport) | **GET** /admin/integrity/report | Get integrity report by type *MaintenanceAdminApi* | [**getIntegrityReportCsv**](doc//MaintenanceAdminApi.md#getintegrityreportcsv) | **GET** /admin/integrity/report/{type}/csv | Export integrity report by type as CSV *MaintenanceAdminApi* | [**getIntegrityReportFile**](doc//MaintenanceAdminApi.md#getintegrityreportfile) | **GET** /admin/integrity/report/{id}/file | Download flagged file *MaintenanceAdminApi* | [**getIntegrityReportSummary**](doc//MaintenanceAdminApi.md#getintegrityreportsummary) | **GET** /admin/integrity/summary | Get integrity report summary @@ -435,7 +435,6 @@ Class | Method | HTTP request | Description - [FoldersResponse](doc//FoldersResponse.md) - [FoldersUpdate](doc//FoldersUpdate.md) - [ImageFormat](doc//ImageFormat.md) - - [IntegrityGetReportDto](doc//IntegrityGetReportDto.md) - [IntegrityReportDto](doc//IntegrityReportDto.md) - [IntegrityReportResponseDto](doc//IntegrityReportResponseDto.md) - [IntegrityReportSummaryResponseDto](doc//IntegrityReportSummaryResponseDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index e29264478b..7a6a6e54d1 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -170,7 +170,6 @@ part 'model/facial_recognition_config.dart'; part 'model/folders_response.dart'; part 'model/folders_update.dart'; part 'model/image_format.dart'; -part 'model/integrity_get_report_dto.dart'; part 'model/integrity_report_dto.dart'; part 'model/integrity_report_response_dto.dart'; part 'model/integrity_report_summary_response_dto.dart'; diff --git a/mobile/openapi/lib/api/maintenance_admin_api.dart b/mobile/openapi/lib/api/maintenance_admin_api.dart index b662dd9194..95bc294697 100644 --- a/mobile/openapi/lib/api/maintenance_admin_api.dart +++ b/mobile/openapi/lib/api/maintenance_admin_api.dart @@ -121,24 +121,38 @@ class MaintenanceAdminApi { /// /// Parameters: /// - /// * [IntegrityGetReportDto] integrityGetReportDto (required): - Future getIntegrityReportWithHttpInfo(IntegrityGetReportDto integrityGetReportDto,) async { + /// * [IntegrityReportType] type (required): + /// + /// * [String] cursor: + /// Cursor for pagination + /// + /// * [num] limit: + /// Number of items per page + Future getIntegrityReportWithHttpInfo(IntegrityReportType type, { String? cursor, num? limit, }) async { // ignore: prefer_const_declarations final apiPath = r'/admin/integrity/report'; // ignore: prefer_final_locals - Object? postBody = integrityGetReportDto; + Object? postBody; final queryParams = []; final headerParams = {}; final formParams = {}; - const contentTypes = ['application/json']; + if (cursor != null) { + queryParams.addAll(_queryParams('', 'cursor', cursor)); + } + if (limit != null) { + queryParams.addAll(_queryParams('', 'limit', limit)); + } + queryParams.addAll(_queryParams('', 'type', type)); + + const contentTypes = []; return apiClient.invokeAPI( apiPath, - 'POST', + 'GET', queryParams, postBody, headerParams, @@ -153,9 +167,15 @@ class MaintenanceAdminApi { /// /// Parameters: /// - /// * [IntegrityGetReportDto] integrityGetReportDto (required): - Future getIntegrityReport(IntegrityGetReportDto integrityGetReportDto,) async { - final response = await getIntegrityReportWithHttpInfo(integrityGetReportDto,); + /// * [IntegrityReportType] type (required): + /// + /// * [String] cursor: + /// Cursor for pagination + /// + /// * [num] limit: + /// Number of items per page + Future getIntegrityReport(IntegrityReportType type, { String? cursor, num? limit, }) async { + final response = await getIntegrityReportWithHttpInfo(type, cursor: cursor, limit: limit, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 9e543373cb..58e491cfc4 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -386,8 +386,6 @@ class ApiClient { return FoldersUpdate.fromJson(value); case 'ImageFormat': return ImageFormatTypeTransformer().decode(value); - case 'IntegrityGetReportDto': - return IntegrityGetReportDto.fromJson(value); case 'IntegrityReportDto': return IntegrityReportDto.fromJson(value); case 'IntegrityReportResponseDto': diff --git a/mobile/openapi/lib/model/integrity_get_report_dto.dart b/mobile/openapi/lib/model/integrity_get_report_dto.dart deleted file mode 100644 index 156647a1f2..0000000000 --- a/mobile/openapi/lib/model/integrity_get_report_dto.dart +++ /dev/null @@ -1,134 +0,0 @@ -// -// AUTO-GENERATED FILE, DO NOT MODIFY! -// -// @dart=2.18 - -// 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 IntegrityGetReportDto { - /// Returns a new [IntegrityGetReportDto] instance. - IntegrityGetReportDto({ - this.cursor, - this.limit, - required this.type, - }); - - /// - /// 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. - /// - String? cursor; - - /// Minimum value: 1 - /// - /// 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. - /// - num? limit; - - IntegrityReportType type; - - @override - bool operator ==(Object other) => identical(this, other) || other is IntegrityGetReportDto && - other.cursor == cursor && - other.limit == limit && - other.type == type; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (cursor == null ? 0 : cursor!.hashCode) + - (limit == null ? 0 : limit!.hashCode) + - (type.hashCode); - - @override - String toString() => 'IntegrityGetReportDto[cursor=$cursor, limit=$limit, type=$type]'; - - Map toJson() { - final json = {}; - if (this.cursor != null) { - json[r'cursor'] = this.cursor; - } else { - // json[r'cursor'] = null; - } - if (this.limit != null) { - json[r'limit'] = this.limit; - } else { - // json[r'limit'] = null; - } - json[r'type'] = this.type; - return json; - } - - /// Returns a new [IntegrityGetReportDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static IntegrityGetReportDto? fromJson(dynamic value) { - upgradeDto(value, "IntegrityGetReportDto"); - if (value is Map) { - final json = value.cast(); - - return IntegrityGetReportDto( - cursor: mapValueOfType(json, r'cursor'), - limit: num.parse('${json[r'limit']}'), - type: IntegrityReportType.fromJson(json[r'type'])!, - ); - } - return null; - } - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = IntegrityGetReportDto.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = IntegrityGetReportDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of IntegrityGetReportDto-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = IntegrityGetReportDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'type', - }; -} - diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index a47c69a121..9aaec0ccca 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -561,20 +561,41 @@ } }, "/admin/integrity/report": { - "post": { + "get": { "description": "Get all flagged items by integrity report type", "operationId": "getIntegrityReport", - "parameters": [], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/IntegrityGetReportDto" - } + "parameters": [ + { + "name": "cursor", + "required": false, + "in": "query", + "description": "Cursor for pagination", + "schema": { + "format": "uuid", + "default": 1, + "type": "string" } }, - "required": true - }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of items per page", + "schema": { + "minimum": 1, + "default": 500, + "type": "number" + } + }, + { + "name": "type", + "required": true, + "in": "query", + "schema": { + "$ref": "#/components/schemas/IntegrityReportType" + } + } + ], "responses": { "200": { "content": { @@ -18274,29 +18295,6 @@ ], "type": "string" }, - "IntegrityGetReportDto": { - "properties": { - "cursor": { - "format": "uuid", - "type": "string" - }, - "limit": { - "minimum": 1, - "type": "number" - }, - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/IntegrityReportType" - } - ] - } - }, - "required": [ - "type" - ], - "type": "object" - }, "IntegrityReportDto": { "properties": { "id": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 0e6c3d1c22..4b78c17adc 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -70,11 +70,6 @@ export type DatabaseBackupListResponseDto = { export type DatabaseBackupUploadDto = { file?: Blob; }; -export type IntegrityGetReportDto = { - cursor?: string; - limit?: number; - "type": IntegrityReportType; -}; export type IntegrityReportDto = { id: string; path: string; @@ -3413,17 +3408,21 @@ export function downloadDatabaseBackup({ filename }: { /** * Get integrity report by type */ -export function getIntegrityReport({ integrityGetReportDto }: { - integrityGetReportDto: IntegrityGetReportDto; +export function getIntegrityReport({ cursor, limit, $type }: { + cursor?: string; + limit?: number; + $type: IntegrityReportType; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; data: IntegrityReportResponseDto; - }>("/admin/integrity/report", oazapfts.json({ - ...opts, - method: "POST", - body: integrityGetReportDto - }))); + }>(`/admin/integrity/report${QS.query(QS.explode({ + cursor, + limit, + "type": $type + }))}`, { + ...opts + })); } /** * Delete integrity report item diff --git a/server/src/controllers/integrity.controller.ts b/server/src/controllers/integrity.controller.ts index f6825282b6..214bf86b70 100644 --- a/server/src/controllers/integrity.controller.ts +++ b/server/src/controllers/integrity.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Next, Param, Post, Res } from '@nestjs/common'; +import { Controller, Delete, Get, HttpCode, HttpStatus, Next, Param, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; @@ -34,7 +34,7 @@ export class IntegrityController { return this.service.getIntegrityReportSummary(); } - @Post('report') + @Get('report') @HttpCode(HttpStatus.OK) @Endpoint({ summary: 'Get integrity report by type', @@ -42,7 +42,7 @@ export class IntegrityController { history: new HistoryBuilder().added('v2.6.0').alpha('v2.6.0'), }) @Authenticated({ permission: Permission.Maintenance, admin: true }) - getIntegrityReport(@Body() dto: IntegrityGetReportDto): Promise { + getIntegrityReport(@Query() dto: IntegrityGetReportDto): Promise { return this.service.getIntegrityReport(dto); } diff --git a/server/src/dtos/integrity.dto.ts b/server/src/dtos/integrity.dto.ts index 7425a4d727..b7636f2543 100644 --- a/server/src/dtos/integrity.dto.ts +++ b/server/src/dtos/integrity.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsOptional, IsUUID, Min } from 'class-validator'; import { IntegrityReportType } from 'src/enum'; @@ -17,10 +17,12 @@ export class IntegrityGetReportDto { @ValidateEnum({ enum: IntegrityReportType, name: 'IntegrityReportType' }) type!: IntegrityReportType; + @ApiPropertyOptional({ description: 'Cursor for pagination', default: 1 }) @IsOptional() @IsUUID() cursor?: string; + @ApiPropertyOptional({ description: 'Number of items per page', default: 500 }) @IsInt() @Min(1) @IsOptional() diff --git a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte index 5a3e7f04c9..3fbfcb6f87 100644 --- a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte +++ b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte @@ -17,14 +17,13 @@ let { data }: Props = $props(); + // svelte-ignore state_referenced_locally let integrityReport = $state(data.integrityReport); async function loadMore() { const { items, nextCursor } = await getIntegrityReport({ - integrityGetReportDto: { - type: data.type, - cursor: integrityReport.nextCursor, - }, + $type: data.type, + cursor: integrityReport.nextCursor, }); integrityReport.items.push(...items); @@ -41,9 +40,7 @@ expectingUpdate = true; } else if (expectingUpdate) { integrityReport = await getIntegrityReport({ - integrityGetReportDto: { - type: data.type, - }, + $type: data.type, }); expectingUpdate = false; } diff --git a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts index 306cc1ac56..d70c09bd5a 100644 --- a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts +++ b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.ts @@ -8,9 +8,7 @@ export const load = (async ({ params, url }) => { await authenticate(url, { admin: true }); const integrityReport = await getIntegrityReport({ - integrityGetReportDto: { - type, - }, + $type: type, }); const $t = await getFormatter();