0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-18 02:31:28 -05:00

feat: global edit

This commit is contained in:
martabal 2023-12-09 00:22:20 +01:00
parent e8b308141b
commit 5f98f12731
No known key found for this signature in database
GPG key ID: C00196E3148A52BD
17 changed files with 491 additions and 71 deletions

View file

@ -11618,11 +11618,11 @@ export const FaceApiAxiosParamCreator = function (configuration?: Configuration)
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
reassignFace: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFacesById', 'id', id)
assertParamExists('reassignFace', 'id', id)
// verify required parameter 'faceDto' is not null or undefined
assertParamExists('reassignFacesById', 'faceDto', faceDto)
assertParamExists('reassignFace', 'faceDto', faceDto)
const localVarPath = `/face/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
@ -11728,8 +11728,8 @@ export const FaceApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFacesById(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFacesById(id, faceDto, options);
async reassignFace(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFace(id, faceDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@ -11763,12 +11763,12 @@ export const FaceApiFactory = function (configuration?: Configuration, basePath?
},
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {FaceApiReassignFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
reassignFace(requestParameters: FaceApiReassignFaceRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.reassignFace(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
},
/**
*
@ -11797,22 +11797,22 @@ export interface FaceApiGetFacesRequest {
}
/**
* Request parameters for reassignFacesById operation in FaceApi.
* Request parameters for reassignFace operation in FaceApi.
* @export
* @interface FaceApiReassignFacesByIdRequest
* @interface FaceApiReassignFaceRequest
*/
export interface FaceApiReassignFacesByIdRequest {
export interface FaceApiReassignFaceRequest {
/**
*
* @type {string}
* @memberof FaceApiReassignFacesById
* @memberof FaceApiReassignFace
*/
readonly id: string
/**
*
* @type {FaceDto}
* @memberof FaceApiReassignFacesById
* @memberof FaceApiReassignFace
*/
readonly faceDto: FaceDto
}
@ -11851,13 +11851,13 @@ export class FaceApi extends BaseAPI {
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {FaceApiReassignFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FaceApi
*/
public reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
public reassignFace(requestParameters: FaceApiReassignFaceRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).reassignFace(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
}
/**
@ -14037,6 +14037,50 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('unassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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: 'DELETE', ...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)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -14232,6 +14276,16 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async unassignFaces(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.unassignFaces(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {PeopleUpdateDto} peopleUpdateDto
@ -14334,6 +14388,15 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
@ -14467,6 +14530,20 @@ export interface PersonApiReassignFacesRequest {
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for unassignFaces operation in PersonApi.
* @export
* @interface PersonApiUnassignFacesRequest
*/
export interface PersonApiUnassignFacesRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiUnassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for updatePeople operation in PersonApi.
* @export
@ -14596,6 +14673,17 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.

View file

@ -134,7 +134,7 @@ Class | Method | HTTP request | Description
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
*FaceApi* | [**getFaces**](doc//FaceApi.md#getfaces) | **GET** /face |
*FaceApi* | [**reassignFacesById**](doc//FaceApi.md#reassignfacesbyid) | **PUT** /face/{id} |
*FaceApi* | [**reassignFace**](doc//FaceApi.md#reassignface) | **PUT** /face/{id} |
*FaceApi* | [**unassignFace**](doc//FaceApi.md#unassignface) | **DELETE** /face/{id} |
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
@ -164,6 +164,7 @@ Class | Method | HTTP request | Description
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
*PersonApi* | [**reassignFaces**](doc//PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
*PersonApi* | [**unassignFaces**](doc//PersonApi.md#unassignfaces) | **DELETE** /person |
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |

View file

@ -10,7 +10,7 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**getFaces**](FaceApi.md#getfaces) | **GET** /face |
[**reassignFacesById**](FaceApi.md#reassignfacesbyid) | **PUT** /face/{id} |
[**reassignFace**](FaceApi.md#reassignface) | **PUT** /face/{id} |
[**unassignFace**](FaceApi.md#unassignface) | **DELETE** /face/{id} |
@ -69,8 +69,8 @@ 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)
# **reassignFacesById**
> PersonResponseDto reassignFacesById(id, faceDto)
# **reassignFace**
> PersonResponseDto reassignFace(id, faceDto)
@ -97,10 +97,10 @@ final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final faceDto = FaceDto(); // FaceDto |
try {
final result = api_instance.reassignFacesById(id, faceDto);
final result = api_instance.reassignFace(id, faceDto);
print(result);
} catch (e) {
print('Exception when calling FaceApi->reassignFacesById: $e\n');
print('Exception when calling FaceApi->reassignFace: $e\n');
}
```

