diff --git a/e2e/src/specs/server/api/asset.e2e-spec.ts b/e2e/src/specs/server/api/asset.e2e-spec.ts index 2d9a325289..ef5f5d373d 100644 --- a/e2e/src/specs/server/api/asset.e2e-spec.ts +++ b/e2e/src/specs/server/api/asset.e2e-spec.ts @@ -1,7 +1,6 @@ import { AssetMediaResponseDto, AssetMediaStatus, - AssetResponseDto, AssetTypeEnum, AssetVisibility, getAssetInfo, @@ -19,7 +18,7 @@ import { Socket } from 'socket.io-client'; import { createUserDto, uuidDto } from 'src/fixtures'; import { makeRandomImage } from 'src/generators'; import { errorDto } from 'src/responses'; -import { app, asBearerAuth, tempDir, TEN_TIMES, testAssetDir, utils } from 'src/utils'; +import { app, asBearerAuth, tempDir, testAssetDir, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -380,57 +379,6 @@ describe('/asset', () => { }); }); - describe('GET /assets/random', () => { - beforeAll(async () => { - await Promise.all([ - utils.createAsset(user1.accessToken), - utils.createAsset(user1.accessToken), - utils.createAsset(user1.accessToken), - utils.createAsset(user1.accessToken), - utils.createAsset(user1.accessToken), - utils.createAsset(user1.accessToken), - ]); - - await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration'); - }); - - it.each(TEN_TIMES)('should return 1 random assets', async () => { - const { status, body } = await request(app) - .get('/assets/random') - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - - const assets: AssetResponseDto[] = body; - expect(assets.length).toBe(1); - expect(assets[0].ownerId).toBe(user1.userId); - }); - - it.each(TEN_TIMES)('should return 2 random assets', async () => { - const { status, body } = await request(app) - .get('/assets/random?count=2') - .set('Authorization', `Bearer ${user1.accessToken}`); - - expect(status).toBe(200); - - const assets: AssetResponseDto[] = body; - expect(assets.length).toBe(2); - - for (const asset of assets) { - expect(asset.ownerId).toBe(user1.userId); - } - }); - - it.skip('should return 1 asset if there are 10 assets in the database but user 2 only has 1', async () => { - const { status, body } = await request(app) - .get('/assets/random') - .set('Authorization', `Bearer ${user2.accessToken}`); - - expect(status).toBe(200); - expect(body).toEqual([expect.objectContaining({ id: user2Assets[0].id })]); - }); - }); - describe('PUT /assets/:id', () => { it('should require access', async () => { const { status, body } = await request(app) diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index e905fc41e8..612a65ae4e 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -110,7 +110,6 @@ Class | Method | HTTP request | Description *AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key *AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr | Retrieve asset OCR data *AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics -*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets *AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video *AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset *AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job @@ -147,7 +146,6 @@ Class | Method | HTTP request | Description *DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user *DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user *DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status -*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets *DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs *DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive *DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index 8e9e5461a1..9e0183536e 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -927,71 +927,6 @@ class AssetsApi { return null; } - /// Get random assets - /// - /// Retrieve a specified number of random assets for the authenticated user. - /// - /// Note: This method returns the HTTP [Response]. - /// - /// Parameters: - /// - /// * [num] count: - /// Number of random assets to return - Future getRandomWithHttpInfo({ num? count, }) async { - // ignore: prefer_const_declarations - final apiPath = r'/assets/random'; - - // ignore: prefer_final_locals - Object? postBody; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - if (count != null) { - queryParams.addAll(_queryParams('', 'count', count)); - } - - const contentTypes = []; - - - return apiClient.invokeAPI( - apiPath, - 'GET', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - /// Get random assets - /// - /// Retrieve a specified number of random assets for the authenticated user. - /// - /// Parameters: - /// - /// * [num] count: - /// Number of random assets to return - Future?> getRandom({ num? count, }) async { - final response = await getRandomWithHttpInfo( count: count, ); - if (response.statusCode >= HttpStatus.badRequest) { - throw ApiException(response.statusCode, await _decodeBodyBytes(response)); - } - // When a remote server returns no body with a status of 204, we shall not decode it. - // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" - // FormatException when trying to decode an empty string. - if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() - .toList(growable: false); - - } - return null; - } - /// Play asset video /// /// Streams the video file for the specified asset. This endpoint also supports byte range requests. diff --git a/mobile/openapi/lib/api/deprecated_api.dart b/mobile/openapi/lib/api/deprecated_api.dart index f4f3d5f6b1..a292c176fd 100644 --- a/mobile/openapi/lib/api/deprecated_api.dart +++ b/mobile/openapi/lib/api/deprecated_api.dart @@ -298,71 +298,6 @@ class DeprecatedApi { return null; } - /// Get random assets - /// - /// Retrieve a specified number of random assets for the authenticated user. - /// - /// Note: This method returns the HTTP [Response]. - /// - /// Parameters: - /// - /// * [num] count: - /// Number of random assets to return - Future getRandomWithHttpInfo({ num? count, }) async { - // ignore: prefer_const_declarations - final apiPath = r'/assets/random'; - - // ignore: prefer_final_locals - Object? postBody; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - - if (count != null) { - queryParams.addAll(_queryParams('', 'count', count)); - } - - const contentTypes = []; - - - return apiClient.invokeAPI( - apiPath, - 'GET', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - ); - } - - /// Get random assets - /// - /// Retrieve a specified number of random assets for the authenticated user. - /// - /// Parameters: - /// - /// * [num] count: - /// Number of random assets to return - Future?> getRandom({ num? count, }) async { - final response = await getRandomWithHttpInfo( count: count, ); - if (response.statusCode >= HttpStatus.badRequest) { - throw ApiException(response.statusCode, await _decodeBodyBytes(response)); - } - // When a remote server returns no body with a status of 204, we shall not decode it. - // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" - // FormatException when trying to decode an empty string. - if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, 'List') as List) - .cast() - .toList(growable: false); - - } - return null; - } - /// Run jobs /// /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index bc855058c2..9422786605 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -3363,69 +3363,6 @@ "x-immich-state": "Beta" } }, - "/assets/random": { - "get": { - "deprecated": true, - "description": "Retrieve a specified number of random assets for the authenticated user.", - "operationId": "getRandom", - "parameters": [ - { - "name": "count", - "required": false, - "in": "query", - "description": "Number of random assets to return", - "schema": { - "minimum": 1, - "type": "number" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/AssetResponseDto" - }, - "type": "array" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "summary": "Get random assets", - "tags": [ - "Assets", - "Deprecated" - ], - "x-immich-history": [ - { - "version": "v1", - "state": "Added" - }, - { - "version": "v1", - "state": "Deprecated", - "replacementId": "searchAssets" - } - ], - "x-immich-permission": "asset.read", - "x-immich-state": "Deprecated" - } - }, "/assets/statistics": { "get": { "description": "Retrieve various statistics about the assets owned by the authenticated user.", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 520948b820..bd96223cb9 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -4050,21 +4050,6 @@ export function updateBulkAssetMetadata({ assetMetadataBulkUpsertDto }: { body: assetMetadataBulkUpsertDto }))); } -/** - * Get random assets - */ -export function getRandom({ count }: { - count?: number; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: AssetResponseDto[]; - }>(`/assets/random${QS.query(QS.explode({ - count - }))}`, { - ...opts - })); -} /** * Get asset statistics */ diff --git a/server/src/controllers/asset.controller.spec.ts b/server/src/controllers/asset.controller.spec.ts index 4a8d4b3582..3c01e3d0a9 100644 --- a/server/src/controllers/asset.controller.spec.ts +++ b/server/src/controllers/asset.controller.spec.ts @@ -245,19 +245,6 @@ describe(AssetController.name, () => { }); }); - describe('GET /assets/random', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/random`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should not allow count to be a string', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/assets/random?count=ABC'); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['[count] Invalid input: expected number, received NaN'])); - }); - }); - describe('GET /assets/:id/metadata', () => { it('should be an authenticated route', async () => { await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/metadata`); diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts index 2024760975..d36a9dd7fd 100644 --- a/server/src/controllers/asset.controller.ts +++ b/server/src/controllers/asset.controller.ts @@ -16,7 +16,6 @@ import { AssetStatsDto, AssetStatsResponseDto, DeviceIdDto, - RandomAssetsDto, UpdateAssetDto, } from 'src/dtos/asset.dto'; import { AuthDto } from 'src/dtos/auth.dto'; @@ -32,17 +31,6 @@ import { UUIDParamDto } from 'src/validation'; export class AssetController { constructor(private service: AssetService) {} - @Get('random') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Get random assets', - description: 'Retrieve a specified number of random assets for the authenticated user.', - history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'searchAssets' }), - }) - getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise { - return this.service.getRandom(auth, dto.count ?? 1); - } - @Get('/device/:deviceId') @Endpoint({ summary: 'Retrieve assets by device ID', diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 3adb937475..c05462c554 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -58,12 +58,6 @@ const UpdateAssetSchema = UpdateAssetBaseSchema.extend({ livePhotoVideoId: z.uuidv4().nullish().describe('Live photo video ID'), }).meta({ id: 'UpdateAssetDto' }); -const RandomAssetsSchema = z - .object({ - count: z.coerce.number().min(1).optional().describe('Number of random assets to return'), - }) - .meta({ id: 'RandomAssetsDto' }); - const AssetBulkDeleteSchema = BulkIdsSchema.extend({ force: z.boolean().optional().describe('Force delete even if in use'), }).meta({ id: 'AssetBulkDeleteDto' }); @@ -191,7 +185,6 @@ export const mapStats = (stats: AssetStats): AssetStatsResponseDto => { export class DeviceIdDto extends createZodDto(DeviceIdSchema) {} export class AssetBulkUpdateDto extends createZodDto(AssetBulkUpdateSchema) {} export class UpdateAssetDto extends createZodDto(UpdateAssetSchema) {} -export class RandomAssetsDto extends createZodDto(RandomAssetsSchema) {} export class AssetBulkDeleteDto extends createZodDto(AssetBulkDeleteSchema) {} export class AssetIdsDto extends createZodDto(AssetIdsSchema) {} export class AssetJobsDto extends createZodDto(AssetJobsSchema) {} diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 2ff4d224cf..932f72daae 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -681,19 +681,6 @@ export class AssetRepository { .executeTakeFirstOrThrow(); } - getRandom(userIds: string[], take: number) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .$call(withDefaultVisibility) - .where('ownerId', '=', anyUuid(userIds)) - .where('deletedAt', 'is', null) - .orderBy((eb) => eb.fn('random')) - .limit(take) - .execute(); - } - @GenerateSql({ params: [{}] }) async getTimeBuckets(options: TimeBucketOptions): Promise { return this.db diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts index 718ec00f1d..bef6f332d3 100755 --- a/server/src/services/asset.service.spec.ts +++ b/server/src/services/asset.service.spec.ts @@ -7,9 +7,8 @@ import { AssetStats } from 'src/repositories/asset.repository'; import { AssetService } from 'src/services/asset.service'; import { AssetFactory } from 'test/factories/asset.factory'; import { AuthFactory } from 'test/factories/auth.factory'; -import { PartnerFactory } from 'test/factories/partner.factory'; import { authStub } from 'test/fixtures/auth.stub'; -import { getForAsset, getForAssetDeletion, getForPartner } from 'test/mappers'; +import { getForAsset, getForAssetDeletion } from 'test/mappers'; import { factory, newUuid } from 'test/small.factory'; import { makeStream, newTestService, ServiceMocks } from 'test/utils'; @@ -70,41 +69,6 @@ describe(AssetService.name, () => { }); }); - describe('getRandom', () => { - it('should get own random assets', async () => { - mocks.partner.getAll.mockResolvedValue([]); - mocks.asset.getRandom.mockResolvedValue([getForAsset(AssetFactory.create())]); - - await sut.getRandom(authStub.admin, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1); - }); - - it('should not include partner assets if not in timeline', async () => { - const partner = PartnerFactory.create({ inTimeline: false }); - const auth = AuthFactory.create({ id: partner.sharedWithId }); - - mocks.asset.getRandom.mockResolvedValue([getForAsset(AssetFactory.create())]); - mocks.partner.getAll.mockResolvedValue([getForPartner(partner)]); - - await sut.getRandom(auth, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id], 1); - }); - - it('should include partner assets if in timeline', async () => { - const partner = PartnerFactory.create({ inTimeline: true }); - const auth = AuthFactory.create({ id: partner.sharedWithId }); - - mocks.asset.getRandom.mockResolvedValue([getForAsset(AssetFactory.create())]); - mocks.partner.getAll.mockResolvedValue([getForPartner(partner)]); - - await sut.getRandom(auth, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id, partner.sharedById], 1); - }); - }); - describe('get', () => { it('should allow owner access', async () => { const asset = AssetFactory.create(); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts index 1e5d23a98d..dde358234b 100644 --- a/server/src/services/asset.service.ts +++ b/server/src/services/asset.service.ts @@ -39,7 +39,6 @@ import { requireElevatedPermission } from 'src/utils/access'; import { getAssetFiles, getDimensions, - getMyPartnerIds, isPanorama, onAfterUnlink, onBeforeLink, @@ -60,16 +59,6 @@ export class AssetService extends BaseService { return mapStats(stats); } - async getRandom(auth: AuthDto, count: number): Promise { - const partnerIds = await getMyPartnerIds({ - userId: auth.user.id, - repository: this.partnerRepository, - timelineEnabled: true, - }); - const assets = await this.assetRepository.getRandom([auth.user.id, ...partnerIds], count); - return assets.map((a) => mapAsset(a, { auth })); - } - async getUserAssetsByDeviceId(auth: AuthDto, deviceId: string) { return this.assetRepository.getAllByDeviceId(auth.user.id, deviceId); } diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 68667fa109..f66de5371e 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -20,7 +20,6 @@ export const newAssetRepositoryMock = (): Mocked