@apito-io/js-admin-sdk 2.5.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apito-io/js-admin-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Admin JavaScript SDK for Apito GraphQL API (mirrors go-internal-sdk)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -277,17 +277,23 @@ describe('ApitoClient', () => {
277
277
 
278
278
  it('should login when APITO_PROJECT_ID and credentials are set', async () => {
279
279
  const projectId = process.env.APITO_PROJECT_ID;
280
- const username = process.env.APITO_TENANT_USERNAME;
280
+ const email = process.env.APITO_TENANT_EMAIL ?? '';
281
+ const phone = process.env.APITO_TENANT_PHONE ?? '';
281
282
  const password = process.env.APITO_TENANT_PASSWORD;
282
- if (!projectId || !username || !password) {
283
+ if (!projectId || !(email.trim() || phone.trim()) || !password) {
283
284
  console.log(
284
- 'loginTenantUser skipped — set APITO_PROJECT_ID, APITO_TENANT_USERNAME, APITO_TENANT_PASSWORD'
285
+ 'loginTenantUser skipped — set APITO_PROJECT_ID, APITO_TENANT_PASSWORD, and APITO_TENANT_EMAIL and/or APITO_TENANT_PHONE'
285
286
  );
286
287
  return;
287
288
  }
288
289
  const client = getTestClient();
289
290
  try {
290
- const login = await client.loginTenantUser(username, password, projectId);
291
+ const login = await client.loginTenantUser({
292
+ projectId,
293
+ password,
294
+ ...(email.trim() ? { email: email.trim() } : {}),
295
+ ...(phone.trim() ? { phone: phone.trim() } : {}),
296
+ });
291
297
  expect(login.token).toBeDefined();
292
298
  expect(login.token.length).toBeGreaterThan(0);
293
299
  console.log('✅ loginTenantUser token length:', login.token.length);
package/src/client.ts CHANGED
@@ -16,6 +16,9 @@ import {
16
16
  TenantUsersResponse,
17
17
  TenantByDomainResponse,
18
18
  TenantCatalogSearchRow,
19
+ TenantLoginParams,
20
+ CreateTenantUserParams,
21
+ UpdateTenantUserParams,
19
22
  } from './types';
20
23
 
21
24
  /**
@@ -145,21 +148,46 @@ export class ApitoClient implements InjectedDBOperationInterface {
145
148
  }
146
149
 
147
150
  /**
148
- * Tenant catalog login (system GraphQL). Returns a tenant-scoped `ak_` API token on success.
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`.
149
152
  */
150
- async loginTenantUser(
151
- username: string,
152
- password: string,
153
- projectId: string
154
- ): Promise<TenantLoginResponse> {
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
+
155
183
  const query = `
156
- query LoginTenantUser($project_id: String!, $username: String!, $password: String!) {
157
- loginTenantUser(project_id: $project_id, username: $username, password: $password) {
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) {
158
186
  token
159
187
  user {
160
188
  id
161
- username
162
189
  email
190
+ phone
163
191
  role
164
192
  provider
165
193
  tenant_id
@@ -170,7 +198,6 @@ export class ApitoClient implements InjectedDBOperationInterface {
170
198
  }
171
199
  }
172
200
  `;
173
- const variables: Record<string, any> = { project_id: projectId, username, password };
174
201
  const response = await this.executeGraphQL(query, variables);
175
202
  const raw = response.data?.loginTenantUser;
176
203
  if (!raw?.token) {
@@ -183,37 +210,24 @@ export class ApitoClient implements InjectedDBOperationInterface {
183
210
  }
184
211
 
185
212
  /**
186
- * Google ID token login for tenant users (project must have google_client_id set).
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.
187
214
  */
188
- async loginTenantUserGoogle(projectId: string, idToken: string): Promise<TenantLoginResponse> {
215
+ async tenantGoogleOAuthState(projectId: string): Promise<{ state: string }> {
189
216
  const query = `
190
- mutation LoginTenantUserGoogle($project_id: String!, $id_token: String!) {
191
- loginTenantUserGoogle(project_id: $project_id, id_token: $id_token) {
192
- token
193
- user {
194
- id
195
- username
196
- email
197
- role
198
- provider
199
- tenant_id
200
- status
201
- created_at
202
- updated_at
203
- }
217
+ query TenantGoogleOAuthState($project_id: String!) {
218
+ tenantGoogleOAuthState(project_id: $project_id) {
219
+ state
204
220
  }
205
221
  }
206
222
  `;
207
- const variables = { project_id: projectId, id_token: idToken };
223
+ const variables = { project_id: projectId };
208
224
  const response = await this.executeGraphQL(query, variables);
209
- const raw = response.data?.loginTenantUserGoogle;
210
- if (!raw?.token) {
211
- throw new ValidationError('Invalid response format for loginTenantUserGoogle');
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');
212
229
  }
213
- return {
214
- token: raw.token as string,
215
- user: raw.user as TenantUser | undefined,
216
- };
230
+ return { state };
217
231
  }
218
232
 
219
233
  /**
@@ -230,8 +244,8 @@ export class ApitoClient implements InjectedDBOperationInterface {
230
244
  count
231
245
  users {
232
246
  id
233
- username
234
247
  email
248
+ phone
235
249
  role
236
250
  provider
237
251
  tenant_id
@@ -289,21 +303,22 @@ export class ApitoClient implements InjectedDBOperationInterface {
289
303
  }
290
304
 
291
305
  /**
292
- * Create a tenant catalog user (local password).
306
+ * Create a tenant catalog user (local password). Use `email` and/or `phone` per engine validation for the project identifier mode.
293
307
  */
294
308
  async createTenantUser(
295
309
  projectId: string,
296
- username: string,
297
- email: string,
298
- password: string,
299
- role: string
310
+ params: CreateTenantUserParams
300
311
  ): Promise<TenantUser> {
312
+ const password = (params.password ?? '').trim();
313
+ if (!password) {
314
+ throw new ValidationError('password is required');
315
+ }
301
316
  const query = `
302
- mutation CreateTenantUser($project_id: String!, $username: String!, $password: String!, $role: String, $email: String) {
303
- createTenantUser(project_id: $project_id, username: $username, password: $password, role: $role, email: $email) {
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) {
304
319
  id
305
- username
306
320
  email
321
+ phone
307
322
  role
308
323
  provider
309
324
  tenant_id
@@ -313,7 +328,16 @@ export class ApitoClient implements InjectedDBOperationInterface {
313
328
  }
314
329
  }
315
330
  `;
316
- const variables = { project_id: projectId, username, password, email, role };
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;
317
341
  const response = await this.executeGraphQL(query, variables);
318
342
  const u = response.data?.createTenantUser;
319
343
  if (!u?.id) {
@@ -322,6 +346,71 @@ export class ApitoClient implements InjectedDBOperationInterface {
322
346
  return u as TenantUser;
323
347
  }
324
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
+
325
414
  /**
326
415
  * Get a single resource by model and ID
327
416
  */
package/src/index.ts CHANGED
@@ -24,6 +24,9 @@ export type {
24
24
  ApitoError,
25
25
  ValidationError,
26
26
  InjectedDBOperationInterface,
27
+ TenantLoginParams,
28
+ CreateTenantUserParams,
29
+ UpdateTenantUserParams,
27
30
  } from './types';
28
31
 
29
32
  // Default export for convenience
package/src/types.ts CHANGED
@@ -89,8 +89,8 @@ export interface CreateAndUpdateRequest {
89
89
  /** Tenant catalog user from engine system DB (pro_tenant_users). */
90
90
  export interface TenantUser {
91
91
  id: string;
92
- username: string;
93
92
  email?: string;
93
+ phone?: string;
94
94
  role: string;
95
95
  tenant_id: string;
96
96
  provider?: string;
@@ -99,6 +99,40 @@ export interface TenantUser {
99
99
  updated_at?: string;
100
100
  }
101
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
+
102
136
  export interface TenantLoginResponse {
103
137
  token: string;
104
138
  user?: TenantUser;
@@ -158,17 +192,13 @@ export interface InjectedDBOperationInterface {
158
192
  ): Promise<GraphQLResponse>;
159
193
  /** @param token Legacy; ignored. Auth uses client API key. */
160
194
  generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string>;
161
- loginTenantUser(username: string, password: string, projectId: string): Promise<TenantLoginResponse>;
162
- loginTenantUserGoogle(projectId: string, idToken: string): Promise<TenantLoginResponse>;
195
+ loginTenantUser(params: TenantLoginParams): Promise<TenantLoginResponse>;
196
+ tenantGoogleOAuthState(projectId: string): Promise<TenantGoogleOAuthStateResponse>;
163
197
  searchTenantUsers(projectId: string, limit?: number, offset?: number): Promise<TenantUsersResponse>;
164
198
  searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse>;
165
- createTenantUser(
166
- projectId: string,
167
- username: string,
168
- email: string,
169
- password: string,
170
- role: string
171
- ): Promise<TenantUser>;
199
+ createTenantUser(projectId: string, params: CreateTenantUserParams): Promise<TenantUser>;
200
+ updateTenantUser(userId: string, params: UpdateTenantUserParams): Promise<TenantUser>;
201
+ deleteTenantUser(userId: string): Promise<boolean>;
172
202
  getSingleResource(model: string, id: string, singlePageData?: boolean): Promise<DefaultDocumentStructure>;
173
203
  searchResources(model: string, filter?: Record<string, any>, aggregate?: boolean): Promise<SearchResult>;
174
204
  getRelationDocuments(id: string, connection: Record<string, any>): Promise<SearchResult>;
package/src/version.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Apito JavaScript internal SDK version (kept in sync with package.json for releases)
3
3
  */
4
- export const Version = '2.5.0';
4
+ export const Version = '2.7.0';
5
5
 
6
6
  /**
7
7
  * GetVersion returns the current version of the SDK