Revert "Revert "[ACS-9166] Migrate Saved Searches to preferences API from con…" (#10594)

This reverts commit fcd6e25dc65b83929c1dbed4121c929eef666910.
This commit is contained in:
MichalKinas 2025-01-28 08:41:23 +01:00 committed by GitHub
parent 78e527f7df
commit 35aec24c0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 171 additions and 130 deletions

View File

@ -1,7 +1,7 @@
# Saved Searches Service # Saved Searches Service
Manages operations related to saving and retrieving user-defined searches in the Alfresco Process Services (APS) environment. Manages operations related to saving and retrieving user-defined searches.
## Class members ## Class members
@ -14,7 +14,7 @@ Manages operations related to saving and retrieving user-defined searches in the
#### getSavedSearches(): [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>` #### getSavedSearches(): [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>`
Fetches the file with list of saved searches either from a locally cached node ID or by querying the APS server. Then it reads the file and maps JSON objects into SavedSearches Fetches the file with list of saved searches either from a locally cached node ID or by querying the ACS server. Then it reads the file and maps JSON objects into SavedSearches
- **Returns**: - **Returns**:
- [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>` - An observable that emits the list of saved searches. - [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>` - An observable that emits the list of saved searches.
@ -51,14 +51,3 @@ this.savedSearchService.saveSearch(newSearch).subscribe((response) => {
console.log('Saved new search:', response); console.log('Saved new search:', response);
}); });
``` ```
#### Creating Saved Searches Node
When the saved searches file does not exist, it will be created:
```typescript
this.savedSearchService.createSavedSearchesNode('parent-node-id').subscribe((node) => {
console.log('Created config.json node:', node);
});
```

View File

