vibecodingmachine-core 2026.1.3-2209 → 2026.1.23-1010
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/__tests__/provider-manager-fallback.test.js +43 -0
- package/__tests__/provider-manager-rate-limit.test.js +61 -0
- package/package.json +1 -1
- package/src/compliance/compliance-manager.js +5 -2
- package/src/database/migrations.js +135 -12
- package/src/database/user-database-client.js +63 -8
- package/src/database/user-schema.js +7 -0
- package/src/health-tracking/__tests__/ide-health-tracker.test.js +420 -0
- package/src/health-tracking/__tests__/interaction-recorder.test.js +392 -0
- package/src/health-tracking/errors.js +50 -0
- package/src/health-tracking/health-reporter.js +331 -0
- package/src/health-tracking/ide-health-tracker.js +446 -0
- package/src/health-tracking/interaction-recorder.js +161 -0
- package/src/health-tracking/json-storage.js +276 -0
- package/src/health-tracking/storage-interface.js +63 -0
- package/src/health-tracking/validators.js +277 -0
- package/src/ide-integration/applescript-manager.cjs +1062 -4
- package/src/ide-integration/applescript-manager.js +560 -11
- package/src/ide-integration/provider-manager.cjs +158 -28
- package/src/ide-integration/quota-detector.cjs +339 -16
- package/src/ide-integration/quota-detector.js +6 -1
- package/src/index.cjs +32 -1
- package/src/index.js +16 -0
- package/src/localization/translations/en.js +13 -1
- package/src/localization/translations/es.js +12 -0
- package/src/utils/admin-utils.js +33 -0
- package/src/utils/error-reporter.js +12 -4
- package/src/utils/requirement-helpers.js +34 -4
- package/src/utils/requirements-parser.js +3 -3
- package/tests/health-tracking/health-reporter.test.js +329 -0
- package/tests/health-tracking/ide-health-tracker.test.js +368 -0
- package/tests/health-tracking/interaction-recorder.test.js +309 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const ProviderManager = require('../src/ide-integration/provider-manager.cjs');
|
|
2
|
+
|
|
3
|
+
describe('ProviderManager fallback behavior', () => {
|
|
4
|
+
let pm;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
pm = new ProviderManager();
|
|
8
|
+
pm.clearAllRateLimits();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
pm.clearAllRateLimits();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('isRateLimited should return true for provider when only legacy/model-agnostic key exists', () => {
|
|
16
|
+
// Simulate legacy stored key with model undefined (string 'undefined')
|
|
17
|
+
const resetTime = Date.now() + 10 * 60 * 1000; // 10 minutes from now
|
|
18
|
+
pm.rateLimits['antigravity:undefined'] = {
|
|
19
|
+
provider: 'antigravity',
|
|
20
|
+
model: undefined,
|
|
21
|
+
resetTime,
|
|
22
|
+
resetDate: new Date(resetTime).toISOString(),
|
|
23
|
+
reason: 'Quota limit reached',
|
|
24
|
+
markedAt: new Date().toISOString()
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(pm.isRateLimited('antigravity', 'antigravity')).toBe(true);
|
|
28
|
+
const t = pm.getTimeUntilReset('antigravity', 'antigravity');
|
|
29
|
+
expect(t).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('getRateLimitInfo should return earliest reset for provider when model-specific not present', () => {
|
|
33
|
+
const resetA = Date.now() + 60 * 60 * 1000; // 60m
|
|
34
|
+
const resetB = Date.now() + 5 * 60 * 1000; // 5m
|
|
35
|
+
|
|
36
|
+
pm.rateLimits['antigravity:legacy'] = { provider: 'antigravity', model: 'legacy', resetTime: resetA, reason: 'legacy', markedAt: new Date().toISOString() };
|
|
37
|
+
pm.rateLimits['antigravity:other'] = { provider: 'antigravity', model: 'other', resetTime: resetB, reason: 'other', markedAt: new Date().toISOString() };
|
|
38
|
+
|
|
39
|
+
const info = pm.getRateLimitInfo('antigravity', 'antigravity');
|
|
40
|
+
expect(info.isRateLimited).toBe(true);
|
|
41
|
+
expect(info.resetTime).toBe(resetB);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const ProviderManager = require('../src/ide-integration/provider-manager.cjs');
|
|
2
|
+
|
|
3
|
+
describe('ProviderManager rate limit handling', () => {
|
|
4
|
+
let pm;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
pm = new ProviderManager();
|
|
8
|
+
pm.clearAllRateLimits();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
pm.clearAllRateLimits();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('markRateLimited with undefined model normalizes model to provider', () => {
|
|
16
|
+
// Call with model undefined to simulate IDE provider case
|
|
17
|
+
pm.markRateLimited('antigravity', undefined, 'Quota limit reached');
|
|
18
|
+
|
|
19
|
+
// The provider should be considered rate-limited when queried using provider as model
|
|
20
|
+
expect(pm.isRateLimited('antigravity', 'antigravity')).toBe(true);
|
|
21
|
+
|
|
22
|
+
const info = pm.getRateLimitInfo('antigravity', 'antigravity');
|
|
23
|
+
expect(info.isRateLimited).toBe(true);
|
|
24
|
+
expect(info.resetTime).toBeGreaterThan(Date.now());
|
|
25
|
+
|
|
26
|
+
const ms = pm.getTimeUntilReset('antigravity', 'antigravity');
|
|
27
|
+
expect(ms).toBeGreaterThan(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
test('parses "resets Month Day at Time" format correctly', () => {
|
|
32
|
+
// Mock Date.now to Jan 13, 2026 10:00 AM
|
|
33
|
+
const mockNow = new Date('2026-01-13T10:00:00').getTime();
|
|
34
|
+
const originalDateNow = Date.now;
|
|
35
|
+
Date.now = jest.fn(() => mockNow);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const errorMessage = 'Spending cap reached resets Jan 17 at 12pm';
|
|
39
|
+
|
|
40
|
+
// Expected: Jan 17 12:00 PM = 12:00 + (17-13)*24 hours
|
|
41
|
+
// Jan 13 10am to Jan 17 12pm
|
|
42
|
+
// Diff: 4 days + 2 hours = 98 hours
|
|
43
|
+
const expectedDuration = (4 * 24 * 60 * 60 * 1000) + (2 * 60 * 60 * 1000);
|
|
44
|
+
|
|
45
|
+
// Verify regex in test env
|
|
46
|
+
const regex = /resets\s+([a-zA-Z]+)\s+(\d{1,2})\s+at\s+(\d{1,2}(?::\d{2})?)\s*(am|pm)?/i;
|
|
47
|
+
const match = errorMessage.match(regex);
|
|
48
|
+
expect(match).not.toBeNull();
|
|
49
|
+
|
|
50
|
+
const duration = pm.parseRateLimitDuration(errorMessage);
|
|
51
|
+
|
|
52
|
+
// Allow for some minor variance if calculation logic differs slightly, but it should be close
|
|
53
|
+
expect(duration).toBeDefined();
|
|
54
|
+
expect(duration).not.toBeNull();
|
|
55
|
+
expect(Math.abs(duration - expectedDuration)).toBeLessThan(1000);
|
|
56
|
+
|
|
57
|
+
} finally {
|
|
58
|
+
Date.now = originalDateNow;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
package/package.json
CHANGED
|
@@ -10,8 +10,11 @@ const fs = require('fs').promises
|
|
|
10
10
|
const path = require('path')
|
|
11
11
|
|
|
12
12
|
class ComplianceManager {
|
|
13
|
-
constructor() {
|
|
13
|
+
constructor(authToken = null) {
|
|
14
14
|
this.userDb = new UserDatabase()
|
|
15
|
+
if (authToken) {
|
|
16
|
+
this.userDb.setAuthToken(authToken)
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -19,7 +22,7 @@ class ComplianceManager {
|
|
|
19
22
|
* Returns what needs to be acknowledged
|
|
20
23
|
*/
|
|
21
24
|
async checkComplianceStatus(userId) {
|
|
22
|
-
const user = await this.userDb.getUser(
|
|
25
|
+
const user = await this.userDb.getUser()
|
|
23
26
|
|
|
24
27
|
if (!user) {
|
|
25
28
|
throw new Error('User not found')
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
|
|
9
|
-
const {
|
|
10
|
-
CreateTableCommand,
|
|
11
|
-
DescribeTableCommand,
|
|
9
|
+
const {
|
|
10
|
+
CreateTableCommand,
|
|
11
|
+
DescribeTableCommand,
|
|
12
12
|
UpdateTableCommand,
|
|
13
|
-
ListTablesCommand
|
|
13
|
+
ListTablesCommand
|
|
14
14
|
} = require('@aws-sdk/client-dynamodb')
|
|
15
15
|
|
|
16
16
|
class DatabaseMigrations {
|
|
@@ -25,13 +25,15 @@ class DatabaseMigrations {
|
|
|
25
25
|
*/
|
|
26
26
|
async runMigrations() {
|
|
27
27
|
console.log('🔄 Running database migrations...')
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
try {
|
|
30
30
|
await this.createUsersTable()
|
|
31
31
|
await this.createComputersTable()
|
|
32
32
|
await this.createProjectsTable()
|
|
33
33
|
await this.createActivityTable()
|
|
34
|
-
|
|
34
|
+
await this.createErrorsTable()
|
|
35
|
+
await this.createFeedbackTable()
|
|
36
|
+
|
|
35
37
|
console.log('✅ All migrations completed successfully')
|
|
36
38
|
} catch (error) {
|
|
37
39
|
console.error('❌ Migration failed:', error)
|
|
@@ -44,7 +46,7 @@ class DatabaseMigrations {
|
|
|
44
46
|
*/
|
|
45
47
|
async createUsersTable() {
|
|
46
48
|
const tableName = 'vibecodingmachine-users'
|
|
47
|
-
|
|
49
|
+
|
|
48
50
|
if (await this.tableExists(tableName)) {
|
|
49
51
|
console.log(`📋 Table ${tableName} already exists`)
|
|
50
52
|
return
|
|
@@ -94,7 +96,7 @@ class DatabaseMigrations {
|
|
|
94
96
|
*/
|
|
95
97
|
async createComputersTable() {
|
|
96
98
|
const tableName = 'vibecodingmachine-computers'
|
|
97
|
-
|
|
99
|
+
|
|
98
100
|
if (await this.tableExists(tableName)) {
|
|
99
101
|
console.log(`📋 Table ${tableName} already exists`)
|
|
100
102
|
return
|
|
@@ -137,7 +139,7 @@ class DatabaseMigrations {
|
|
|
137
139
|
*/
|
|
138
140
|
async createProjectsTable() {
|
|
139
141
|
const tableName = 'vibecodingmachine-projects'
|
|
140
|
-
|
|
142
|
+
|
|
141
143
|
if (await this.tableExists(tableName)) {
|
|
142
144
|
console.log(`📋 Table ${tableName} already exists`)
|
|
143
145
|
return
|
|
@@ -180,7 +182,7 @@ class DatabaseMigrations {
|
|
|
180
182
|
*/
|
|
181
183
|
async createActivityTable() {
|
|
182
184
|
const tableName = 'vibecodingmachine-activity'
|
|
183
|
-
|
|
185
|
+
|
|
184
186
|
if (await this.tableExists(tableName)) {
|
|
185
187
|
console.log(`📋 Table ${tableName} already exists`)
|
|
186
188
|
return
|
|
@@ -222,6 +224,126 @@ class DatabaseMigrations {
|
|
|
222
224
|
console.log(`✅ Created table: ${tableName}`)
|
|
223
225
|
}
|
|
224
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Create errors table for tracking application errors
|
|
229
|
+
*/
|
|
230
|
+
async createErrorsTable() {
|
|
231
|
+
const tableName = 'vibecodingmachine-errors'
|
|
232
|
+
|
|
233
|
+
if (await this.tableExists(tableName)) {
|
|
234
|
+
console.log(`📋 Table ${tableName} already exists`)
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const params = {
|
|
239
|
+
TableName: tableName,
|
|
240
|
+
KeySchema: [
|
|
241
|
+
{ AttributeName: 'errorId', KeyType: 'HASH' }
|
|
242
|
+
],
|
|
243
|
+
AttributeDefinitions: [
|
|
244
|
+
{ AttributeName: 'errorId', AttributeType: 'S' }
|
|
245
|
+
],
|
|
246
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
247
|
+
Tags: [
|
|
248
|
+
{ Key: 'Application', Value: 'VibeCodingMachine' },
|
|
249
|
+
{ Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await this.client.send(new CreateTableCommand(params))
|
|
254
|
+
console.log(`✅ Created table: ${tableName}`)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create feedback table for user comments
|
|
259
|
+
*/
|
|
260
|
+
async createFeedbackTable() {
|
|
261
|
+
const tableName = 'vibecodingmachine-feedback'
|
|
262
|
+
|
|
263
|
+
if (await this.tableExists(tableName)) {
|
|
264
|
+
console.log(`📋 Table ${tableName} already exists`)
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const params = {
|
|
269
|
+
TableName: tableName,
|
|
270
|
+
KeySchema: [
|
|
271
|
+
{ AttributeName: 'feedbackId', KeyType: 'HASH' }
|
|
272
|
+
],
|
|
273
|
+
AttributeDefinitions: [
|
|
274
|
+
{ AttributeName: 'feedbackId', AttributeType: 'S' }
|
|
275
|
+
],
|
|
276
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
277
|
+
Tags: [
|
|
278
|
+
{ Key: 'Application', Value: 'VibeCodingMachine' },
|
|
279
|
+
{ Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await this.client.send(new CreateTableCommand(params))
|
|
284
|
+
console.log(`✅ Created table: ${tableName}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create errors table for tracking application errors
|
|
289
|
+
*/
|
|
290
|
+
async createErrorsTable() {
|
|
291
|
+
const tableName = 'vibecodingmachine-errors'
|
|
292
|
+
|
|
293
|
+
if (await this.tableExists(tableName)) {
|
|
294
|
+
console.log(`📋 Table ${tableName} already exists`)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const params = {
|
|
299
|
+
TableName: tableName,
|
|
300
|
+
KeySchema: [
|
|
301
|
+
{ AttributeName: 'errorId', KeyType: 'HASH' }
|
|
302
|
+
],
|
|
303
|
+
AttributeDefinitions: [
|
|
304
|
+
{ AttributeName: 'errorId', AttributeType: 'S' }
|
|
305
|
+
],
|
|
306
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
307
|
+
Tags: [
|
|
308
|
+
{ Key: 'Application', Value: 'VibeCodingMachine' },
|
|
309
|
+
{ Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await this.client.send(new CreateTableCommand(params))
|
|
314
|
+
console.log(`✅ Created table: ${tableName}`)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Create feedback table for user comments
|
|
319
|
+
*/
|
|
320
|
+
async createFeedbackTable() {
|
|
321
|
+
const tableName = 'vibecodingmachine-feedback'
|
|
322
|
+
|
|
323
|
+
if (await this.tableExists(tableName)) {
|
|
324
|
+
console.log(`📋 Table ${tableName} already exists`)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const params = {
|
|
329
|
+
TableName: tableName,
|
|
330
|
+
KeySchema: [
|
|
331
|
+
{ AttributeName: 'feedbackId', KeyType: 'HASH' }
|
|
332
|
+
],
|
|
333
|
+
AttributeDefinitions: [
|
|
334
|
+
{ AttributeName: 'feedbackId', AttributeType: 'S' }
|
|
335
|
+
],
|
|
336
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
337
|
+
Tags: [
|
|
338
|
+
{ Key: 'Application', Value: 'VibeCodingMachine' },
|
|
339
|
+
{ Key: 'Environment', Value: process.env.NODE_ENV || 'development' }
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
await this.client.send(new CreateTableCommand(params))
|
|
344
|
+
console.log(`✅ Created table: ${tableName}`)
|
|
345
|
+
}
|
|
346
|
+
|
|
225
347
|
/**
|
|
226
348
|
* Check if a table exists
|
|
227
349
|
*/
|
|
@@ -272,10 +394,11 @@ class DatabaseMigrations {
|
|
|
272
394
|
*/
|
|
273
395
|
async getAllTableStatus() {
|
|
274
396
|
const tableNames = [
|
|
275
|
-
'vibecodingmachine-users',
|
|
276
397
|
'vibecodingmachine-computers',
|
|
277
398
|
'vibecodingmachine-projects',
|
|
278
|
-
'vibecodingmachine-activity'
|
|
399
|
+
'vibecodingmachine-activity',
|
|
400
|
+
'vibecodingmachine-errors',
|
|
401
|
+
'vibecodingmachine-feedback'
|
|
279
402
|
]
|
|
280
403
|
|
|
281
404
|
const statuses = await Promise.all(
|
|
@@ -18,9 +18,21 @@ try {
|
|
|
18
18
|
|
|
19
19
|
class UserDatabaseClient {
|
|
20
20
|
constructor(apiBaseUrl = null) {
|
|
21
|
-
//
|
|
22
|
-
|
|
21
|
+
// Check if we're in a development workspace
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const rootDir = path.join(__dirname, '..', '..', '..', '..');
|
|
25
|
+
const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
|
|
26
|
+
|
|
27
|
+
// Default to local dev server in development, production otherwise
|
|
28
|
+
if (isDevWorkspace && !apiBaseUrl && !process.env.VIBECODINGMACHINE_API_URL) {
|
|
29
|
+
this.apiBaseUrl = 'http://localhost:3457';
|
|
30
|
+
} else {
|
|
31
|
+
this.apiBaseUrl = apiBaseUrl || process.env.VIBECODINGMACHINE_API_URL || 'https://api.vibecodingmachine.com';
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
this.authToken = null;
|
|
35
|
+
console.log(require('chalk').gray(`UserDatabaseClient: Using API at ${this.apiBaseUrl}`));
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
/**
|
|
@@ -44,14 +56,36 @@ class UserDatabaseClient {
|
|
|
44
56
|
headers.Authorization = `Bearer ${this.authToken}`;
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await fetch(url, {
|
|
62
|
+
...options,
|
|
63
|
+
headers
|
|
64
|
+
});
|
|
65
|
+
} catch (fetchError) {
|
|
66
|
+
const msg = `Connection failed: ${fetchError.message}`;
|
|
67
|
+
console.warn(`UserDatabaseClient: ${msg} [${url}]`);
|
|
68
|
+
throw new Error(msg);
|
|
69
|
+
}
|
|
51
70
|
|
|
52
71
|
if (!response.ok) {
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
let errorDetail = '';
|
|
73
|
+
let bodyText = '';
|
|
74
|
+
try {
|
|
75
|
+
bodyText = await response.text();
|
|
76
|
+
try {
|
|
77
|
+
const errorJson = JSON.parse(bodyText);
|
|
78
|
+
errorDetail = errorJson.error || errorJson.message || '';
|
|
79
|
+
} catch (e) {
|
|
80
|
+
errorDetail = bodyText;
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
errorDetail = 'Could not read response body';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (errorDetail.length > 200) errorDetail = errorDetail.substring(0, 200) + '...';
|
|
87
|
+
const errorMsg = errorDetail || `HTTP ${response.status}`;
|
|
88
|
+
throw new Error(errorMsg);
|
|
55
89
|
}
|
|
56
90
|
|
|
57
91
|
return response.json();
|
|
@@ -274,6 +308,27 @@ class UserDatabaseClient {
|
|
|
274
308
|
return false; // Client-side can't check admin status
|
|
275
309
|
}
|
|
276
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Submit user feedback to the admin database
|
|
313
|
+
*/
|
|
314
|
+
async submitFeedback(feedbackData) {
|
|
315
|
+
try {
|
|
316
|
+
const response = await this.apiRequest('/api/feedback', {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
body: JSON.stringify({
|
|
319
|
+
timestamp: Date.now(),
|
|
320
|
+
hostname: os.hostname(),
|
|
321
|
+
platform: os.platform(),
|
|
322
|
+
...feedbackData
|
|
323
|
+
})
|
|
324
|
+
});
|
|
325
|
+
return response;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.warn('UserDatabase: submitFeedback failed:', error.message);
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
277
332
|
/**
|
|
278
333
|
* Report an error to the admin database for tracking and fixing
|
|
279
334
|
*/
|
|
@@ -115,6 +115,13 @@ class UserDatabase {
|
|
|
115
115
|
return this.apiClient.isAdmin(userId)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Submit user feedback to the admin database
|
|
120
|
+
*/
|
|
121
|
+
async submitFeedback(feedbackData) {
|
|
122
|
+
return this.apiClient.submitFeedback(feedbackData)
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
/**
|
|
119
126
|
* Report an error to the admin database for tracking and fixing
|
|
120
127
|
*/
|