Skip to content

Commit e58343c

Browse files
committed
chore: refactor
1 parent 7e00fb0 commit e58343c

8 files changed

Lines changed: 313 additions & 48 deletions

File tree

tools/user-scripts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"license": "ISC",
88
"author": "",
99
"main": "./dist/index.js",
10+
"files": ["dist", "!dist/.tsbuildinfo"],
1011
"scripts": {
1112
"build": "pnpm nx nxBuild",
1213
"lint": "pnpm nx nxLint",

tools/user-scripts/src/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@ import { UserRuntime, UserService } from './lib/user-scripts.js';
44
export const deleteUser = (baseUrl: string, envId: string, userId: string, accessToken: string) =>
55
UserRuntime.runPromise(
66
UserService.pipe(
7-
Effect.flatMap((userService) => userService.deleteUser(baseUrl, envId, userId, accessToken)),
7+
Effect.flatMap((userService) =>
8+
Effect.retry(userService.deleteUser(baseUrl, envId, userId, accessToken), {
9+
times: 5,
10+
}),
11+
),
12+
),
13+
);
14+
15+
export const getUsers = (
16+
baseUrl: string,
17+
envId: string,
18+
accessToken: string,
19+
filterTerm: string,
20+
query: string,
21+
) =>
22+
UserRuntime.runPromise(
23+
UserService.pipe(
24+
Effect.flatMap((userService) =>
25+
Effect.retry(userService.getUsers(baseUrl, envId, accessToken, filterTerm, query), {
26+
times: 5,
27+
}),
28+
),
829
),
930
);

tools/user-scripts/src/lib/schemas.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,98 @@ export const CreateUserResponse = Schema.Struct({
7070
updatedAt: Schema.String,
7171
username: Schema.String,
7272
});
73+
export const getUsersResponse = Schema.Struct({
74+
_links: Schema.Struct({
75+
self: Schema.Struct({
76+
href: Schema.String,
77+
}),
78+
next: Schema.optional(
79+
Schema.Struct({
80+
href: Schema.String,
81+
}),
82+
),
83+
}),
84+
_embedded: Schema.Struct({
85+
users: Schema.Array(
86+
Schema.Struct({
87+
_links: Schema.Struct({
88+
self: Schema.Struct({
89+
href: Schema.String,
90+
}),
91+
password: Schema.Struct({
92+
href: Schema.String,
93+
}),
94+
'password.set': Schema.Struct({
95+
href: Schema.String,
96+
}),
97+
'password.reset': Schema.Struct({
98+
href: Schema.String,
99+
}),
100+
'password.check': Schema.Struct({
101+
href: Schema.String,
102+
}),
103+
'password.recover': Schema.Struct({
104+
href: Schema.String,
105+
}),
106+
'account.sendVerificationCode': Schema.Struct({
107+
href: Schema.String,
108+
}),
109+
linkedAccounts: Schema.Struct({
110+
href: Schema.String,
111+
}),
112+
}),
113+
_embedded: Schema.Struct({
114+
password: Schema.Struct({
115+
environment: Schema.Struct({
116+
id: Schema.String,
117+
}),
118+
user: Schema.Struct({
119+
id: Schema.String,
120+
}),
121+
passwordPolicy: Schema.Struct({
122+
id: Schema.String,
123+
}),
124+
status: Schema.String,
125+
lastChangedAt: Schema.String,
126+
}),
127+
}),
128+
id: Schema.String,
129+
environment: Schema.Struct({
130+
id: Schema.String,
131+
}),
132+
account: Schema.Struct({
133+
canAuthenticate: Schema.Boolean,
134+
status: Schema.String,
135+
}),
136+
createdAt: Schema.String,
137+
email: Schema.String,
138+
enabled: Schema.Boolean,
139+
identityProvider: Schema.Struct({
140+
type: Schema.String,
141+
}),
142+
lastSignOn: Schema.optional(
143+
Schema.Struct({
144+
at: Schema.String,
145+
remoteIp: Schema.String,
146+
}),
147+
),
148+
lifecycle: Schema.Struct({
149+
status: Schema.String,
150+
}),
151+
mfaEnabled: Schema.Boolean,
152+
name: Schema.Struct({
153+
given: Schema.String,
154+
family: Schema.String,
155+
}),
156+
population: Schema.Struct({
157+
id: Schema.String,
158+
}),
159+
updatedAt: Schema.String,
160+
username: Schema.String,
161+
verifyStatus: Schema.String,
162+
}),
163+
),
164+
}),
165+
count: Schema.Number,
166+
size: Schema.Number,
167+
});
Lines changed: 97 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
import { describe, expect, it } from '@effect/vitest';
2-
import { UnexpectedStatus, UserService } from './user-scripts.js';
2+
import { GetUsersError, UnexpectedStatus, UserService } from './user-scripts.js';
33
import { Effect, Layer } from 'effect';
44
import { HttpClient, HttpClientResponse } from '@effect/platform';
55

6-
class NoContentResponse extends Response {
7-
constructor(status?: number) {
8-
super(null, {
9-
status: status || 204,
10-
statusText: 'No Content',
11-
});
6+
describe('deleteUser', () => {
7+
class NoContentResponse extends Response {
8+
constructor(status?: number) {
9+
super(null, {
10+
status: status || 204,
11+
statusText: 'No Content',
12+
});
13+
}
1214
}
13-
}
14-
15-
const TestHttpClient = Layer.succeed(
16-
HttpClient.HttpClient,
17-
HttpClient.make((request) => {
18-
return Effect.sync(function () {
19-
return HttpClientResponse.fromWeb(request, new NoContentResponse());
20-
});
21-
}),
22-
);
23-
const TestHttpClient200 = Layer.succeed(
24-
HttpClient.HttpClient,
25-
HttpClient.make((request) => {
26-
return Effect.sync(function () {
27-
return HttpClientResponse.fromWeb(request, new NoContentResponse(200));
28-
});
29-
}),
30-
);
31-
32-
const TestLayer = UserService.DefaultWithoutDependencies.pipe(Layer.provide(TestHttpClient));
33-
const TestLayerFail = UserService.DefaultWithoutDependencies.pipe(Layer.provide(TestHttpClient200));
3415

35-
describe('deleteUser', () => {
16+
const TestHttpClient = Layer.succeed(
17+
HttpClient.HttpClient,
18+
HttpClient.make((request) => {
19+
return Effect.sync(function () {
20+
return HttpClientResponse.fromWeb(request, new NoContentResponse());
21+
});
22+
}),
23+
);
24+
const TestHttpClient200 = Layer.succeed(
25+
HttpClient.HttpClient,
26+
HttpClient.make((request) => {
27+
return Effect.sync(function () {
28+
return HttpClientResponse.fromWeb(request, new NoContentResponse(200));
29+
});
30+
}),
31+
);
32+
33+
const TestLayer = UserService.DefaultWithoutDependencies.pipe(Layer.provide(TestHttpClient));
34+
const TestLayerFail = UserService.DefaultWithoutDependencies.pipe(
35+
Layer.provide(TestHttpClient200),
36+
);
37+
3638
it.effect('should delete a user', () =>
3739
Effect.gen(function* () {
3840
const userServices = yield* UserService;
@@ -55,3 +57,69 @@ describe('deleteUser', () => {
5557
}).pipe(Effect.provide(TestLayerFail)),
5658
);
5759
});
60+
61+
describe('getUsers', () => {
62+
const TestHttpClient = Layer.succeed(
63+
HttpClient.HttpClient,
64+
HttpClient.make((request) => {
65+
return Effect.sync(function () {
66+
return HttpClientResponse.fromWeb(
67+
request,
68+
new Response(
69+
JSON.stringify({
70+
_links: {
71+
self: {
72+
href: 'http://localhost:9000/environments/123/users',
73+
},
74+
},
75+
_embedded: {
76+
users: [],
77+
},
78+
count: 0,
79+
size: 0,
80+
}),
81+
),
82+
);
83+
});
84+
}),
85+
);
86+
87+
const TestLayer = UserService.DefaultWithoutDependencies.pipe(Layer.provide(TestHttpClient));
88+
89+
it.effect('should get users', () =>
90+
Effect.gen(function* () {
91+
const userServices = yield* UserService;
92+
93+
const res = yield* userServices.getUsers(
94+
'http://localhost:9000',
95+
'123',
96+
'abc',
97+
'email',
98+
'wingar_helena',
99+
);
100+
101+
expect(res).toEqual({
102+
_links: {
103+
self: {
104+
href: 'http://localhost:9000/environments/123/users',
105+
},
106+
},
107+
_embedded: {
108+
users: [],
109+
},
110+
count: 0,
111+
size: 0,
112+
});
113+
}).pipe(Effect.provide(TestLayer)),
114+
);
115+
it('should handle errors', () =>
116+
Effect.gen(function* () {
117+
const userServices = yield* UserService;
118+
119+
const res = yield* Effect.flip(
120+
userServices.getUsers('http://localhost:9000', '123', 'abc', 'email', 'wingar_helena'),
121+
);
122+
123+
expect(res).toBeInstanceOf(GetUsersError);
124+
}).pipe(Effect.provide(TestLayer)));
125+
});

tools/user-scripts/src/lib/user-scripts.ts

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { HttpClient, HttpClientRequest } from '@effect/platform';
1+
import { HttpClient, HttpClientRequest, HttpClientResponse } from '@effect/platform';
22
import { Data, Effect, ManagedRuntime } from 'effect';
33
import { NodeHttpClient } from '@effect/platform-node';
4+
import { getUsersResponse } from './schemas.js';
45

56
export class UnexpectedStatus extends Data.TaggedError('UnExpectedStatus')<{
67
message: string;
@@ -12,6 +13,16 @@ export class RequiresServerUrl extends Data.TaggedError('RequiresServerUrl')<{
1213
cause: string;
1314
}> {}
1415

16+
export class GetUsersError extends Data.TaggedError('GetUsersError')<{
17+
message: string;
18+
cause: string;
19+
}> {}
20+
21+
export class DeleteUserError extends Data.TaggedError('DeleteUserError')<{
22+
message: string;
23+
cause: string;
24+
}> {}
25+
1526
export class UserService extends Effect.Service<UserService>()('@users/service', {
1627
dependencies: [NodeHttpClient.layerUndici],
1728
effect: Effect.gen(function* () {
@@ -20,16 +31,34 @@ export class UserService extends Effect.Service<UserService>()('@users/service',
2031
return {
2132
deleteUser: (baseUrl: string, envId: string, userId: string, accessToken: string) =>
2233
Effect.gen(function* () {
23-
const baseRequest = HttpClientRequest.make('POST')(baseUrl).pipe(
24-
HttpClientRequest.setHeader('Content-Type', 'application/json'),
25-
);
26-
const request = baseRequest.pipe(
27-
HttpClientRequest.setMethod('DELETE'),
28-
HttpClientRequest.appendUrl(`/environments/${envId}/users/${userId}`),
29-
HttpClientRequest.bearerToken(accessToken),
30-
);
34+
const response = yield* HttpClientRequest.post(baseUrl)
35+
.pipe(
36+
HttpClientRequest.setHeader('Content-Type', 'application/json'),
37+
HttpClientRequest.setMethod('DELETE'),
38+
HttpClientRequest.appendUrl(`/environments/${envId}/users/${userId}`),
39+
HttpClientRequest.bearerToken(accessToken),
40+
client.execute,
41+
Effect.flatMap(HttpClientResponse.filterStatusOk),
42+
)
43+
.pipe(
44+
Effect.catchTag('ResponseError', (e) =>
45+
Effect.fail(
46+
new DeleteUserError({
47+
message: `Failed to delete user, error in response: ${e}`,
48+
cause: e.message,
49+
}),
50+
),
51+
),
52+
Effect.catchTag('RequestError', (e) =>
53+
Effect.fail(
54+
new DeleteUserError({
55+
message: `Failed to delete user, error in request: ${e}`,
56+
cause: e.message,
57+
}),
58+
),
59+
),
60+
);
3161

32-
const response = yield* client.execute(request);
3362
/**
3463
* Docs says we should expect a 204 response for success
3564
*/
@@ -44,6 +73,49 @@ export class UserService extends Effect.Service<UserService>()('@users/service',
4473

4574
return response;
4675
}),
76+
getUsers: (
77+
baseUrl: string,
78+
envId: string,
79+
accessToken: string,
80+
filterTerm: string,
81+
query: string,
82+
) =>
83+
HttpClientRequest.get(baseUrl)
84+
.pipe(
85+
HttpClientRequest.setHeader('Content-Type', 'application/json'),
86+
HttpClientRequest.appendUrl(`/environments/${envId}/users`),
87+
HttpClientRequest.appendUrlParam('filter', `${filterTerm} eq "${query}"`),
88+
HttpClientRequest.bearerToken(accessToken),
89+
client.execute,
90+
Effect.flatMap(HttpClientResponse.filterStatusOk),
91+
Effect.flatMap(HttpClientResponse.schemaBodyJson(getUsersResponse)),
92+
)
93+
.pipe(
94+
Effect.catchTag('ResponseError', (e) =>
95+
Effect.fail(
96+
new GetUsersError({
97+
message: `Failed to get users, error in response: ${e}`,
98+
cause: e.message,
99+
}),
100+
),
101+
),
102+
Effect.catchTag('RequestError', (e) =>
103+
Effect.fail(
104+
new GetUsersError({
105+
message: `Failed to get users, error in request: ${e}`,
106+
cause: e.message,
107+
}),
108+
),
109+
),
110+
Effect.catchTag('ParseError', (e) =>
111+
Effect.fail(
112+
new GetUsersError({
113+
message: `Failed to parse response for users: ${e}`,
114+
cause: e.message,
115+
}),
116+
),
117+
),
118+
),
47119
};
48120
}),
49121
}) {}

0 commit comments

Comments
 (0)