Skip to content

Commit 14480d2

Browse files
authored
Set context user (#239)
* set context user * handler context user and interactive user changes * validate context user * remove unused import * validate context serialization * 2.23.0
1 parent c879f3a commit 14480d2

10 files changed

Lines changed: 550 additions & 317 deletions

UserService.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ApplicationBase, ApplicationService } from '@themost/common';
2+
import { DataContext } from './types';
3+
import { BehaviorSubject, Observable } from 'rxjs';
4+
5+
export declare interface OnFinalizeService {
6+
finalizeAsync(): Promise<void>;
7+
}
8+
9+
export declare interface OnRemoveUser {
10+
removeUser(name: string): Promise<void>;
11+
}
12+
13+
export declare class UserService extends ApplicationService {
14+
15+
constructor(app:any);
16+
17+
getUser(context: DataContext, name: string): Promise<any>;
18+
19+
getAnonymousUser(context: DataContext): Promise<any>;
20+
21+
getGroup(context: DataContext, name: string): Promise<any>;
22+
23+
/**
24+
* An observable that emits the anonymous user data.
25+
* This can be used to track or handle the state of an anonymous user in the application.
26+
*
27+
* @type {Observable<any>}
28+
*/
29+
anonymousUser$: Observable<any>;
30+
31+
refreshAnonymousUser$: BehaviorSubject<any>;
32+
}

UserService.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const { ApplicationService } = require('@themost/common');
2+
const { BehaviorSubject, shareReplay, switchMap, from, of } = require('rxjs');
3+
4+
class UserService extends ApplicationService {
5+
constructor(app) {
6+
super(app);
7+
8+
this.refreshAnonymousUser$ = new BehaviorSubject(void 0);
9+
10+
this.anonymousUser$ = this.refreshAnonymousUser$.pipe(switchMap((value) => {
11+
if (typeof value !== 'undefined') {
12+
return of(value);
13+
}
14+
// create a new context
15+
const context = this.getApplication().createContext();
16+
// get anonymous user
17+
return from(this.getAnonymousUser(context).finally(() => {
18+
// finalize context
19+
return context.finalizeAsync();
20+
}));
21+
}), shareReplay(1));
22+
23+
}
24+
25+
/**
26+
* @param {import('@themost/common').DataContextBase} context
27+
* @param {string} name
28+
* @returns {Promise<any>}
29+
*/
30+
getUser(context, name) {
31+
return context.model('User').asQueryable().where((x, username) => {
32+
return x.name === username;
33+
}, name).expand((x) => x.groups).silent().getItem();
34+
}
35+
36+
/**
37+
* @param {import('@themost/common').DataContextBase} context
38+
* @param {string} name
39+
* @returns {Promise<any>}
40+
*/
41+
getGroup(context, name) {
42+
return context.model('Group').asQueryable().where((x, username) => {
43+
return x.name === username;
44+
}, name).silent().getItem();
45+
}
46+
47+
/**
48+
* @param {import('@themost/common').DataContextBase} context
49+
* @returns {Promise<any>}
50+
*/
51+
getAnonymousUser(context) {
52+
return context.model('User').asQueryable().where((x) => {
53+
return x.name === 'anonymous';
54+
}).expand((x) => x.groups).silent().getItem();
55+
}
56+
57+
}
58+
59+
module.exports = {
60+
UserService
61+
};

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ export * from './UnattendedMode';
2323
export * from './data-value-resolver';
2424
export * from './ValueFormatter';
2525
export * from './select-object-query';
26+
export * from './UserService';

index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ var { ValueFormatter, ValueDialect } = require('./ValueFormatter');
9494

9595
var { SelectObjectQuery } = require('./select-object-query');
9696