@ -17,9 +17,9 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { AlfrescoApiServiceMock } from '../../mock';
import { NodeEntry } from '@alfresco/js-api'; import { NodeEntry } from '@alfresco/js-api';
import { SavedSearchesService } from './saved-searches.service'; import { SavedSearchesService } from './saved-searches.service';
import { AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AuthenticationService } from '@alfresco/adf-core'; import { AuthenticationService } from '@alfresco/adf-core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -28,10 +28,9 @@ describe('SavedSearchesService', () => {
let service: SavedSearchesService; let service: SavedSearchesService;
let authService: AuthenticationService; let authService: AuthenticationService;
let testUserName: string; let testUserName: string;
let getNodeContentSpy: jasmine.Spy;
const testNodeId = 'test-node-id'; const testNodeId = 'test-node-id';
const SAVED_SEARCHES_NODE_ID = 'saved-searches-node-id__'; const LOCAL_STORAGE_KEY = 'saved-searches-test-user-migrated';
const SAVED_SEARCHES_CONTENT = JSON.stringify([ const SAVED_SEARCHES_CONTENT = JSON.stringify([
{ name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 }, { name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 },
{ name: 'Search 2', description: 'Description 2', encodedUrl: 'url2', order: 1 } { name: 'Search 2', description: 'Description 2', encodedUrl: 'url2', order: 1 }
@ -59,23 +58,28 @@ describe('SavedSearchesService', () => {
service = TestBed.inject(SavedSearchesService); service = TestBed.inject(SavedSearchesService);
authService = TestBed.inject(AuthenticationService); authService = TestBed.inject(AuthenticationService);
spyOn(service.nodesApi, 'getNode').and.callFake(() => Promise.resolve({ entry: { id: testNodeId } } as NodeEntry)); spyOn(service.nodesApi, 'getNode').and.callFake(() => Promise.resolve({ entry: { id: testNodeId } } as NodeEntry));
spyOn(service.nodesApi, 'createNode').and.callFake(() => Promise.resolve({ entry: { id: 'new-node-id' } })); spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry)); spyOn(service.nodesApi, 'deleteNode').and.callFake(() => Promise.resolve());
getNodeContentSpy = spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob()); spyOn(service.preferencesApi, 'getPreference').and.callFake(() =>
Promise.resolve({ entry: { id: 'saved-searches', value: SAVED_SEARCHES_CONTENT } })
);
spyOn(service.preferencesApi, 'updatePreference').and.callFake(() =>
Promise.resolve({ entry: { id: 'saved-searches', value: SAVED_SEARCHES_CONTENT } })
);
}); });
afterEach(() => { afterEach(() => {
localStorage.removeItem(SAVED_SEARCHES_NODE_ID + testUserName); localStorage.removeItem(LOCAL_STORAGE_KEY);
}); });
it('should retrieve saved searches from the config.json file', (done) => { it('should retrieve saved searches from the preferences API', (done) => {
spyOn(authService, 'getUsername').and.callFake(() => testUserName); spyOn(authService, 'getUsername').and.callFake(() => testUserName);
spyOn(localStorage, 'getItem').and.callFake(() => testNodeId); spyOn(localStorage, 'getItem').and.callFake(() => 'true');
service.innit(); service.init();
service.getSavedSearches().subscribe((searches) => { service.getSavedSearches().subscribe((searches) => {
expect(localStorage.getItem).toHaveBeenCalledWith(SAVED_SEARCHES_NODE_ID + testUserName); expect(localStorage.getItem).toHaveBeenCalledWith(LOCAL_STORAGE_KEY);
expect(getNodeContentSpy).toHaveBeenCalledWith(testNodeId); expect(service.preferencesApi.getPreference).toHaveBeenCalledWith('-me-', 'saved-searches');
expect(searches.length).toBe(2); expect(searches.length).toBe(2);
expect(searches[0].name).toBe('Search 1'); expect(searches[0].name).toBe('Search 1');
expect(searches[1].name).toBe('Search 2'); expect(searches[1].name).toBe('Search 2');
@ -83,48 +87,43 @@ describe('SavedSearchesService', () => {
}); });
}); });
it('should create config.json file if it does not exist', (done) => { it('should automatically migrate saved searches if config.json file exists', (done) => {
const error: Error = { name: 'test', message: '{ "error": { "statusCode": 404 } }' }; spyOn(localStorage, 'setItem');
spyOn(authService, 'getUsername').and.callFake(() => testUserName); spyOn(authService, 'getUsername').and.callFake(() => testUserName);
service.nodesApi.getNode = jasmine.createSpy().and.returnValue(Promise.reject(error));
getNodeContentSpy.and.callFake(() => Promise.resolve(new Blob([''])));
service.innit();
service.getSavedSearches().subscribe((searches) => { service.getSavedSearches().subscribe((searches) => {
expect(service.nodesApi.getNode).toHaveBeenCalledWith('-my-', { relativePath: 'config.json' }); expect(service.nodesApi.getNode).toHaveBeenCalledWith('-my-', { relativePath: 'config.json' });
expect(service.nodesApi.createNode).toHaveBeenCalledWith('-my-', jasmine.objectContaining({ name: 'config.json' })); expect(service.nodesApi.getNodeContent).toHaveBeenCalledWith(testNodeId);
expect(searches.length).toBe(0); expect(localStorage.setItem).toHaveBeenCalledWith(LOCAL_STORAGE_KEY, 'true');
expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', SAVED_SEARCHES_CONTENT);
expect(service.nodesApi.deleteNode).toHaveBeenCalledWith(testNodeId, { permanent: true });
expect(searches.length).toBe(2);
done(); done();
}); });
}); });
it('should save a new search', (done) => { it('should save a new search', (done) => {
spyOn(authService, 'getUsername').and.callFake(() => testUserName); spyOn(authService, 'getUsername').and.callFake(() => testUserName);
const nodeId = 'saved-searches-node-id'; spyOn(localStorage, 'getItem').and.callFake(() => 'true');
spyOn(localStorage, 'getItem').and.callFake(() => nodeId);
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' }; const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
service.innit(); service.init();
service.saveSearch(newSearch).subscribe(() => { service.saveSearch(newSearch).subscribe(() => {
expect(service.nodesApi.updateNodeContent).toHaveBeenCalledWith(nodeId, jasmine.any(String)); expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', jasmine.any(String));
expect(service.savedSearches$).toBeDefined(); expect(service.savedSearches$).toBeDefined();
service.savedSearches$.subscribe((searches) => { done();
expect(searches.length).toBe(3);
expect(searches[2].name).toBe('Search 2');
expect(searches[2].order).toBe(2);
done();
});
}); });
}); });
it('should emit initial saved searches on subscription', (done) => { it('should emit initial saved searches on subscription', (done) => {
const nodeId = 'saved-searches-node-id'; spyOn(authService, 'getUsername').and.callFake(() => testUserName);
spyOn(localStorage, 'getItem').and.returnValue(nodeId); spyOn(localStorage, 'getItem').and.returnValue('true');
service.innit(); service.init();
service.savedSearches$.pipe().subscribe((searches) => { service.savedSearches$.pipe().subscribe((searches) => {
expect(searches.length).toBe(2); expect(searches.length).toBe(2);
expect(searches[0].name).toBe('Search 1'); expect(searches[0].name).toBe('Search 1');
expect(service.preferencesApi.getPreference).toHaveBeenCalledWith('-me-', 'saved-searches');
done(); done();
}); });
@ -133,25 +132,18 @@ describe('SavedSearchesService', () => {
it('should emit updated saved searches after saving a new search', (done) => { it('should emit updated saved searches after saving a new search', (done) => {
spyOn(authService, 'getUsername').and.callFake(() => testUserName); spyOn(authService, 'getUsername').and.callFake(() => testUserName);
spyOn(localStorage, 'getItem').and.callFake(() => testNodeId); spyOn(localStorage, 'getItem').and.callFake(() => 'true');
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' }; const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
service.innit(); service.init();
let emissionCount = 0; service.saveSearch(newSearch).subscribe(() => {
service.savedSearches$.subscribe((searches) => {
service.savedSearches$.subscribe((searches) => {
emissionCount++;
if (emissionCount === 1) {
expect(searches.length).toBe(2);
}
if (emissionCount === 2) {
expect(searches.length).toBe(3); expect(searches.length).toBe(3);
expect(searches[2].name).toBe('Search 2'); expect(searches[2].name).toBe('Search 2');
expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', jasmine.any(String));
done(); done();
} });
}); });
service.saveSearch(newSearch).subscribe();
}); });
it('should edit a search', (done) => { it('should edit a search', (done) => {
@ -190,8 +182,7 @@ describe('SavedSearchesService', () => {
*/ */
function prepareDefaultMock(): void { function prepareDefaultMock(): void {
spyOn(authService, 'getUsername').and.callFake(() => testUserName); spyOn(authService, 'getUsername').and.callFake(() => testUserName);
const nodeId = 'saved-searches-node-id'; spyOn(localStorage, 'getItem').and.callFake(() => 'true');
spyOn(localStorage, 'getItem').and.callFake(() => nodeId); service.init();
service.innit();
} }
}); });

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { NodesApi, NodeEntry } from '@alfresco/js-api'; import { NodesApi, NodeEntry, PreferencesApi } from '@alfresco/js-api';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of, from, ReplaySubject, throwError } from 'rxjs'; import { Observable, of, from, ReplaySubject, throwError } from 'rxjs';
import { catchError, concatMap, first, map, switchMap, take, tap } from 'rxjs/operators'; import { catchError, concatMap, first, map, switchMap, take, tap } from 'rxjs/operators';
@ -27,21 +27,25 @@ import { AuthenticationService } from '@alfresco/adf-core';
providedIn: 'root' providedIn: 'root'
}) })
export class SavedSearchesService { export class SavedSearchesService {
private savedSearchFileNodeId: string;
private _nodesApi: NodesApi; private _nodesApi: NodesApi;
private _preferencesApi: PreferencesApi;
get nodesApi(): NodesApi { get nodesApi(): NodesApi {
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance()); this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
return this._nodesApi; return this._nodesApi;
} }
readonly savedSearches$ = new ReplaySubject<SavedSearch[]>(1); get preferencesApi(): PreferencesApi {
this._preferencesApi = this._preferencesApi ?? new PreferencesApi(this.apiService.getInstance());
return this._preferencesApi;
}
private savedSearchFileNodeId: string; readonly savedSearches$ = new ReplaySubject<SavedSearch[]>(1);
private currentUserLocalStorageKey: string;
private createFileAttempt = false;
constructor(private readonly apiService: AlfrescoApiService, private readonly authService: AuthenticationService) {} constructor(private readonly apiService: AlfrescoApiService, private readonly authService: AuthenticationService) {}
innit(): void { init(): void {
this.fetchSavedSearches(); this.fetchSavedSearches();
} }
@ -51,20 +55,27 @@ export class SavedSearchesService {
* @returns SavedSearch list containing user saved searches * @returns SavedSearch list containing user saved searches
*/ */
getSavedSearches(): Observable<SavedSearch[]> { getSavedSearches(): Observable<SavedSearch[]> {
return this.getSavedSearchesNodeId().pipe( const savedSearchesMigrated = localStorage.getItem(this.getLocalStorageKey()) ?? '';
concatMap(() => if (savedSearchesMigrated === 'true') {
from(this.nodesApi.getNodeContent(this.savedSearchFileNodeId).then((content) => this.mapFileContentToSavedSearches(content))).pipe( return from(this.preferencesApi.getPreference('-me-', 'saved-searches')).pipe(
catchError((error) => { map((preference) => JSON.parse(preference.entry.value)),
if (!this.createFileAttempt) { catchError(() => of([]))
this.createFileAttempt = true; );
localStorage.removeItem(this.getLocalStorageKey()); } else {
return this.getSavedSearches(); return this.getSavedSearchesNodeId().pipe(
} take(1),
return throwError(() => error); concatMap(() => {
}) if (this.savedSearchFileNodeId !== '') {
) return this.migrateSavedSearches();
) } else {
); return from(this.preferencesApi.getPreference('-me-', 'saved-searches')).pipe(
map((preference) => JSON.parse(preference.entry.value)),
catchError(() => of([]))
);
}
})
);
}
} }
/** /**
@ -94,7 +105,8 @@ export class SavedSearchesService {
order: index order: index
})); }));
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSavedSearches))).pipe( return from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSavedSearches))).pipe(
map((preference) => JSON.parse(preference.entry.value)),
tap(() => this.savedSearches$.next(updatedSavedSearches)) tap(() => this.savedSearches$.next(updatedSavedSearches))
); );
}), }),
@ -123,7 +135,9 @@ export class SavedSearchesService {
this.savedSearches$.next(updatedSearches); this.savedSearches$.next(updatedSearches);
}), }),
switchMap((updatedSearches: SavedSearch[]) => switchMap((updatedSearches: SavedSearch[]) =>
from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe(
map((preference) => JSON.parse(preference.entry.value))
)
), ),
catchError((error) => { catchError((error) => {
this.savedSearches$.next(previousSavedSearches); this.savedSearches$.next(previousSavedSearches);
@ -154,7 +168,9 @@ export class SavedSearchesService {
this.savedSearches$.next(updatedSearches); this.savedSearches$.next(updatedSearches);
}), }),
switchMap((updatedSearches: SavedSearch[]) => switchMap((updatedSearches: SavedSearch[]) =>
from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe(
map((preference) => JSON.parse(preference.entry.value))
)
), ),
catchError((error) => { catchError((error) => {
this.savedSearches$.next(previousSavedSearchesOrder); this.savedSearches$.next(previousSavedSearchesOrder);
@ -185,7 +201,9 @@ export class SavedSearchesService {
}), }),
tap((savedSearches: SavedSearch[]) => this.savedSearches$.next(savedSearches)), tap((savedSearches: SavedSearch[]) => this.savedSearches$.next(savedSearches)),
switchMap((updatedSearches: SavedSearch[]) => switchMap((updatedSearches: SavedSearch[]) =>
from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe(
map((preference) => JSON.parse(preference.entry.value))
)
), ),
catchError((error) => { catchError((error) => {
this.savedSearches$.next(previousSavedSearchesOrder); this.savedSearches$.next(previousSavedSearchesOrder);
@ -196,52 +214,33 @@ export class SavedSearchesService {
} }
private getSavedSearchesNodeId(): Observable<string> { private getSavedSearchesNodeId(): Observable<string> {
const localStorageKey = this.getLocalStorageKey(); return from(this.nodesApi.getNode('-my-', { relativePath: 'config.json' })).pipe(
if (this.currentUserLocalStorageKey && this.currentUserLocalStorageKey !== localStorageKey) { first(),
this.savedSearches$.next([]); concatMap((configNode) => {
} this.savedSearchFileNodeId = configNode.entry.id;
this.currentUserLocalStorageKey = localStorageKey; return configNode.entry.id;
let savedSearchesNodeId = localStorage.getItem(this.currentUserLocalStorageKey) ?? ''; }),
if (savedSearchesNodeId === '') { catchError((error) => {
return from(this.nodesApi.getNode('-my-', { relativePath: 'config.json' })).pipe( const errorStatusCode = JSON.parse(error.message).error.statusCode;
first(), if (errorStatusCode === 404) {
concatMap((configNode) => { localStorage.setItem(this.getLocalStorageKey(), 'true');
savedSearchesNodeId = configNode.entry.id; return '';
localStorage.setItem(this.currentUserLocalStorageKey, savedSearchesNodeId); } else {
this.savedSearchFileNodeId = savedSearchesNodeId; return throwError(() => error);
return savedSearchesNodeId; }
}), })
catchError((error) => { );
const errorStatusCode = JSON.parse(error.message).error.statusCode;
if (errorStatusCode === 404) {
return this.createSavedSearchesNode('-my-').pipe(
first(),
map((node) => {
localStorage.setItem(this.currentUserLocalStorageKey, node.entry.id);
return node.entry.id;
})
);
} else {
return throwError(() => error);
}
})
);
} else {
this.savedSearchFileNodeId = savedSearchesNodeId;
return of(savedSearchesNodeId);
}
}
private createSavedSearchesNode(parentNodeId: string): Observable<NodeEntry> {
return from(this.nodesApi.createNode(parentNodeId, { name: 'config.json', nodeType: 'cm:content' }));
} }
private async mapFileContentToSavedSearches(blob: Blob): Promise<Array<SavedSearch>> { private async mapFileContentToSavedSearches(blob: Blob): Promise<Array<SavedSearch>> {
return blob.text().then((content) => (content ? JSON.parse(content) : [])); return blob
.text()
.then((content) => (content ? JSON.parse(content) : []))
.catch(() => []);
} }
private getLocalStorageKey(): string { private getLocalStorageKey(): string {
return `saved-searches-node-id__${this.authService.getUsername()}`; return `saved-searches-${this.authService.getUsername()}-migrated`;
} }
private fetchSavedSearches(): void { private fetchSavedSearches(): void {
@ -249,4 +248,14 @@ export class SavedSearchesService {
.pipe(take(1)) .pipe(take(1))
.subscribe((searches) => this.savedSearches$.next(searches)); .subscribe((searches) => this.savedSearches$.next(searches));
} }
private migrateSavedSearches(): Observable<SavedSearch[]> {
return from(this.nodesApi.getNodeContent(this.savedSearchFileNodeId).then((content) => this.mapFileContentToSavedSearches(content))).pipe(
tap((savedSearches) => {
this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(savedSearches));
localStorage.setItem(this.getLocalStorageKey(), 'true');
this.nodesApi.deleteNode(this.savedSearchFileNodeId, { permanent: true });
})
);
}
} }

View File

@ -84,4 +84,25 @@ export class PreferencesApi extends BaseApi {
queryParams queryParams
}); });
} }
updatePreference(personId: string, preferenceName: string, preferenceValue: string): Promise<PreferenceEntry> {
throwIfNotDefined(personId, 'personId');
throwIfNotDefined(preferenceName, 'preferenceName');
throwIfNotDefined(preferenceValue, 'preferenceValue');
const pathParams = {
personId,
preferenceName
};
const bodyParam = {
value: preferenceValue
};
return this.put({
path: '/people/{personId}/preferences/{preferenceName}',
pathParams,
bodyParam: bodyParam
});
}
} }

View File

@ -2,10 +2,11 @@
All URIs are relative to *https://localhost/alfresco/api/-default-/public/alfresco/versions/1* All URIs are relative to *https://localhost/alfresco/api/-default-/public/alfresco/versions/1*
| Method | HTTP request | Description | | Method | HTTP request | Description |
|-------------------------------------|---------------------------------------------------------|------------------| |---------------------------------------|----------------------------------------------------------|-------------------|
| [getPreference](#getPreference) | **GET** /people/{personId}/preferences/{preferenceName} | Get a preference | | [getPreference](#getPreference) | **GET** /people/{personId}/preferences/{preferenceName} | Get a preference |
| [listPreferences](#listPreferences) | **GET** /people/{personId}/preferences | List preferences | | [listPreferences](#listPreferences) | **GET** /people/{personId}/preferences | List preferences |
| [updatePreference](#updatePreference) | **POST** /people/{personId}/preferences/{preferenceName} | Update preference |
## getPreference ## getPreference
@ -71,6 +72,36 @@ preferencesApi.listPreferences(`<personId>`, opts).then((data) => {
}); });
``` ```
## updatePreference
Update preference
You can use the `-me-` string in place of `<personId>` to specify the currently authenticated user.
### Parameters
| Name | Type | Description |
|---------------------|--------|-----------------------------|
| **personId** | string | The identifier of a person. |
| **preferenceName** | string | The name of the preference. |
| **preferenceValue** | string | New preference value. |
**Return type**: [PreferenceEntry](#PreferenceEntry)
**Example**
```javascript
import { AlfrescoApi, PreferencesApi } from '@alfresco/js-api';
const alfrescoApi = new AlfrescoApi(/*..*/);
const preferencesApi = new PreferencesApi(alfrescoApi);
const newPreferenceValue = 'test';
preferencesApi.updatePreference(`<personId>`, `<preferenceName>`, newPreferenceValue).then((data) => {
console.log('API called successfully. Returned data: ' + data);
});
```
# Models # Models
## PreferencePaging ## PreferencePaging
@ -105,4 +136,4 @@ preferencesApi.listPreferences(`<personId>`, opts).then((data) => {
| Name | Type | Description | | Name | Type | Description |
|--------|--------|----------------------------------------------------------------------| |--------|--------|----------------------------------------------------------------------|
| **id** | string | The unique id of the preference | | **id** | string | The unique id of the preference |
| value | string | The value of the preference. Note that this can be of any JSON type. | | value | string | The value of the preference. Note that this can be of any JSON type. |