View file

@ -17,6 +17,7 @@ Method | HTTP request | Description
[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
[**reassignFaces**](PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
[**unassignFaces**](PersonApi.md#unassignfaces) | **DELETE** /person |
[**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person |
[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} |
@ -461,6 +462,61 @@ 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)
# **unassignFaces**
> List<BulkIdResponseDto> unassignFaces(assetFaceUpdateDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = PersonApi();
final assetFaceUpdateDto = AssetFaceUpdateDto(); // AssetFaceUpdateDto |
try {
final result = api_instance.unassignFaces(assetFaceUpdateDto);
print(result);
} catch (e) {
print('Exception when calling PersonApi->unassignFaces: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetFaceUpdateDto** | [**AssetFaceUpdateDto**](AssetFaceUpdateDto.md)| |
### Return type
[**List<BulkIdResponseDto>**](BulkIdResponseDto.md)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[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)
# **updatePeople**
> List<BulkIdResponseDto> updatePeople(peopleUpdateDto)

View file

@ -74,7 +74,7 @@ class FaceApi {
/// * [String] id (required):
///
/// * [FaceDto] faceDto (required):
Future<Response> reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async {
Future<Response> reassignFaceWithHttpInfo(String id, FaceDto faceDto,) async {
// ignore: prefer_const_declarations
final path = r'/face/{id}'
.replaceAll('{id}', id);
@ -105,8 +105,8 @@ class FaceApi {
/// * [String] id (required):
///
/// * [FaceDto] faceDto (required):
Future<PersonResponseDto?> reassignFacesById(String id, FaceDto faceDto,) async {
final response = await reassignFacesByIdWithHttpInfo(id, faceDto,);
Future<PersonResponseDto?> reassignFace(String id, FaceDto faceDto,) async {
final response = await reassignFaceWithHttpInfo(id, faceDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View file

@ -413,6 +413,56 @@ class PersonApi {
return null;
}
/// Performs an HTTP 'DELETE /person' operation and returns the [Response].
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<Response> unassignFacesWithHttpInfo(AssetFaceUpdateDto assetFaceUpdateDto,) async {
// ignore: prefer_const_declarations
final path = r'/person';
// ignore: prefer_final_locals
Object? postBody = assetFaceUpdateDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
Future<List<BulkIdResponseDto>?> unassignFaces(AssetFaceUpdateDto assetFaceUpdateDto,) async {
final response = await unassignFacesWithHttpInfo(assetFaceUpdateDto,);
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<BulkIdResponseDto>') as List)
.cast<BulkIdResponseDto>()
.toList();
}
return null;
}
/// Performs an HTTP 'PUT /person' operation and returns the [Response].
/// Parameters:
///

View file

@ -22,8 +22,8 @@ void main() {
// TODO
});
//Future<PersonResponseDto> reassignFacesById(String id, FaceDto faceDto) async
test('test reassignFacesById', () async {
//Future<PersonResponseDto> reassignFace(String id, FaceDto faceDto) async
test('test reassignFace', () async {
// TODO
});

View file

@ -57,6 +57,11 @@ void main() {
// TODO
});
//Future<List<BulkIdResponseDto>> unassignFaces(AssetFaceUpdateDto assetFaceUpdateDto) async
test('test unassignFaces', () async {
// TODO
});
//Future<List<BulkIdResponseDto>> updatePeople(PeopleUpdateDto peopleUpdateDto) async
test('test updatePeople', () async {
// TODO

View file

@ -3307,7 +3307,7 @@
]
},
"put": {
"operationId": "reassignFacesById",
"operationId": "reassignFace",
"parameters": [
{
"name": "id",
@ -4119,6 +4119,49 @@
}
},
"/person": {
"delete": {
"operationId": "unassignFaces",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AssetFaceUpdateDto"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/BulkIdResponseDto"
},
"type": "array"
}
}
},
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Person"
]
},
"get": {
"operationId": "getAllPeople",
"parameters": [

View file

@ -105,6 +105,11 @@ export class AssetFaceResponseDto extends AssetFaceWithoutPersonResponseDto {
person!: PersonResponseDto | null;
}
export class FaceDto {
@ValidateUUID()
id!: string;
}
export class AssetFaceUpdateDto {
@IsArray()
@ValidateNested({ each: true })
@ -112,11 +117,6 @@ export class AssetFaceUpdateDto {
data!: AssetFaceUpdateItem[];
}
export class FaceDto {
@ValidateUUID()
id!: string;
}
export class AssetFaceUpdateItem {
@ValidateUUID()
personId!: string;

View file

@ -406,7 +406,7 @@ describe(PersonService.name, () => {
});
});
describe('reassignFacesById', () => {
describe('reassignFace', () => {
it('should create a new person', async () => {
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
@ -415,7 +415,7 @@ describe(PersonService.name, () => {
personMock.getById.mockResolvedValue(personStub.noName);
personMock.getRandomFace.mockResolvedValue(null);
await expect(
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
sut.reassignFace(authStub.admin, personStub.noName.id, {
id: faceStub.face1.id,
}),
).resolves.toEqual({
@ -437,7 +437,7 @@ describe(PersonService.name, () => {
personMock.getById.mockResolvedValue(personStub.noName);
personMock.getRandomFace.mockResolvedValue(null);
await expect(
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
sut.reassignFace(authStub.admin, personStub.noName.id, {
id: faceStub.face1.id,
}),
).rejects.toBeInstanceOf(BadRequestException);
@ -471,6 +471,21 @@ describe(PersonService.name, () => {
});
});
describe('unassignFaces', () => {
it('should unassign a face', async () => {
personMock.getFacesByIds.mockResolvedValueOnce([faceStub.face1]);
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
personMock.reassignFace.mockResolvedValue(1);
personMock.getRandomFace.mockResolvedValue(null);
personMock.getFaceById.mockResolvedValueOnce(faceStub.unassignedFace);
await expect(
sut.unassignFaces(authStub.admin, { data: [{ assetId: faceStub.face1.id, personId: 'person-1' }] }),
).resolves.toStrictEqual([{ id: 'assetFaceId', success: true }]);
});
});
describe('handlePersonDelete', () => {
it('should stop if a person has not be found', async () => {
personMock.getById.mockResolvedValue(null);

View file

@ -87,6 +87,24 @@ export class PersonService {
return this.repository.create({ ownerId: authUser.id });
}
async reassignFace(authUser: AuthUserDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, dto.id);
const face = await this.repository.getFaceById(dto.id);
const person = await this.findOrFail(personId);
await this.repository.reassignFace(face.id, personId);
if (person.faceAssetId === null) {
await this.createNewFeaturePhoto([person.id]);
}
if (face.person && face.person.faceAssetId === face.id) {
await this.createNewFeaturePhoto([face.person.id]);
}
return await this.findOrFail(personId).then(mapPerson);
}
async reassignFaces(authUser: AuthUserDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
const person = await this.findOrFail(personId);
@ -131,22 +149,32 @@ export class PersonService {
return mapFace(face, authUser);
}
async reassignFacesById(authUser: AuthUserDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
async unassignFaces(authUser: AuthUserDto, dto: AssetFaceUpdateDto): Promise<BulkIdResponseDto[]> {
const changeFeaturePhoto: string[] = [];
const results: BulkIdResponseDto[] = [];
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, dto.id);
const face = await this.repository.getFaceById(dto.id);
const person = await this.findOrFail(personId);
for (const data of dto.data) {
const faces = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
await this.repository.reassignFace(face.id, personId);
if (person.faceAssetId === null) {
await this.createNewFeaturePhoto([person.id]);
for (const face of faces) {
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, face.id);
if (face.personId) {
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, face.personId);
}
await this.repository.reassignFace(face.id, null);
if (face.person && face.person.faceAssetId === face.id) {
changeFeaturePhoto.push(face.person.id);
}
results.push({ id: face.id, success: true });
}
}
if (face.person && face.person.faceAssetId === face.id) {
await this.createNewFeaturePhoto([face.person.id]);
if (changeFeaturePhoto.length > 0) {
// Remove duplicates
await this.createNewFeaturePhoto(Array.from(new Set(changeFeaturePhoto)));
}
return await this.findOrFail(personId).then(mapPerson);
return results;
}
async getFacesById(authUser: AuthUserDto, dto: FaceDto): Promise<AssetFaceResponseDto[]> {

View file

@ -18,12 +18,12 @@ export class FaceController {
}
@Put(':id')
reassignFacesById(
reassignFace(
@AuthUser() authUser: AuthUserDto,
@Param() { id }: UUIDParamDto,
@Body() dto: FaceDto,
): Promise<PersonResponseDto> {
return this.service.reassignFacesById(authUser, id, dto);
return this.service.reassignFace(authUser, id, dto);
}
@Delete(':id')

View file

@ -13,7 +13,7 @@ import {
PersonStatisticsResponseDto,
PersonUpdateDto,
} from '@app/domain';
import { Body, Controller, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Post, Put, Query, StreamableFile } from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AuthUser, Authenticated } from '../app.guard';
import { UseValidation } from '../app.utils';
@ -49,6 +49,11 @@ export class PersonController {
return this.service.reassignFaces(authUser, id, dto);
}
@Delete()
unassignFaces(@AuthUser() authUser: AuthUserDto, @Body() dto: AssetFaceUpdateDto): Promise<BulkIdResponseDto[]> {
return this.service.unassignFaces(authUser, dto);
}
@Put()
updatePeople(@AuthUser() authUser: AuthUserDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
return this.service.updatePeople(authUser, dto);

View file

@ -11618,11 +11618,11 @@ export const FaceApiAxiosParamCreator = function (configuration?: Configuration)
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
reassignFace: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFacesById', 'id', id)
assertParamExists('reassignFace', 'id', id)
// verify required parameter 'faceDto' is not null or undefined
assertParamExists('reassignFacesById', 'faceDto', faceDto)
assertParamExists('reassignFace', 'faceDto', faceDto)
const localVarPath = `/face/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
@ -11728,8 +11728,8 @@ export const FaceApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFacesById(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFacesById(id, faceDto, options);
async reassignFace(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFace(id, faceDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@ -11763,12 +11763,12 @@ export const FaceApiFactory = function (configuration?: Configuration, basePath?
},
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {FaceApiReassignFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
reassignFace(requestParameters: FaceApiReassignFaceRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.reassignFace(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
},
/**
*
@ -11797,22 +11797,22 @@ export interface FaceApiGetFacesRequest {
}
/**
* Request parameters for reassignFacesById operation in FaceApi.
* Request parameters for reassignFace operation in FaceApi.
* @export
* @interface FaceApiReassignFacesByIdRequest
* @interface FaceApiReassignFaceRequest
*/
export interface FaceApiReassignFacesByIdRequest {
export interface FaceApiReassignFaceRequest {
/**
*
* @type {string}
* @memberof FaceApiReassignFacesById
* @memberof FaceApiReassignFace
*/
readonly id: string
/**
*
* @type {FaceDto}
* @memberof FaceApiReassignFacesById
* @memberof FaceApiReassignFace
*/
readonly faceDto: FaceDto
}
@ -11851,13 +11851,13 @@ export class FaceApi extends BaseAPI {
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {FaceApiReassignFaceRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FaceApi
*/
public reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
public reassignFace(requestParameters: FaceApiReassignFaceRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).reassignFace(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
}
/**
@ -14037,6 +14037,50 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces: async (assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('unassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person`;
// 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: 'DELETE', ...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)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
@ -14232,6 +14276,16 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async unassignFaces(assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<BulkIdResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.unassignFaces(assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {PeopleUpdateDto} peopleUpdateDto
@ -14334,6 +14388,15 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
@ -14467,6 +14530,20 @@ export interface PersonApiReassignFacesRequest {
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for unassignFaces operation in PersonApi.
* @export
* @interface PersonApiUnassignFacesRequest
*/
export interface PersonApiUnassignFacesRequest {
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiUnassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/**
* Request parameters for updatePeople operation in PersonApi.
* @export
@ -14596,6 +14673,17 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUnassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public unassignFaces(requestParameters: PersonApiUnassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).unassignFaces(requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.

View file

@ -197,14 +197,14 @@
const personId = selectedPersonToReassign[i]?.id;
if (personId) {
await api.faceApi.reassignFacesById({
await api.faceApi.reassignFace({
id: personId,
faceDto: { id: peopleWithFaces[i].id },
});
} else if (selectedPersonToCreate[i]) {
const { data } = await api.personApi.createPerson();
idsOfPersonToCreate.push(data.id);
await api.faceApi.reassignFacesById({
await api.faceApi.reassignFace({
id: data.id,
faceDto: { id: peopleWithFaces[i].id },
});
@ -212,14 +212,14 @@
}
for (const face of selectedPersonToAdd) {
if (face.person) {
await api.faceApi.reassignFacesById({
await api.faceApi.reassignFace({
id: face.person.id,
faceDto: { id: face.id },
});
} else {
const { data } = await api.personApi.createPerson();
idsOfPersonToCreate.push(data.id);
await api.faceApi.reassignFacesById({
await api.faceApi.reassignFace({
id: data.id,
faceDto: { id: face.id },
});

View file

@ -6,7 +6,7 @@
import { api, AssetFaceUpdateItem, type PersonResponseDto } from '@api';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import Button from '../elements/buttons/button.svelte';
import { mdiPlus, mdiMerge } from '@mdi/js';
import { mdiPlus, mdiMerge, mdiTagRemove } from '@mdi/js';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { handleError } from '$lib/utils/handle-error';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
@ -21,6 +21,7 @@
let disableButtons = false;
let showLoadingSpinnerCreate = false;
let showLoadingSpinnerReassign = false;
let showLoadingSpinnerUnassign = false;
let hasSelection = false;
let screenHeight: number;
@ -33,7 +34,9 @@
const selectedPeople: AssetFaceUpdateItem[] = [];
for (const assetId of assetIds) {
console.log('h');
selectedPeople.push({ assetId, personId: personAssets.id });
console.log(selectedPeople);
}
onMount(async () => {
@ -109,6 +112,29 @@
showLoadingSpinnerReassign = false;
dispatch('confirm');
};
const handleUnassign = async () => {
const timeout = setTimeout(() => (showLoadingSpinnerUnassign = true), 100);
try {
disableButtons = true;
await api.personApi.unassignFaces({
assetFaceUpdateDto: { data: selectedPeople },
});
notificationController.show({
message: `Un-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''}`,
type: NotificationType.Info,
});
} catch (error) {
handleError(error, 'Unable to unassign assets');
} finally {
clearTimeout(timeout);
}
showLoadingSpinnerCreate = false;
dispatch('confirm');
};
</script>
<svelte:window bind:innerHeight={screenHeight} />
@ -124,6 +150,21 @@
</svelte:fragment>
<svelte:fragment slot="trailing">
<div class="flex gap-4">
<Button
title={'Unassign selected assets to a new person'}
size={'sm'}
disabled={disableButtons || hasSelection}
on:click={() => {
handleUnassign();
}}
>
{#if !showLoadingSpinnerUnassign}
<Icon path={mdiTagRemove} size={18} />
{:else}
<LoadingSpinner />
{/if}
<span class="ml-2"> Unassign</span></Button
>
<Button
title={'Assign selected assets to a new person'}
size={'sm'}