diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index 0f6fe8a100..c72a4bf1bc 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -23,7 +23,6 @@ class ApiService implements Authentication { late MapApi mapApi; late PartnersApi partnersApi; late PeopleApi peopleApi; - late AuditApi auditApi; late SharedLinksApi sharedLinksApi; late SyncApi syncApi; late SystemConfigApi systemConfigApi; @@ -56,7 +55,6 @@ class ApiService implements Authentication { mapApi = MapApi(_apiClient); partnersApi = PartnersApi(_apiClient); peopleApi = PeopleApi(_apiClient); - auditApi = AuditApi(_apiClient); sharedLinksApi = SharedLinksApi(_apiClient); syncApi = SyncApi(_apiClient); systemConfigApi = SystemConfigApi(_apiClient); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 5b0e067dcd..d006ef38bb 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -109,7 +109,6 @@ Class | Method | HTTP request | Description *AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | *AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | *AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | -*AuditApi* | [**getAuditDeletes**](doc//AuditApi.md#getauditdeletes) | **GET** /audit/deletes | *AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | *AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | *AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout | @@ -293,7 +292,6 @@ Class | Method | HTTP request | Description - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) - [AssetTypeEnum](doc//AssetTypeEnum.md) - [AudioCodec](doc//AudioCodec.md) - - [AuditDeletesResponseDto](doc//AuditDeletesResponseDto.md) - [AvatarResponse](doc//AvatarResponse.md) - [AvatarUpdate](doc//AvatarUpdate.md) - [BulkIdResponseDto](doc//BulkIdResponseDto.md) @@ -317,7 +315,6 @@ Class | Method | HTTP request | Description - [DuplicateResponseDto](doc//DuplicateResponseDto.md) - [EmailNotificationsResponse](doc//EmailNotificationsResponse.md) - [EmailNotificationsUpdate](doc//EmailNotificationsUpdate.md) - - [EntityType](doc//EntityType.md) - [ExifResponseDto](doc//ExifResponseDto.md) - [FaceDto](doc//FaceDto.md) - [FacialRecognitionConfig](doc//FacialRecognitionConfig.md) diff --git a/mobile/openapi/devtools_options.yaml b/mobile/openapi/devtools_options.yaml deleted file mode 100644 index fa0b357c4f..0000000000 --- a/mobile/openapi/devtools_options.yaml +++ /dev/null @@ -1,3 +0,0 @@ -description: This file stores settings for Dart & Flutter DevTools. -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states -extensions: diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 3455cdb4fd..2a2b6d46a4 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -34,7 +34,6 @@ part 'api/api_keys_api.dart'; part 'api/activities_api.dart'; part 'api/albums_api.dart'; part 'api/assets_api.dart'; -part 'api/audit_api.dart'; part 'api/authentication_api.dart'; part 'api/deprecated_api.dart'; part 'api/download_api.dart'; @@ -106,7 +105,6 @@ part 'model/asset_stack_response_dto.dart'; part 'model/asset_stats_response_dto.dart'; part 'model/asset_type_enum.dart'; part 'model/audio_codec.dart'; -part 'model/audit_deletes_response_dto.dart'; part 'model/avatar_response.dart'; part 'model/avatar_update.dart'; part 'model/bulk_id_response_dto.dart'; @@ -130,7 +128,6 @@ part 'model/duplicate_detection_config.dart'; part 'model/duplicate_response_dto.dart'; part 'model/email_notifications_response.dart'; part 'model/email_notifications_update.dart'; -part 'model/entity_type.dart'; part 'model/exif_response_dto.dart'; part 'model/face_dto.dart'; part 'model/facial_recognition_config.dart'; diff --git a/mobile/openapi/lib/api/audit_api.dart b/mobile/openapi/lib/api/audit_api.dart deleted file mode 100644 index f6d71eafdb..0000000000 --- a/mobile/openapi/lib/api/audit_api.dart +++ /dev/null @@ -1,79 +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 AuditApi { - AuditApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; - - final ApiClient apiClient; - - /// Performs an HTTP 'GET /audit/deletes' operation and returns the [Response]. - /// Parameters: - /// - /// * [DateTime] after (required): - /// - /// * [EntityType] entityType (required): - /// - /// * [String] userId: - Future getAuditDeletesWithHttpInfo(DateTime after, EntityType entityType, { String? userId, }) async { - // ignore: prefer_const_declarations - final path = r'/audit/deletes'; - - // ignore: prefer_final_locals - Object? postBody; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - queryParams.addAll(_queryParams('', 'after', after)); - queryParams.addAll(_queryParams('', 'entityType', entityType)); - if (userId != null) { - queryParams.addAll(_queryParams('', 'userId', userId)); - } - - const contentTypes = []; - - - return apiClient.invokeAPI( - path, - 'GET', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - /// Parameters: - /// - /// * [DateTime] after (required): - /// - /// * [EntityType] entityType (required): - /// - /// * [String] userId: - Future getAuditDeletes(DateTime after, EntityType entityType, { String? userId, }) async { - final response = await getAuditDeletesWithHttpInfo(after, entityType, userId: userId, ); - 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), 'AuditDeletesResponseDto',) as AuditDeletesResponseDto; - - } - return null; - } -} diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 3721652b8b..49fbe9464b 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -266,8 +266,6 @@ class ApiClient { return AssetTypeEnumTypeTransformer().decode(value); case 'AudioCodec': return AudioCodecTypeTransformer().decode(value); - case 'AuditDeletesResponseDto': - return AuditDeletesResponseDto.fromJson(value); case 'AvatarResponse': return AvatarResponse.fromJson(value); case 'AvatarUpdate': @@ -314,8 +312,6 @@ class ApiClient { return EmailNotificationsResponse.fromJson(value); case 'EmailNotificationsUpdate': return EmailNotificationsUpdate.fromJson(value); - case 'EntityType': - return EntityTypeTypeTransformer().decode(value); case 'ExifResponseDto': return ExifResponseDto.fromJson(value); case 'FaceDto': diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index b7c6ad5e01..6a917201aa 100644 --- a/mobile/openapi/lib/api_helper.dart +++ b/mobile/openapi/lib/api_helper.dart @@ -82,9 +82,6 @@ String parameterToString(dynamic value) { if (value is Colorspace) { return ColorspaceTypeTransformer().encode(value).toString(); } - if (value is EntityType) { - return EntityTypeTypeTransformer().encode(value).toString(); - } if (value is ImageFormat) { return ImageFormatTypeTransformer().encode(value).toString(); } diff --git a/mobile/openapi/lib/model/audit_deletes_response_dto.dart b/mobile/openapi/lib/model/audit_deletes_response_dto.dart deleted file mode 100644 index 6b1df74eb4..0000000000 --- a/mobile/openapi/lib/model/audit_deletes_response_dto.dart +++ /dev/null @@ -1,109 +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 AuditDeletesResponseDto { - /// Returns a new [AuditDeletesResponseDto] instance. - AuditDeletesResponseDto({ - this.ids = const [], - required this.needsFullSync, - }); - - List ids; - - bool needsFullSync; - - @override - bool operator ==(Object other) => identical(this, other) || other is AuditDeletesResponseDto && - _deepEquality.equals(other.ids, ids) && - other.needsFullSync == needsFullSync; - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - (ids.hashCode) + - (needsFullSync.hashCode); - - @override - String toString() => 'AuditDeletesResponseDto[ids=$ids, needsFullSync=$needsFullSync]'; - - Map toJson() { - final json = {}; - json[r'ids'] = this.ids; - json[r'needsFullSync'] = this.needsFullSync; - return json; - } - - /// Returns a new [AuditDeletesResponseDto] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static AuditDeletesResponseDto? fromJson(dynamic value) { - upgradeDto(value, "AuditDeletesResponseDto"); - if (value is Map) { - final json = value.cast(); - - return AuditDeletesResponseDto( - ids: json[r'ids'] is Iterable - ? (json[r'ids'] as Iterable).cast().toList(growable: false) - : const [], - needsFullSync: mapValueOfType(json, r'needsFullSync')!, - ); - } - 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 = AuditDeletesResponseDto.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 = AuditDeletesResponseDto.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of AuditDeletesResponseDto-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] = AuditDeletesResponseDto.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { - 'ids', - 'needsFullSync', - }; -} - diff --git a/mobile/openapi/lib/model/entity_type.dart b/mobile/openapi/lib/model/entity_type.dart deleted file mode 100644 index 93a0d0d3cc..0000000000 --- a/mobile/openapi/lib/model/entity_type.dart +++ /dev/null @@ -1,85 +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 EntityType { - /// Instantiate a new enum with the provided [value]. - const EntityType._(this.value); - - /// The underlying value of this enum member. - final String value; - - @override - String toString() => value; - - String toJson() => value; - - static const ASSET = EntityType._(r'ASSET'); - static const ALBUM = EntityType._(r'ALBUM'); - - /// List of all possible values in this [enum][EntityType]. - static const values = [ - ASSET, - ALBUM, - ]; - - static EntityType? fromJson(dynamic value) => EntityTypeTypeTransformer().decode(value); - - static List listFromJson(dynamic json, {bool growable = false,}) { - final result = []; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = EntityType.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } -} - -/// Transformation class that can [encode] an instance of [EntityType] to String, -/// and [decode] dynamic data back to [EntityType]. -class EntityTypeTypeTransformer { - factory EntityTypeTypeTransformer() => _instance ??= const EntityTypeTypeTransformer._(); - - const EntityTypeTypeTransformer._(); - - String encode(EntityType data) => data.value; - - /// Decodes a [dynamic value][data] to a EntityType. - /// - /// 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. - EntityType? decode(dynamic data, {bool allowNull = true}) { - if (data != null) { - switch (data) { - case r'ASSET': return EntityType.ASSET; - case r'ALBUM': return EntityType.ALBUM; - default: - if (!allowNull) { - throw ArgumentError('Unknown enum value to decode: $data'); - } - } - } - return null; - } - - /// Singleton [EntityTypeTypeTransformer] instance. - static EntityTypeTypeTransformer? _instance; -} - diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 25d649e195..5b5c3a1503 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -2079,65 +2079,6 @@ ] } }, - "/audit/deletes": { - "get": { - "operationId": "getAuditDeletes", - "parameters": [ - { - "name": "after", - "required": true, - "in": "query", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "name": "entityType", - "required": true, - "in": "query", - "schema": { - "$ref": "#/components/schemas/EntityType" - } - }, - { - "name": "userId", - "required": false, - "in": "query", - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuditDeletesResponseDto" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "Audit" - ] - } - }, "/auth/admin-sign-up": { "post": { "operationId": "signUpAdmin", @@ -8643,24 +8584,6 @@ ], "type": "string" }, - "AuditDeletesResponseDto": { - "properties": { - "ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "needsFullSync": { - "type": "boolean" - } - }, - "required": [ - "ids", - "needsFullSync" - ], - "type": "object" - }, "AvatarResponse": { "properties": { "color": { @@ -9075,13 +8998,6 @@ }, "type": "object" }, - "EntityType": { - "enum": [ - "ASSET", - "ALBUM" - ], - "type": "string" - }, "ExifResponseDto": { "properties": { "city": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 0473b5603b..d4b36a04f0 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -449,10 +449,6 @@ export type AssetMediaReplaceDto = { fileCreatedAt: string; fileModifiedAt: string; }; -export type AuditDeletesResponseDto = { - ids: string[]; - needsFullSync: boolean; -}; export type SignUpDto = { email: string; name: string; @@ -1913,22 +1909,6 @@ export function playAssetVideo({ id, key }: { ...opts })); } -export function getAuditDeletes({ after, entityType, userId }: { - after: string; - entityType: EntityType; - userId?: string; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: AuditDeletesResponseDto; - }>(`/audit/deletes${QS.query(QS.explode({ - after, - entityType, - userId - }))}`, { - ...opts - })); -} export function signUpAdmin({ signUpDto }: { signUpDto: SignUpDto; }, opts?: Oazapfts.RequestOpts) { @@ -3499,10 +3479,6 @@ export enum AssetMediaSize { Preview = "preview", Thumbnail = "thumbnail" } -export enum EntityType { - Asset = "ASSET", - Album = "ALBUM" -} export enum ManualJobName { PersonCleanup = "person-cleanup", TagCleanup = "tag-cleanup", diff --git a/server/src/controllers/audit.controller.ts b/server/src/controllers/audit.controller.ts deleted file mode 100644 index 856a1cc755..0000000000 --- a/server/src/controllers/audit.controller.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Controller, Get, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { AuditDeletesDto, AuditDeletesResponseDto } from 'src/dtos/audit.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { AuditService } from 'src/services/audit.service'; - -@ApiTags('Audit') -@Controller('audit') -export class AuditController { - constructor(private service: AuditService) {} - - @Get('deletes') - @Authenticated() - getAuditDeletes(@Auth() auth: AuthDto, @Query() dto: AuditDeletesDto): Promise { - return this.service.getDeletes(auth, dto); - } -} diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index f10bf601b4..c9d63f8bcd 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -4,7 +4,6 @@ import { APIKeyController } from 'src/controllers/api-key.controller'; import { AppController } from 'src/controllers/app.controller'; import { AssetMediaController } from 'src/controllers/asset-media.controller'; import { AssetController } from 'src/controllers/asset.controller'; -import { AuditController } from 'src/controllers/audit.controller'; import { AuthController } from 'src/controllers/auth.controller'; import { DownloadController } from 'src/controllers/download.controller'; import { DuplicateController } from 'src/controllers/duplicate.controller'; @@ -40,7 +39,6 @@ export const controllers = [ AppController, AssetController, AssetMediaController, - AuditController, AuthController, DownloadController, DuplicateController, diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index c64f6f2071..f5e588cdd0 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -1,17 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { FileReportItemDto } from 'src/dtos/audit.dto'; -import { - AssetFileType, - AssetPathType, - DatabaseAction, - EntityType, - JobStatus, - PersonPathType, - UserPathType, -} from 'src/enum'; +import { AssetFileType, AssetPathType, JobStatus, PersonPathType, UserPathType } from 'src/enum'; import { AuditService } from 'src/services/audit.service'; -import { auditStub } from 'test/fixtures/audit.stub'; -import { authStub } from 'test/fixtures/auth.stub'; import { newTestService, ServiceMocks } from 'test/utils'; describe(AuditService.name, () => { @@ -33,40 +23,6 @@ describe(AuditService.name, () => { }); }); - describe('getDeletes', () => { - it('should require full sync if the request is older than 100 days', async () => { - mocks.audit.getAfter.mockResolvedValue([]); - - const date = new Date(2022, 0, 1); - await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ - needsFullSync: true, - ids: [], - }); - - expect(mocks.audit.getAfter).toHaveBeenCalledWith(date, { - action: DatabaseAction.DELETE, - userIds: [authStub.admin.user.id], - entityType: EntityType.ASSET, - }); - }); - - it('should get any new or updated assets and deleted ids', async () => { - mocks.audit.getAfter.mockResolvedValue([auditStub.delete.entityId]); - - const date = new Date(); - await expect(sut.getDeletes(authStub.admin, { after: date, entityType: EntityType.ASSET })).resolves.toEqual({ - needsFullSync: false, - ids: ['asset-deleted'], - }); - - expect(mocks.audit.getAfter).toHaveBeenCalledWith(date, { - action: DatabaseAction.DELETE, - userIds: [authStub.admin.user.id], - entityType: EntityType.ASSET, - }); - }); - }); - describe('getChecksums', () => { it('should fail if the file is not in the immich path', async () => { await expect(sut.getChecksums({ filenames: ['foo/bar'] })).rejects.toBeInstanceOf(BadRequestException); diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index a952a0e64f..3948469765 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -4,22 +4,12 @@ import { resolve } from 'node:path'; import { AUDIT_LOG_MAX_DURATION, JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; import { StorageCore } from 'src/cores/storage.core'; import { OnJob } from 'src/decorators'; -import { - AuditDeletesDto, - AuditDeletesResponseDto, - FileChecksumDto, - FileChecksumResponseDto, - FileReportItemDto, - PathEntityType, -} from 'src/dtos/audit.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; +import { FileChecksumDto, FileChecksumResponseDto, FileReportItemDto, PathEntityType } from 'src/dtos/audit.dto'; import { AssetFileType, AssetPathType, - DatabaseAction, JobName, JobStatus, - Permission, PersonPathType, QueueName, StorageFolder, @@ -37,24 +27,6 @@ export class AuditService extends BaseService { return JobStatus.SUCCESS; } - async getDeletes(auth: AuthDto, dto: AuditDeletesDto): Promise { - const userId = dto.userId || auth.user.id; - await this.requireAccess({ auth, permission: Permission.TIMELINE_READ, ids: [userId] }); - - const audits = await this.auditRepository.getAfter(dto.after, { - userIds: [userId], - entityType: dto.entityType, - action: DatabaseAction.DELETE, - }); - - const duration = DateTime.now().diff(DateTime.fromJSDate(dto.after)); - - return { - needsFullSync: duration > AUDIT_LOG_MAX_DURATION, - ids: audits, - }; - } - async getChecksums(dto: FileChecksumDto) { const results: FileChecksumResponseDto[] = []; for (const filename of dto.filenames) {