1
0
mirror of https://github.com/immich-app/immich.git synced 2025-01-24 17:07:39 +02:00

feat(server): search by is favorite (#1400)

* feat(server): search by is favorite

* chore: regenerate api

* fix: boolean transform

* chore: remove console log

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen 2023-01-23 23:16:20 -05:00 committed by GitHub
parent eade36ee82
commit b7d34079d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 123 additions and 63 deletions

View File

@ -381,7 +381,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAllAssets** # **getAllAssets**
> List<AssetResponseDto> getAllAssets(ifNoneMatch) > List<AssetResponseDto> getAllAssets(isFavorite, skip, ifNoneMatch)
@ -398,10 +398,12 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi(); final api_instance = AssetApi();
final isFavorite = true; // bool |
final skip = 8.14; // num |
final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
try { try {
final result = api_instance.getAllAssets(ifNoneMatch); final result = api_instance.getAllAssets(isFavorite, skip, ifNoneMatch);
print(result); print(result);
} catch (e) { } catch (e) {
print('Exception when calling AssetApi->getAllAssets: $e\n'); print('Exception when calling AssetApi->getAllAssets: $e\n');
@ -412,6 +414,8 @@ try {
Name | Type | Description | Notes Name | Type | Description | Notes
------------- | ------------- | ------------- | ------------- ------------- | ------------- | ------------- | -------------
**isFavorite** | **bool**| | [optional]
**skip** | **num**| | [optional]
**ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional]
### Return type ### Return type

View File

@ -409,9 +409,13 @@ class AssetApi {
/// ///
/// Parameters: /// Parameters:
/// ///
/// * [bool] isFavorite:
///
/// * [num] skip:
///
/// * [String] ifNoneMatch: /// * [String] ifNoneMatch:
/// ETag of data already cached on the client /// ETag of data already cached on the client
Future<Response> getAllAssetsWithHttpInfo({ String? ifNoneMatch, }) async { Future<Response> getAllAssetsWithHttpInfo({ bool? isFavorite, num? skip, String? ifNoneMatch, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/asset'; final path = r'/asset';
@ -422,6 +426,13 @@ class AssetApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip));
}
if (ifNoneMatch != null) { if (ifNoneMatch != null) {
headerParams[r'if-none-match'] = parameterToString(ifNoneMatch); headerParams[r'if-none-match'] = parameterToString(ifNoneMatch);
} }
@ -444,10 +455,14 @@ class AssetApi {
/// ///
/// Parameters: /// Parameters:
/// ///
/// * [bool] isFavorite:
///
/// * [num] skip:
///
/// * [String] ifNoneMatch: /// * [String] ifNoneMatch:
/// ETag of data already cached on the client /// ETag of data already cached on the client
Future<List<AssetResponseDto>?> getAllAssets({ String? ifNoneMatch, }) async { Future<List<AssetResponseDto>?> getAllAssets({ bool? isFavorite, num? skip, String? ifNoneMatch, }) async {
final response = await getAllAssetsWithHttpInfo( ifNoneMatch: ifNoneMatch, ); final response = await getAllAssetsWithHttpInfo( isFavorite: isFavorite, skip: skip, ifNoneMatch: ifNoneMatch, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }

View File

@ -68,7 +68,7 @@ void main() {
// Get all AssetEntity belong to the user // Get all AssetEntity belong to the user
// //
//Future<List<AssetResponseDto>> getAllAssets({ String ifNoneMatch }) async //Future<List<AssetResponseDto>> getAllAssets({ bool isFavorite, num skip, String ifNoneMatch }) async
test('test getAllAssets', () async { test('test getAllAssets', () async {
// TODO // TODO
}); });

View File

@ -1,17 +1,11 @@
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsOptional, IsBoolean } from 'class-validator'; import { IsBoolean, IsOptional } from 'class-validator';
import { toBoolean } from '../../../utils/transform.util';
export class GetAlbumsDto { export class GetAlbumsDto {
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@Transform(({ value }) => { @Transform(toBoolean)
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
})
/** /**
* true: only shared albums * true: only shared albums
* false: only non-shared own albums * false: only non-shared own albums

View File

@ -15,7 +15,8 @@ import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-as
import { In } from 'typeorm/find-options/operator/In'; import { In } from 'typeorm/find-options/operator/In';
import { UpdateAssetDto } from './dto/update-asset.dto'; import { UpdateAssetDto } from './dto/update-asset.dto';
import { ITagRepository } from '../tag/tag.repository'; import { ITagRepository } from '../tag/tag.repository';
import { IsNull } from 'typeorm'; import { IsNull, Not } from 'typeorm';
import { AssetSearchDto } from './dto/asset-search.dto';
export interface IAssetRepository { export interface IAssetRepository {
create( create(
@ -28,7 +29,7 @@ export interface IAssetRepository {
livePhotoAssetEntity?: AssetEntity, livePhotoAssetEntity?: AssetEntity,
): Promise<AssetEntity>; ): Promise<AssetEntity>;
update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise<AssetEntity>; update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise<AssetEntity>;
getAllByUserId(userId: string, skip?: number): Promise<AssetEntity[]>; getAllByUserId(userId: string, dto: AssetSearchDto): Promise<AssetEntity[]>;
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>; getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
getById(assetId: string): Promise<AssetEntity>; getById(assetId: string): Promise<AssetEntity>;
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>; getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
@ -244,17 +245,23 @@ export class AssetRepository implements IAssetRepository {
* Get all assets belong to the user on the database * Get all assets belong to the user on the database
* @param userId * @param userId
*/ */
async getAllByUserId(userId: string, skip?: number): Promise<AssetEntity[]> { async getAllByUserId(userId: string, dto: AssetSearchDto): Promise<AssetEntity[]> {
const query = this.assetRepository return this.assetRepository.find({
.createQueryBuilder('asset') where: {
.where('asset.userId = :userId', { userId: userId }) userId,
.andWhere('asset.resizePath is not NULL') resizePath: Not(IsNull()),
.andWhere('asset.isVisible = true') isVisible: true,
.leftJoinAndSelect('asset.exifInfo', 'exifInfo') isFavorite: dto.isFavorite,
.leftJoinAndSelect('asset.tags', 'tags') },
.skip(skip || 0) relations: {
.orderBy('asset.createdAt', 'DESC'); exifInfo: true,
return await query.getMany(); tags: true,
},
skip: dto.skip || 0,
order: {
createdAt: 'DESC',
},
});
} }
/** /**

View File

@ -54,6 +54,7 @@ import { DownloadFilesDto } from './dto/download-files.dto';
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto'; import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
import { AssetSearchDto } from './dto/asset-search.dto';
@ApiBearerAuth() @ApiBearerAuth()
@ApiTags('Asset') @ApiTags('Asset')
@ -219,9 +220,11 @@ export class AssetController {
required: false, required: false,
schema: { type: 'string' }, schema: { type: 'string' },
}) })
async getAllAssets(@GetAuthUser() authUser: AuthUserDto): Promise<AssetResponseDto[]> { getAllAssets(
const assets = await this.assetService.getAllAssets(authUser); @GetAuthUser() authUser: AuthUserDto,
return assets; @Query(new ValidationPipe({ transform: true })) dto: AssetSearchDto,
): Promise<AssetResponseDto[]> {
return this.assetService.getAllAssets(authUser, dto);
} }
@Authenticated() @Authenticated()

View File

@ -54,6 +54,7 @@ import { DownloadFilesDto } from './dto/download-files.dto';
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto'; import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
import { AssetSearchDto } from './dto/asset-search.dto';
const fileInfo = promisify(stat); const fileInfo = promisify(stat);
@ -200,8 +201,8 @@ export class AssetService {
return this._assetRepository.getAllByDeviceId(authUser.id, deviceId); return this._assetRepository.getAllByDeviceId(authUser.id, deviceId);
} }
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> { public async getAllAssets(authUser: AuthUserDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
const assets = await this._assetRepository.getAllByUserId(authUser.id); const assets = await this._assetRepository.getAllByUserId(authUser.id, dto);
return assets.map((asset) => mapAsset(asset)); return assets.map((asset) => mapAsset(asset));
} }
@ -238,7 +239,7 @@ export class AssetService {
} }
public async downloadLibrary(user: AuthUserDto, dto: DownloadDto) { public async downloadLibrary(user: AuthUserDto, dto: DownloadDto) {
const assets = await this._assetRepository.getAllByUserId(user.id, dto.skip); const assets = await this._assetRepository.getAllByUserId(user.id, dto);
return this.downloadService.downloadArchive(dto.name || `library`, assets); return this.downloadService.downloadArchive(dto.name || `library`, assets);
} }

View File

@ -0,0 +1,15 @@
import { Transform } from 'class-transformer';
import { IsBoolean, IsNotEmpty, IsNumber, IsOptional } from 'class-validator';
import { toBoolean } from '../../../utils/transform.util';
export class AssetSearchDto {
@IsOptional()
@IsNotEmpty()
@IsBoolean()
@Transform(toBoolean)
isFavorite?: boolean;
@IsOptional()
@IsNumber()
skip?: number;
}

View File

@ -1,6 +0,0 @@
import { IsNotEmpty } from 'class-validator';
export class GetAssetDto {
@IsNotEmpty()
deviceId!: string;
}

View File

@ -1,31 +1,18 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsBoolean, IsOptional } from 'class-validator'; import { IsBoolean, IsOptional } from 'class-validator';
import { toBoolean } from '../../../utils/transform.util';
export class ServeFileDto { export class ServeFileDto {
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@Transform(({ value }) => { @Transform(toBoolean)
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
})
@ApiProperty({ type: Boolean, title: 'Is serve thumbnail (resize) file' }) @ApiProperty({ type: Boolean, title: 'Is serve thumbnail (resize) file' })
isThumb?: boolean; isThumb?: boolean;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@Transform(({ value }) => { @Transform(toBoolean)
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
})
@ApiProperty({ type: Boolean, title: 'Is request made from web' }) @ApiProperty({ type: Boolean, title: 'Is request made from web' })
isWeb?: boolean; isWeb?: boolean;
} }

View File

@ -0,0 +1,8 @@
export const toBoolean = ({ value }: { value: string }) => {
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
};

View File

@ -1352,6 +1352,22 @@
"operationId": "getAllAssets", "operationId": "getAllAssets",
"description": "Get all AssetEntity belong to the user", "description": "Get all AssetEntity belong to the user",
"parameters": [ "parameters": [
{
"name": "isFavorite",
"required": false,
"in": "query",
"schema": {
"type": "boolean"
}
},
{
"name": "skip",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
},
{ {
"name": "if-none-match", "name": "if-none-match",
"in": "header", "in": "header",

View File

@ -3846,11 +3846,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
}, },
/** /**
* Get all AssetEntity belong to the user * Get all AssetEntity belong to the user
* @param {boolean} [isFavorite]
* @param {number} [skip]
* @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {string} [ifNoneMatch] ETag of data already cached on the client
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getAllAssets: async (ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { getAllAssets: async (isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset`; const localVarPath = `/asset`;
// use dummy base URL string because the URL constructor only accepts absolute URLs. // use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@ -3867,6 +3869,14 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
// http bearer authentication required // http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration) await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (isFavorite !== undefined) {
localVarQueryParameter['isFavorite'] = isFavorite;
}
if (skip !== undefined) {
localVarQueryParameter['skip'] = skip;
}
if (ifNoneMatch !== undefined && ifNoneMatch !== null) { if (ifNoneMatch !== undefined && ifNoneMatch !== null) {
localVarHeaderParameter['if-none-match'] = String(ifNoneMatch); localVarHeaderParameter['if-none-match'] = String(ifNoneMatch);
} }
@ -4504,12 +4514,14 @@ export const AssetApiFp = function(configuration?: Configuration) {
}, },
/** /**
* Get all AssetEntity belong to the user * Get all AssetEntity belong to the user
* @param {boolean} [isFavorite]
* @param {number} [skip]
* @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {string} [ifNoneMatch] ETag of data already cached on the client
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async getAllAssets(ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> { async getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(ifNoneMatch, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(isFavorite, skip, ifNoneMatch, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
@ -4729,12 +4741,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
}, },
/** /**
* Get all AssetEntity belong to the user * Get all AssetEntity belong to the user
* @param {boolean} [isFavorite]
* @param {number} [skip]
* @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {string} [ifNoneMatch] ETag of data already cached on the client
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getAllAssets(ifNoneMatch?: string, options?: any): AxiosPromise<Array<AssetResponseDto>> { getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.getAllAssets(ifNoneMatch, options).then((request) => request(axios, basePath)); return localVarFp.getAllAssets(isFavorite, skip, ifNoneMatch, options).then((request) => request(axios, basePath));
}, },
/** /**
* Get a single asset\'s information * Get a single asset\'s information
@ -4953,13 +4967,15 @@ export class AssetApi extends BaseAPI {
/** /**
* Get all AssetEntity belong to the user * Get all AssetEntity belong to the user
* @param {boolean} [isFavorite]
* @param {number} [skip]
* @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {string} [ifNoneMatch] ETag of data already cached on the client
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
* @memberof AssetApi * @memberof AssetApi
*/ */
public getAllAssets(ifNoneMatch?: string, options?: AxiosRequestConfig) { public getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).getAllAssets(ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); return AssetApiFp(this.configuration).getAllAssets(isFavorite, skip, ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
} }
/** /**