@apito-io/js-admin-sdk 2.1.2 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/dist/index.d.mts +107 -7
- package/dist/index.d.ts +107 -7
- package/dist/index.js +95 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/client.test.ts +62 -4
- package/src/client.ts +298 -15
- package/src/index.ts +3 -0
- package/src/types.ts +78 -1
- package/src/version.ts +1 -1
|
@@ -230,10 +230,7 @@ describe('ApitoClient', () => {
|
|
|
230
230
|
const client = getTestClient();
|
|
231
231
|
|
|
232
232
|
try {
|
|
233
|
-
const token = await client.generateTenantToken(
|
|
234
|
-
'ak_4HESWVQEXE7V4GVGGDRYGXVWXSCAJL44TAUICSLBPQTOB6CJ53KTU3GUOEXJUIXVAKFMM2BDRJRWWPKEN3DRA3HDLZUY4NZMVLFJUIK5H4BWLY26AUKDOHPZE2ENGJNCXPPPEBKCNLTUXXUFUKVDGYJ2H6CZCSMQCY5KSCYNJVYBXVJBYE6O7C73DI3NV7Q',
|
|
235
|
-
'ba0ee756-6aea-43a6-b052-c7baab3da91c'
|
|
236
|
-
);
|
|
233
|
+
const token = await client.generateTenantToken('ba0ee756-6aea-43a6-b052-c7baab3da91c');
|
|
237
234
|
|
|
238
235
|
expect(token).toBeDefined();
|
|
239
236
|
expect(typeof token).toBe('string');
|
|
@@ -245,6 +242,67 @@ describe('ApitoClient', () => {
|
|
|
245
242
|
}, 30000);
|
|
246
243
|
});
|
|
247
244
|
|
|
245
|
+
describe('Tenant users (Pro)', () => {
|
|
246
|
+
it('should search tenant users when APITO_PROJECT_ID is set', async () => {
|
|
247
|
+
const projectId = process.env.APITO_PROJECT_ID;
|
|
248
|
+
if (!projectId) {
|
|
249
|
+
console.log('Tenant users (Pro): skipped — set APITO_PROJECT_ID');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const client = getTestClient();
|
|
253
|
+
try {
|
|
254
|
+
const { count, users } = await client.searchTenantUsers(projectId, 10, 0);
|
|
255
|
+
expect(count).toBeGreaterThanOrEqual(0);
|
|
256
|
+
console.log('✅ searchTenantUsers:', count, users?.length);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.log('searchTenantUsers failed (may be expected):', error);
|
|
259
|
+
}
|
|
260
|
+
}, 30000);
|
|
261
|
+
|
|
262
|
+
it('should search tenants by domain when APITO_PROJECT_ID is set', async () => {
|
|
263
|
+
const projectId = process.env.APITO_PROJECT_ID;
|
|
264
|
+
if (!projectId) {
|
|
265
|
+
console.log('searchTenantsByDomain: skipped — set APITO_PROJECT_ID');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const client = getTestClient();
|
|
269
|
+
try {
|
|
270
|
+
const { tenant } = await client.searchTenantsByDomain(projectId, 'example.invalid');
|
|
271
|
+
expect(tenant === null || typeof tenant?.id === 'string').toBe(true);
|
|
272
|
+
console.log('✅ searchTenantsByDomain:', tenant ? 'match' : 'no match');
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.log('searchTenantsByDomain failed (may be expected):', error);
|
|
275
|
+
}
|
|
276
|
+
}, 30000);
|
|
277
|
+
|
|
278
|
+
it('should login when APITO_PROJECT_ID and credentials are set', async () => {
|
|
279
|
+
const projectId = process.env.APITO_PROJECT_ID;
|
|
280
|
+
const email = process.env.APITO_TENANT_EMAIL ?? '';
|
|
281
|
+
const phone = process.env.APITO_TENANT_PHONE ?? '';
|
|
282
|
+
const password = process.env.APITO_TENANT_PASSWORD;
|
|
283
|
+
if (!projectId || !(email.trim() || phone.trim()) || !password) {
|
|
284
|
+
console.log(
|
|
285
|
+
'loginTenantUser skipped — set APITO_PROJECT_ID, APITO_TENANT_PASSWORD, and APITO_TENANT_EMAIL and/or APITO_TENANT_PHONE'
|
|
286
|
+
);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const client = getTestClient();
|
|
290
|
+
try {
|
|
291
|
+
const login = await client.loginTenantUser({
|
|
292
|
+
projectId,
|
|
293
|
+
password,
|
|
294
|
+
...(email.trim() ? { email: email.trim() } : {}),
|
|
295
|
+
...(phone.trim() ? { phone: phone.trim() } : {}),
|
|
296
|
+
});
|
|
297
|
+
expect(login.token).toBeDefined();
|
|
298
|
+
expect(login.token.length).toBeGreaterThan(0);
|
|
299
|
+
console.log('✅ loginTenantUser token length:', login.token.length);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.log('loginTenantUser failed (may be expected):', error);
|
|
302
|
+
}
|
|
303
|
+
}, 30000);
|
|
304
|
+
});
|
|
305
|
+
|
|
248
306
|
describe('Debug', () => {
|
|
249
307
|
it('should send debug data', async () => {
|
|
250
308
|
const client = getTestClient();
|
package/src/client.ts
CHANGED
|
@@ -11,6 +11,14 @@ import {
|
|
|
11
11
|
ApitoError,
|
|
12
12
|
ValidationError,
|
|
13
13
|
InjectedDBOperationInterface,
|
|
14
|
+
TenantLoginResponse,
|
|
15
|
+
TenantUser,
|
|
16
|
+
TenantUsersResponse,
|
|
17
|
+
TenantByDomainResponse,
|
|
18
|
+
TenantCatalogSearchRow,
|
|
19
|
+
TenantLoginParams,
|
|
20
|
+
CreateTenantUserParams,
|
|
21
|
+
UpdateTenantUserParams,
|
|
14
22
|
} from './types';
|
|
15
23
|
|
|
16
24
|
/**
|
|
@@ -32,7 +40,9 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
32
40
|
timeout: config.timeout || 30000,
|
|
33
41
|
headers: {
|
|
34
42
|
'Content-Type': 'application/json',
|
|
35
|
-
'
|
|
43
|
+
...(this.apiKey.startsWith('cli-') || this.apiKey.startsWith('sdk-')
|
|
44
|
+
? { 'X-Apito-Sync-Key': this.apiKey }
|
|
45
|
+
: { 'X-Apito-Key': this.apiKey }),
|
|
36
46
|
},
|
|
37
47
|
...config.httpClient,
|
|
38
48
|
});
|
|
@@ -60,7 +70,9 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
60
70
|
|
|
61
71
|
const headers: Record<string, string> = {
|
|
62
72
|
'Content-Type': 'application/json',
|
|
63
|
-
'
|
|
73
|
+
...(this.apiKey.startsWith('cli-') || this.apiKey.startsWith('sdk-')
|
|
74
|
+
? { 'X-Apito-Sync-Key': this.apiKey }
|
|
75
|
+
: { 'X-Apito-Key': this.apiKey }),
|
|
64
76
|
};
|
|
65
77
|
|
|
66
78
|
if (options?.tenantId || this.tenantId) {
|
|
@@ -96,28 +108,35 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
/**
|
|
99
|
-
* Generate a tenant-scoped API key
|
|
111
|
+
* Generate a tenant-scoped API key. Matches engine `generateTenantToken`: `tenant_id`, `duration`, optional `role`.
|
|
112
|
+
* Auth uses `X-Apito-Key` (client `apiKey`).
|
|
100
113
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
114
|
+
* @param tenantId Catalog tenant id (`tenant_id` in the mutation).
|
|
115
|
+
* @param duration Expiry calendar day `YYYY-MM-DD`. If omitted/empty, defaults to one year ahead in UTC.
|
|
116
|
+
* @param role Optional token role; if omitted/empty, the engine defaults to `admin`.
|
|
103
117
|
*/
|
|
104
|
-
async generateTenantToken(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
async generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string> {
|
|
119
|
+
let dur = (duration ?? '').trim();
|
|
120
|
+
if (!dur) {
|
|
121
|
+
const d = new Date();
|
|
122
|
+
dur = `${d.getUTCFullYear() + 1}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(
|
|
123
|
+
d.getUTCDate()
|
|
124
|
+
).padStart(2, '0')}`;
|
|
125
|
+
}
|
|
111
126
|
|
|
112
127
|
const query = `
|
|
113
|
-
mutation GenerateTenantToken($tenantId: String!, $duration: String
|
|
114
|
-
generateTenantToken(tenant_id: $tenantId, duration: $duration) {
|
|
128
|
+
mutation GenerateTenantToken($tenantId: String!, $duration: String!, $role: String) {
|
|
129
|
+
generateTenantToken(tenant_id: $tenantId, duration: $duration, role: $role) {
|
|
115
130
|
token
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
133
|
`;
|
|
119
134
|
|
|
120
|
-
const variables = {
|
|
135
|
+
const variables: Record<string, any> = {
|
|
136
|
+
tenantId,
|
|
137
|
+
duration: dur,
|
|
138
|
+
role: role !== undefined && role.trim() !== '' ? role.trim() : null,
|
|
139
|
+
};
|
|
121
140
|
const response = await this.executeGraphQL(query, variables, { tenantId });
|
|
122
141
|
|
|
123
142
|
const data = response.data?.generateTenantToken;
|
|
@@ -128,6 +147,270 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
128
147
|
return data.token;
|
|
129
148
|
}
|
|
130
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Tenant catalog login (system GraphQL `loginTenantUser`). Password path: pass `password` and `email` or `phone` per project Authentication settings. Google OAuth path: `authMethod: 'google'` with `code` and `state` from the redirect; call `tenantGoogleOAuthState(projectId)` before opening Google to obtain `state`.
|
|
152
|
+
*/
|
|
153
|
+
async loginTenantUser(params: TenantLoginParams): Promise<TenantLoginResponse> {
|
|
154
|
+
const authMethod = (params.authMethod ?? 'general').trim().toLowerCase() || 'general';
|
|
155
|
+
const variables: Record<string, any> = {
|
|
156
|
+
project_id: params.projectId,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (authMethod === 'google') {
|
|
160
|
+
variables.auth_method = 'google';
|
|
161
|
+
const code = (params.code ?? '').trim();
|
|
162
|
+
const state = (params.state ?? '').trim();
|
|
163
|
+
if (!code || !state) {
|
|
164
|
+
throw new ValidationError('code and state are required for Google login');
|
|
165
|
+
}
|
|
166
|
+
variables.code = code;
|
|
167
|
+
variables.state = state;
|
|
168
|
+
} else {
|
|
169
|
+
const password = (params.password ?? '').trim();
|
|
170
|
+
if (!password) {
|
|
171
|
+
throw new ValidationError('password is required');
|
|
172
|
+
}
|
|
173
|
+
variables.password = password;
|
|
174
|
+
const email = (params.email ?? '').trim();
|
|
175
|
+
const phone = (params.phone ?? '').trim();
|
|
176
|
+
if (!email && !phone) {
|
|
177
|
+
throw new ValidationError('email or phone is required');
|
|
178
|
+
}
|
|
179
|
+
if (email) variables.email = email;
|
|
180
|
+
if (phone) variables.phone = phone;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const query = `
|
|
184
|
+
query LoginTenantUser($project_id: String!, $password: String, $auth_method: String, $email: String, $phone: String, $code: String, $state: String) {
|
|
185
|
+
loginTenantUser(project_id: $project_id, password: $password, auth_method: $auth_method, email: $email, phone: $phone, code: $code, state: $state) {
|
|
186
|
+
token
|
|
187
|
+
user {
|
|
188
|
+
id
|
|
189
|
+
email
|
|
190
|
+
phone
|
|
191
|
+
role
|
|
192
|
+
provider
|
|
193
|
+
tenant_id
|
|
194
|
+
status
|
|
195
|
+
created_at
|
|
196
|
+
updated_at
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
`;
|
|
201
|
+
const response = await this.executeGraphQL(query, variables);
|
|
202
|
+
const raw = response.data?.loginTenantUser;
|
|
203
|
+
if (!raw?.token) {
|
|
204
|
+
throw new ValidationError('Invalid response format for loginTenantUser');
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
token: raw.token as string,
|
|
208
|
+
user: raw.user as TenantUser | undefined,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Signed OAuth state for tenant Google login (system query `tenantGoogleOAuthState`). Use in the authorize URL together with project `google_client_id` and the configured redirect URI.
|
|
214
|
+
*/
|
|
215
|
+
async tenantGoogleOAuthState(projectId: string): Promise<{ state: string }> {
|
|
216
|
+
const query = `
|
|
217
|
+
query TenantGoogleOAuthState($project_id: String!) {
|
|
218
|
+
tenantGoogleOAuthState(project_id: $project_id) {
|
|
219
|
+
state
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
const variables = { project_id: projectId };
|
|
224
|
+
const response = await this.executeGraphQL(query, variables);
|
|
225
|
+
const raw = response.data?.tenantGoogleOAuthState;
|
|
226
|
+
const state = typeof raw?.state === 'string' ? raw.state.trim() : '';
|
|
227
|
+
if (!state) {
|
|
228
|
+
throw new ValidationError('Invalid response format for tenantGoogleOAuthState');
|
|
229
|
+
}
|
|
230
|
+
return { state };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Search tenant users for a project.
|
|
235
|
+
*/
|
|
236
|
+
async searchTenantUsers(
|
|
237
|
+
projectId: string,
|
|
238
|
+
limit?: number,
|
|
239
|
+
offset?: number
|
|
240
|
+
): Promise<TenantUsersResponse> {
|
|
241
|
+
const query = `
|
|
242
|
+
query SearchTenantUsers($project_id: String!, $limit: Int, $offset: Int) {
|
|
243
|
+
searchTenantUsers(project_id: $project_id, limit: $limit, offset: $offset) {
|
|
244
|
+
count
|
|
245
|
+
users {
|
|
246
|
+
id
|
|
247
|
+
email
|
|
248
|
+
phone
|
|
249
|
+
role
|
|
250
|
+
provider
|
|
251
|
+
tenant_id
|
|
252
|
+
status
|
|
253
|
+
created_at
|
|
254
|
+
updated_at
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
const variables: Record<string, any> = { project_id: projectId };
|
|
260
|
+
if (limit !== undefined) variables.limit = limit;
|
|
261
|
+
if (offset !== undefined) variables.offset = offset;
|
|
262
|
+
const response = await this.executeGraphQL(query, variables);
|
|
263
|
+
const raw = response.data?.searchTenantUsers;
|
|
264
|
+
if (!raw) {
|
|
265
|
+
throw new ValidationError('Invalid response format for searchTenantUsers');
|
|
266
|
+
}
|
|
267
|
+
let count = 0;
|
|
268
|
+
if (typeof raw.count === 'number') {
|
|
269
|
+
count = raw.count;
|
|
270
|
+
}
|
|
271
|
+
const users = (raw.users ?? []) as TenantUser[];
|
|
272
|
+
return { users, count };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Resolve the single SaaS catalog tenant for an exact domain match in the project (`tenant` null if none).
|
|
277
|
+
*/
|
|
278
|
+
async searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse> {
|
|
279
|
+
const query = `
|
|
280
|
+
query SearchTenantsByDomain($project_id: String!, $domain: String!) {
|
|
281
|
+
searchTenantsByDomain(project_id: $project_id, domain: $domain) {
|
|
282
|
+
tenant {
|
|
283
|
+
id
|
|
284
|
+
name
|
|
285
|
+
status
|
|
286
|
+
domain
|
|
287
|
+
data
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
`;
|
|
292
|
+
const variables: Record<string, any> = {
|
|
293
|
+
project_id: projectId,
|
|
294
|
+
domain,
|
|
295
|
+
};
|
|
296
|
+
const response = await this.executeGraphQL(query, variables);
|
|
297
|
+
const raw = response.data?.searchTenantsByDomain;
|
|
298
|
+
if (!raw) {
|
|
299
|
+
throw new ValidationError('Invalid response format for searchTenantsByDomain');
|
|
300
|
+
}
|
|
301
|
+
const tenant = (raw.tenant ?? null) as TenantCatalogSearchRow | null;
|
|
302
|
+
return { tenant };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a tenant catalog user (local password). Use `email` and/or `phone` per engine validation for the project identifier mode.
|
|
307
|
+
*/
|
|
308
|
+
async createTenantUser(
|
|
309
|
+
projectId: string,
|
|
310
|
+
params: CreateTenantUserParams
|
|
311
|
+
): Promise<TenantUser> {
|
|
312
|
+
const password = (params.password ?? '').trim();
|
|
313
|
+
if (!password) {
|
|
314
|
+
throw new ValidationError('password is required');
|
|
315
|
+
}
|
|
316
|
+
const query = `
|
|
317
|
+
mutation CreateTenantUser($project_id: String!, $password: String!, $role: String, $email: String, $phone: String) {
|
|
318
|
+
createTenantUser(project_id: $project_id, password: $password, role: $role, email: $email, phone: $phone) {
|
|
319
|
+
id
|
|
320
|
+
email
|
|
321
|
+
phone
|
|
322
|
+
role
|
|
323
|
+
provider
|
|
324
|
+
tenant_id
|
|
325
|
+
status
|
|
326
|
+
created_at
|
|
327
|
+
updated_at
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
const variables: Record<string, any> = {
|
|
332
|
+
project_id: projectId,
|
|
333
|
+
password,
|
|
334
|
+
};
|
|
335
|
+
const role = (params.role ?? '').trim();
|
|
336
|
+
if (role) variables.role = role;
|
|
337
|
+
const email = (params.email ?? '').trim();
|
|
338
|
+
if (email) variables.email = email;
|
|
339
|
+
const phone = (params.phone ?? '').trim();
|
|
340
|
+
if (phone) variables.phone = phone;
|
|
341
|
+
const response = await this.executeGraphQL(query, variables);
|
|
342
|
+
const u = response.data?.createTenantUser;
|
|
343
|
+
if (!u?.id) {
|
|
344
|
+
throw new ValidationError('Invalid response format for createTenantUser');
|
|
345
|
+
}
|
|
346
|
+
return u as TenantUser;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Update a tenant catalog user. Project scope is implied by the API key. Only include fields to change.
|
|
351
|
+
*/
|
|
352
|
+
async updateTenantUser(userId: string, params: UpdateTenantUserParams): Promise<TenantUser> {
|
|
353
|
+
const uid = (userId ?? '').trim();
|
|
354
|
+
if (!uid) {
|
|
355
|
+
throw new ValidationError('userId is required');
|
|
356
|
+
}
|
|
357
|
+
if (
|
|
358
|
+
params.email === undefined &&
|
|
359
|
+
params.phone === undefined &&
|
|
360
|
+
params.password === undefined &&
|
|
361
|
+
params.role === undefined
|
|
362
|
+
) {
|
|
363
|
+
throw new ValidationError('at least one field must be provided');
|
|
364
|
+
}
|
|
365
|
+
const query = `
|
|
366
|
+
mutation UpdateTenantUser($user_id: String!, $email: String, $phone: String, $password: String, $role: String) {
|
|
367
|
+
updateTenantUser(user_id: $user_id, email: $email, phone: $phone, password: $password, role: $role) {
|
|
368
|
+
id
|
|
369
|
+
email
|
|
370
|
+
phone
|
|
371
|
+
role
|
|
372
|
+
provider
|
|
373
|
+
tenant_id
|
|
374
|
+
status
|
|
375
|
+
created_at
|
|
376
|
+
updated_at
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
const variables: Record<string, any> = { user_id: uid };
|
|
381
|
+
if (params.email !== undefined) variables.email = params.email;
|
|
382
|
+
if (params.phone !== undefined) variables.phone = params.phone;
|
|
383
|
+
if (params.password !== undefined) variables.password = params.password;
|
|
384
|
+
if (params.role !== undefined) variables.role = params.role;
|
|
385
|
+
const response = await this.executeGraphQL(query, variables);
|
|
386
|
+
const u = response.data?.updateTenantUser;
|
|
387
|
+
if (!u?.id) {
|
|
388
|
+
throw new ValidationError('Invalid response format for updateTenantUser');
|
|
389
|
+
}
|
|
390
|
+
return u as TenantUser;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Delete a tenant catalog user by id. Project scope is implied by the API key.
|
|
395
|
+
*/
|
|
396
|
+
async deleteTenantUser(userId: string): Promise<boolean> {
|
|
397
|
+
const uid = (userId ?? '').trim();
|
|
398
|
+
if (!uid) {
|
|
399
|
+
throw new ValidationError('userId is required');
|
|
400
|
+
}
|
|
401
|
+
const query = `
|
|
402
|
+
mutation DeleteTenantUser($user_id: String!) {
|
|
403
|
+
deleteTenantUser(user_id: $user_id)
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
const response = await this.executeGraphQL(query, { user_id: uid });
|
|
407
|
+
const ok = response.data?.deleteTenantUser;
|
|
408
|
+
if (typeof ok !== 'boolean') {
|
|
409
|
+
throw new ValidationError('Invalid response format for deleteTenantUser');
|
|
410
|
+
}
|
|
411
|
+
return ok;
|
|
412
|
+
}
|
|
413
|
+
|
|
131
414
|
/**
|
|
132
415
|
* Get a single resource by model and ID
|
|
133
416
|
*/
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -86,6 +86,76 @@ export interface CreateAndUpdateRequest {
|
|
|
86
86
|
forceUpdate?: boolean;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** Tenant catalog user from engine system DB (pro_tenant_users). */
|
|
90
|
+
export interface TenantUser {
|
|
91
|
+
id: string;
|
|
92
|
+
email?: string;
|
|
93
|
+
phone?: string;
|
|
94
|
+
role: string;
|
|
95
|
+
tenant_id: string;
|
|
96
|
+
provider?: string;
|
|
97
|
+
status?: string;
|
|
98
|
+
created_at?: string;
|
|
99
|
+
updated_at?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Login via system GraphQL `loginTenantUser`. Password path: use `email` or `phone` per project settings. Google OAuth code path: `authMethod: 'google'`, `code`, `state` from redirect (get `state` first via `tenantGoogleOAuthState`). */
|
|
103
|
+
export interface TenantLoginParams {
|
|
104
|
+
projectId: string;
|
|
105
|
+
/** Required for general (password) login. */
|
|
106
|
+
password?: string;
|
|
107
|
+
email?: string;
|
|
108
|
+
phone?: string;
|
|
109
|
+
/** `general` (default) or `google`. */
|
|
110
|
+
authMethod?: string;
|
|
111
|
+
/** Google authorization code (with `authMethod: 'google'`). */
|
|
112
|
+
code?: string;
|
|
113
|
+
/** OAuth state from `tenantGoogleOAuthState` or callback (with `authMethod: 'google'`). */
|
|
114
|
+
state?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TenantGoogleOAuthStateResponse {
|
|
118
|
+
state: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CreateTenantUserParams {
|
|
122
|
+
password: string;
|
|
123
|
+
role?: string;
|
|
124
|
+
email?: string;
|
|
125
|
+
phone?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Optional fields for `updateTenantUser`; omitted keys are not sent. */
|
|
129
|
+
export interface UpdateTenantUserParams {
|
|
130
|
+
email?: string;
|
|
131
|
+
phone?: string;
|
|
132
|
+
password?: string;
|
|
133
|
+
role?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface TenantLoginResponse {
|
|
137
|
+
token: string;
|
|
138
|
+
user?: TenantUser;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface TenantUsersResponse {
|
|
142
|
+
users: TenantUser[];
|
|
143
|
+
count: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** One SaaS catalog tenant row from searchTenantsByDomain. */
|
|
147
|
+
export interface TenantCatalogSearchRow {
|
|
148
|
+
id: string;
|
|
149
|
+
name: string;
|
|
150
|
+
status?: string;
|
|
151
|
+
domain?: string;
|
|
152
|
+
data?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface TenantByDomainResponse {
|
|
156
|
+
tenant: TenantCatalogSearchRow | null;
|
|
157
|
+
}
|
|
158
|
+
|
|
89
159
|
export interface ClientConfig {
|
|
90
160
|
baseURL: string;
|
|
91
161
|
apiKey: string;
|
|
@@ -121,7 +191,14 @@ export interface InjectedDBOperationInterface {
|
|
|
121
191
|
options?: { tenantId?: string }
|
|
122
192
|
): Promise<GraphQLResponse>;
|
|
123
193
|
/** @param token Legacy; ignored. Auth uses client API key. */
|
|
124
|
-
generateTenantToken(
|
|
194
|
+
generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string>;
|
|
195
|
+
loginTenantUser(params: TenantLoginParams): Promise<TenantLoginResponse>;
|
|
196
|
+
tenantGoogleOAuthState(projectId: string): Promise<TenantGoogleOAuthStateResponse>;
|
|
197
|
+
searchTenantUsers(projectId: string, limit?: number, offset?: number): Promise<TenantUsersResponse>;
|
|
198
|
+
searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse>;
|
|
199
|
+
createTenantUser(projectId: string, params: CreateTenantUserParams): Promise<TenantUser>;
|
|
200
|
+
updateTenantUser(userId: string, params: UpdateTenantUserParams): Promise<TenantUser>;
|
|
201
|
+
deleteTenantUser(userId: string): Promise<boolean>;
|
|
125
202
|
getSingleResource(model: string, id: string, singlePageData?: boolean): Promise<DefaultDocumentStructure>;
|
|
126
203
|
searchResources(model: string, filter?: Record<string, any>, aggregate?: boolean): Promise<SearchResult>;
|
|
127
204
|
getRelationDocuments(id: string, connection: Record<string, any>): Promise<SearchResult>;
|