97+
var { UserService } = require('./UserService');
98+
9799
module.exports = {
98100
TypeParser,
99101
PrivilegeType,
@@ -181,6 +183,7 @@ module.exports = {
181183
DataValueResolver,
182184
ValueFormatter,
183185
ValueDialect,
184-
SelectObjectQuery
186+
SelectObjectQuery,
187+
UserService
185188
};
186189

package-lock.json

Lines changed: 18 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@themost/data",
3-
"version": "2.22.0",
3+
"version": "2.23.0",
44
"description": "MOST Web Framework Codename Blueshift - Data module",
55
"main": "index.js",
66
"scripts": {
@@ -36,6 +36,7 @@
3636
"node-cache": "^1.1.0",
3737
"pluralize": "^7.0.0",
3838
"q": "^1.4.1",
39+
"rxjs": "^7.8.2",
3940
"sprintf-js": "^1.1.2",
4041
"symbol": "^0.3.1",
4142
"uuid": "^10.0.0"

spec/DataContext.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {TestApplication, TestApplication2} from './TestApplication';
2+
import {DataContext, UserService} from '@themost/data';
3+
import {firstValueFrom} from 'rxjs';
4+
5+
describe('DataContext', () => {
6+
let app: TestApplication;
7+
let context: DataContext;
8+
beforeAll((done) => {
9+
app = new TestApplication2();
10+
app.useService(UserService);
11+
context = app.createContext();
12+
return done();
13+
});
14+
afterAll(async () => {
15+
await context.finalizeAsync();
16+
await app.finalize();
17+
});
18+
19+
it('should serialize context as empty object', async () => {
20+
context.setUser({
21+
name: 'alexis.rees@example.com',
22+
});
23+
const user = await firstValueFrom(context.user$);
24+
const str = JSON.stringify(context);
25+
expect(str).toEqual('{}');
26+
})
27+
28+
it('should have user$', async () => {
29+
context.setUser(null);
30+
const user = await firstValueFrom(context.user$);
31+
expect(user?.name).toBeFalsy();
32+
context.setUser({
33+
name: 'alexis.rees@example.com'
34+
});
35+
const user2 = await firstValueFrom(context.user$);
36+
expect(user2?.name).toBe('alexis.rees@example.com');
37+
context.switchUser({
38+
name: 'james.may@example.com'
39+
});
40+
const user3 = await firstValueFrom(context.user$);
41+
expect(user3?.name).toBe('james.may@example.com');
42+
});
43+
44+
it('should have interactiveUser$', async () => {
45+
let user = await firstValueFrom(context.interactiveUser$);
46+
expect(user?.name).toBeFalsy();
47+
context.setInteractiveUser({
48+
name: 'alexis.rees@example.com'
49+
});
50+
user = await firstValueFrom(context.interactiveUser$);
51+
expect(user?.name).toBe('alexis.rees@example.com');
52+
});
53+
54+
it('should use different context', async () => {
55+
context.switchUser({
56+
name: 'james.may@example.com'
57+
});
58+
let user = await firstValueFrom(context.user$);
59+
expect(user?.name).toBe('james.may@example.com');
60+
61+
const otherContext = app.createContext();
62+
otherContext.setUser({
63+
name: 'alexis.rees@example.com'
64+
});
65+
user = await firstValueFrom(otherContext.user$);
66+
expect(user?.name).toBe('alexis.rees@example.com');
67+
user = await firstValueFrom(context.user$);
68+
expect(user?.name).toBe('james.may@example.com');
69+
70+
});
71+
72+
it('should set user', async () => {
73+
context.user = null;
74+
let user = await firstValueFrom(context.user$);
75+
expect(user?.name).toBeFalsy();
76+
context.user = {
77+
name: 'alexis.rees@example.com'
78+
};
79+
user = await firstValueFrom(context.user$);
80+
expect(user?.name).toBe('alexis.rees@example.com');
81+
context.user = {
82+
name: 'james.may@example.com'
83+
};
84+
user = await firstValueFrom(context.user$);
85+
expect(user?.name).toBe( 'james.may@example.com');
86+
});
87+
88+
it('should set user name', async () => {
89+
context.user = null;
90+
let user = await firstValueFrom(context.user$);
91+
expect(user?.name).toBeFalsy();
92+
context.user = {
93+
name: 'alexis.rees@example.com'
94+
};
95+
expect(context.user.name).toBe('alexis.rees@example.com');
96+
user = await firstValueFrom(context.user$);
97+
expect(user?.name).toBe('alexis.rees@example.com');
98+
context.user.name = 'james.may@example.com';
99+
user = await firstValueFrom(context.user$);
100+
expect(user?.name).toBe( 'james.may@example.com');
101+
});
102+
103+
});

spec/TestApplication.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IApplication, ConfigurationBase } from "@themost/common";
1+
import {IApplication, ConfigurationBase, ApplicationService, ApplicationServiceConstructor} from "@themost/common";
22
import {resolve} from 'path';
33
import {
44
DataConfigurationStrategy,
@@ -14,6 +14,12 @@ export class TestApplication extends IApplication {
1414
private _services: Map<any,any> = new Map();
1515
private readonly _configuration: ConfigurationBase;
1616

17+
useService(serviceCtor: ApplicationServiceConstructor<any>) {
18+
const ServiceClass: any = serviceCtor;
19+
this._services.set(ServiceClass.name, new ServiceClass(this));
20+
return this;
21+
}
22+
1723
useStrategy(serviceCtor: void, strategyCtor: void): this {
1824
const ServiceClass: any = serviceCtor;
1925
const StrategyClass: any = strategyCtor;
@@ -23,9 +29,15 @@ export class TestApplication extends IApplication {
2329
hasStrategy(serviceCtor: void): boolean {
2430
return this._services.has((<any>serviceCtor).name);
2531
}
32+
2633
getStrategy<T>(serviceCtor: new () => T): T {
2734
return this._services.get(serviceCtor.name);
2835
}
36+
37+
getService<T>(serviceCtor: new () => T): T {
38+
return this._services.get(serviceCtor.name);
39+
}
40+
2941
getConfiguration(): ConfigurationBase {
3042
return this._configuration;
3143
}
@@ -67,6 +79,7 @@ export class TestApplication extends IApplication {
6779
context.getConfiguration = () => {
6880
return this._configuration;
6981
};
82+
context.setApplication(this);
7083
return context;
7184
}
7285

0 commit comments

Comments
 (0)