@oxyhq/core 3.2.0 → 3.4.1
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +3 -1
- package/dist/cjs/HttpService.js +89 -0
- package/dist/cjs/OxyServices.js +1 -1
- package/dist/cjs/constants/version.js +1 -1
- package/dist/cjs/i18n/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/es-ES.json +44 -44
- package/dist/cjs/i18n/locales/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/locales/es-ES.json +44 -44
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/mixins/OxyServices.applications.js +3 -1
- package/dist/cjs/mixins/OxyServices.reputation.js +244 -0
- package/dist/cjs/mixins/OxyServices.workspaces.js +3 -1
- package/dist/cjs/mixins/index.js +2 -2
- package/dist/cjs/utils/accountUtils.js +12 -5
- package/dist/cjs/utils/ssoReturn.js +80 -33
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +3 -1
- package/dist/esm/HttpService.js +89 -0
- package/dist/esm/OxyServices.js +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/i18n/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/es-ES.json +44 -44
- package/dist/esm/i18n/locales/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/locales/es-ES.json +44 -44
- package/dist/esm/index.js +4 -0
- package/dist/esm/mixins/OxyServices.applications.js +3 -1
- package/dist/esm/mixins/OxyServices.reputation.js +241 -0
- package/dist/esm/mixins/OxyServices.workspaces.js +3 -1
- package/dist/esm/mixins/index.js +2 -2
- package/dist/esm/utils/accountUtils.js +12 -5
- package/dist/esm/utils/ssoReturn.js +80 -33
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +57 -0
- package/dist/types/OxyServices.d.ts +1 -1
- package/dist/types/constants/version.d.ts +2 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +8 -2
- package/dist/types/mixins/OxyServices.features.d.ts +0 -1
- package/dist/types/mixins/OxyServices.reputation.d.ts +436 -0
- package/dist/types/mixins/OxyServices.workspaces.d.ts +8 -2
- package/dist/types/mixins/index.d.ts +2 -2
- package/dist/types/models/interfaces.d.ts +15 -26
- package/dist/types/utils/accountUtils.d.ts +17 -4
- package/dist/types/utils/ssoReturn.d.ts +30 -9
- package/package.json +2 -1
- package/src/AuthManager.ts +3 -1
- package/src/HttpService.ts +91 -0
- package/src/OxyServices.ts +1 -1
- package/src/__tests__/httpServiceCache.test.ts +198 -0
- package/src/constants/version.ts +1 -1
- package/src/i18n/locales/en-US.json +44 -44
- package/src/i18n/locales/es-ES.json +44 -44
- package/src/index.ts +32 -4
- package/src/mixins/OxyServices.applications.ts +8 -2
- package/src/mixins/OxyServices.auth.ts +2 -1
- package/src/mixins/OxyServices.features.ts +0 -1
- package/src/mixins/OxyServices.reputation.ts +674 -0
- package/src/mixins/OxyServices.workspaces.ts +8 -2
- package/src/mixins/__tests__/reputation.test.ts +408 -0
- package/src/mixins/index.ts +3 -3
- package/src/models/interfaces.ts +16 -32
- package/src/utils/__tests__/accountUtils.test.ts +142 -0
- package/src/utils/__tests__/consumeSsoReturn.test.ts +229 -37
- package/src/utils/accountUtils.ts +20 -5
- package/src/utils/ssoReturn.ts +98 -37
- package/dist/cjs/mixins/OxyServices.developer.js +0 -97
- package/dist/cjs/mixins/OxyServices.karma.js +0 -108
- package/dist/esm/mixins/OxyServices.developer.js +0 -94
- package/dist/esm/mixins/OxyServices.karma.js +0 -105
- package/dist/types/mixins/OxyServices.developer.d.ts +0 -106
- package/dist/types/mixins/OxyServices.karma.d.ts +0 -92
- package/src/mixins/OxyServices.karma.ts +0 -111
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reputation Methods Mixin (Oxy Trust)
|
|
3
|
+
*
|
|
4
|
+
* Provides typed access to the reputation ledger (#217) and the derived
|
|
5
|
+
* trust-tier / capped-influence model (#219) via the `/reputation` API.
|
|
6
|
+
*
|
|
7
|
+
* The reputation ledger is append-only: transactions are NEVER deleted.
|
|
8
|
+
* A correction is expressed either as a compensating REVERSAL (the original is
|
|
9
|
+
* marked `reversed` and a new `active` transaction with negated points is
|
|
10
|
+
* appended) or a VOID (the original is marked `voided` and excluded from the
|
|
11
|
+
* balance). A user's `ReputationBalance` is a recomputable cache of the sum of
|
|
12
|
+
* their `active` transactions, augmented with a trust tier, capped influence
|
|
13
|
+
* weights, and reliability signals.
|
|
14
|
+
*
|
|
15
|
+
* Reference users by their Mongo `_id` (or publicKey, which the API resolves),
|
|
16
|
+
* transactions by their `id`, and disputes by their `id`.
|
|
17
|
+
*/
|
|
18
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
19
|
+
import type { User } from '../models/interfaces';
|
|
20
|
+
import { CACHE_TIMES } from './mixinHelpers';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// UNION TYPES (mirror packages/api/src/utils/reputation.constants.ts)
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Category bucket a reputation transaction falls into. Drives the per-category
|
|
28
|
+
* balance breakdown.
|
|
29
|
+
*/
|
|
30
|
+
export type ReputationCategory =
|
|
31
|
+
| 'content'
|
|
32
|
+
| 'social'
|
|
33
|
+
| 'trust'
|
|
34
|
+
| 'moderation'
|
|
35
|
+
| 'physical'
|
|
36
|
+
| 'penalty'
|
|
37
|
+
| 'other';
|
|
38
|
+
|
|
39
|
+
/** Trust tiers, lowest → highest (plus the punitive `restricted`). */
|
|
40
|
+
export type TrustTier = 'new' | 'trusted' | 'high_trust' | 'verified' | 'restricted';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Transaction lifecycle status. Only `active` transactions count toward the
|
|
44
|
+
* balance; `disputed` still counts until the dispute resolves; `reversed` and
|
|
45
|
+
* `voided` are excluded.
|
|
46
|
+
*/
|
|
47
|
+
export type ReputationTransactionStatus = 'active' | 'disputed' | 'reversed' | 'voided';
|
|
48
|
+
|
|
49
|
+
/** Kind of entity a transaction may target. */
|
|
50
|
+
export type ReputationTargetEntityType =
|
|
51
|
+
| 'post'
|
|
52
|
+
| 'comment'
|
|
53
|
+
| 'report'
|
|
54
|
+
| 'purchase'
|
|
55
|
+
| 'event'
|
|
56
|
+
| 'check_in'
|
|
57
|
+
| 'manual_review'
|
|
58
|
+
| 'user'
|
|
59
|
+
| 'other';
|
|
60
|
+
|
|
61
|
+
/** Dispute lifecycle status. */
|
|
62
|
+
export type ReputationDisputeStatus = 'open' | 'accepted' | 'rejected' | 'needs_review';
|
|
63
|
+
|
|
64
|
+
/** Influence context selecting which capped weight axis to return. */
|
|
65
|
+
export type ReputationInfluenceContext = 'default' | 'report' | 'moderation' | 'ranking';
|
|
66
|
+
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// ENTITY SHAPES (mirror the server models; ids are strings, dates ISO strings)
|
|
69
|
+
// =============================================================================
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A single immutable entry in the reputation ledger. Ids are emitted as strings
|
|
73
|
+
* and dates as ISO strings by the API.
|
|
74
|
+
*/
|
|
75
|
+
export interface ReputationTransaction {
|
|
76
|
+
/** The transaction's Mongo `_id` as a string. */
|
|
77
|
+
id: string;
|
|
78
|
+
/** Subject of the reputation change — the user whose balance moves. */
|
|
79
|
+
userId: string;
|
|
80
|
+
/** Signed point delta. Positive awards, negative penalties/reversals. */
|
|
81
|
+
points: number;
|
|
82
|
+
/** The rule/action key that produced this transaction (e.g. `post_created`). */
|
|
83
|
+
actionType: string;
|
|
84
|
+
/** Category bucket the points fall into. */
|
|
85
|
+
category: ReputationCategory;
|
|
86
|
+
/** Canonical source application that reported the action, if any. */
|
|
87
|
+
applicationId?: string;
|
|
88
|
+
/** The specific credential used by the source application, if any. */
|
|
89
|
+
credentialId?: string;
|
|
90
|
+
/** Opaque id of the originating action in the source system (idempotency key). */
|
|
91
|
+
sourceActionId?: string;
|
|
92
|
+
/** Source-system action type (e.g. `report_confirmed`, `event_check_in`). */
|
|
93
|
+
sourceActionType?: string;
|
|
94
|
+
/** Id of the entity the action targeted (post id, report id, etc.). */
|
|
95
|
+
targetEntityId?: string;
|
|
96
|
+
/** Kind of the targeted entity. */
|
|
97
|
+
targetEntityType?: ReputationTargetEntityType;
|
|
98
|
+
/** Lifecycle status — only `active` transactions count toward the balance. */
|
|
99
|
+
status: ReputationTransactionStatus;
|
|
100
|
+
/**
|
|
101
|
+
* Set ONLY on a compensating reversal transaction; references the original
|
|
102
|
+
* transaction it reverses. The original carries `status: 'reversed'`.
|
|
103
|
+
*/
|
|
104
|
+
reversedTransactionId?: string;
|
|
105
|
+
/** Human-readable reason / note. */
|
|
106
|
+
reason?: string;
|
|
107
|
+
/** Free-form structured metadata from the source system. */
|
|
108
|
+
metadata?: Record<string, unknown>;
|
|
109
|
+
/** The user who caused this change (the liker, the reporting user, staff). */
|
|
110
|
+
createdByUserId?: string;
|
|
111
|
+
/** Staff/service principal who reviewed (reversed/voided) this transaction. */
|
|
112
|
+
reviewedByUserId?: string;
|
|
113
|
+
/** ISO timestamp the transaction was reviewed at, if reviewed. */
|
|
114
|
+
reviewedAt?: string;
|
|
115
|
+
/** ISO creation timestamp. */
|
|
116
|
+
createdAt: string;
|
|
117
|
+
/** ISO last-update timestamp. */
|
|
118
|
+
updatedAt: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Per-category sums of a user's ACTIVE transactions. `penalties` is the
|
|
123
|
+
* absolute sum of every negative-point transaction; the named buckets carry the
|
|
124
|
+
* signed sum of transactions in that category.
|
|
125
|
+
*/
|
|
126
|
+
export interface ReputationBalanceBreakdown {
|
|
127
|
+
content: number;
|
|
128
|
+
social: number;
|
|
129
|
+
trust: number;
|
|
130
|
+
moderation: number;
|
|
131
|
+
physical: number;
|
|
132
|
+
penalties: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Capped influence weights (#219). Every weight is clamped to a configured
|
|
137
|
+
* range; restricted users are floored on every axis. Downstream systems
|
|
138
|
+
* (ranking, moderation, reporting) consume these to weight a user's
|
|
139
|
+
* contributions without letting any single user dominate.
|
|
140
|
+
*/
|
|
141
|
+
export interface ReputationInfluence {
|
|
142
|
+
/** General-purpose trust weight derived from the lifetime total. */
|
|
143
|
+
defaultWeight: number;
|
|
144
|
+
/** Weight applied to this user's reports (scales with report accuracy). */
|
|
145
|
+
reportWeight: number;
|
|
146
|
+
/** Weight applied to this user's moderation actions (scales with tier). */
|
|
147
|
+
moderationWeight: number;
|
|
148
|
+
/** Damped weight applied to this user's ranking feedback. */
|
|
149
|
+
rankingFeedbackWeight: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reliability signals (#219) derived from the user's moderation track record in
|
|
154
|
+
* the ledger.
|
|
155
|
+
*/
|
|
156
|
+
export interface ReputationReliability {
|
|
157
|
+
/** Count of active transactions stamped `report_confirmed`. */
|
|
158
|
+
accurateReports: number;
|
|
159
|
+
/** Count of active transactions stamped `report_rejected`. */
|
|
160
|
+
rejectedReports: number;
|
|
161
|
+
/** accurate / (accurate + rejected), or the neutral 0.5 when no history. */
|
|
162
|
+
reportAccuracyScore: number;
|
|
163
|
+
/** Smoothed 0..1 abuse signal; high values force the `restricted` tier. */
|
|
164
|
+
abuseScore: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Cached, recomputable snapshot of a user's reputation. Shape mirrors the
|
|
169
|
+
* `/reputation/:userId/balance` response (which omits internal `lastTransactionId`
|
|
170
|
+
* and `createdAt`).
|
|
171
|
+
*/
|
|
172
|
+
export interface ReputationBalance {
|
|
173
|
+
userId: string;
|
|
174
|
+
/** Net lifetime total across all active transactions. */
|
|
175
|
+
total: number;
|
|
176
|
+
/** Sum of positive points only. */
|
|
177
|
+
positive: number;
|
|
178
|
+
/** Sum of negative points only (a negative number). */
|
|
179
|
+
negative: number;
|
|
180
|
+
breakdown: ReputationBalanceBreakdown;
|
|
181
|
+
trustTier: TrustTier;
|
|
182
|
+
influence: ReputationInfluence;
|
|
183
|
+
reliability: ReputationReliability;
|
|
184
|
+
/** ISO timestamp the snapshot was last recomputed at. */
|
|
185
|
+
recalculatedAt: string;
|
|
186
|
+
/** ISO last-update timestamp. */
|
|
187
|
+
updatedAt: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A user-initiated dispute against a specific reputation transaction. Ids are
|
|
192
|
+
* strings and dates ISO strings.
|
|
193
|
+
*/
|
|
194
|
+
export interface ReputationDispute {
|
|
195
|
+
/** The dispute's Mongo `_id` as a string. */
|
|
196
|
+
id: string;
|
|
197
|
+
/** The transaction being disputed. */
|
|
198
|
+
transactionId: string;
|
|
199
|
+
/** The user raising the dispute. */
|
|
200
|
+
userId: string;
|
|
201
|
+
/** Why the user believes the transaction is wrong. */
|
|
202
|
+
reason: string;
|
|
203
|
+
status: ReputationDisputeStatus;
|
|
204
|
+
/** Optional supporting evidence (URLs / references). */
|
|
205
|
+
evidence?: string[];
|
|
206
|
+
/** ISO timestamp the dispute was resolved at, if resolved. */
|
|
207
|
+
resolvedAt?: string;
|
|
208
|
+
/** Staff principal who resolved the dispute, if resolved. */
|
|
209
|
+
resolvedByUserId?: string;
|
|
210
|
+
/** ISO creation timestamp. */
|
|
211
|
+
createdAt: string;
|
|
212
|
+
/** ISO last-update timestamp. */
|
|
213
|
+
updatedAt: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A configurable reputation award/penalty rule. The `/reputation/rules`
|
|
218
|
+
* response shape: `id` is the rule's `_id`; no timestamps are emitted.
|
|
219
|
+
*/
|
|
220
|
+
export interface ReputationRule {
|
|
221
|
+
/** The rule's Mongo `_id` as a string. */
|
|
222
|
+
id: string;
|
|
223
|
+
/** Unique action key (e.g. `post_created`). */
|
|
224
|
+
actionType: string;
|
|
225
|
+
/** Signed points the rule awards (may be negative for penalties). */
|
|
226
|
+
points: number;
|
|
227
|
+
/** Category the resulting transaction is filed under. */
|
|
228
|
+
category: ReputationCategory;
|
|
229
|
+
description: string;
|
|
230
|
+
/** Per (user, actionType) cooldown in minutes; 0 disables the cooldown. */
|
|
231
|
+
cooldownInMinutes: number;
|
|
232
|
+
isEnabled: boolean;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A single leaderboard entry. `user` is the populated user document the API
|
|
237
|
+
* returns alongside the lifetime total, derived trust tier, and 1-based rank.
|
|
238
|
+
*/
|
|
239
|
+
export interface ReputationLeaderboardEntry {
|
|
240
|
+
/** The populated user (id, username, name, avatar, publicKey). */
|
|
241
|
+
user: Pick<User, 'id' | 'username' | 'name' | 'avatar' | 'publicKey'> & Partial<User>;
|
|
242
|
+
/** Net lifetime total. */
|
|
243
|
+
total: number;
|
|
244
|
+
/** Derived trust tier. */
|
|
245
|
+
trustTier: TrustTier;
|
|
246
|
+
/** 1-based rank within the leaderboard (`offset + index + 1`). */
|
|
247
|
+
rank: number;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Result of `getReputationInfluence` — the requested context, the single capped
|
|
252
|
+
* weight for that context, and the full influence block.
|
|
253
|
+
*/
|
|
254
|
+
export interface ReputationInfluenceResult {
|
|
255
|
+
context: ReputationInfluenceContext;
|
|
256
|
+
weight: number;
|
|
257
|
+
influence: ReputationInfluence;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Result of `reverseReputationTransaction` — the now-`reversed` original plus
|
|
262
|
+
* the compensating `active` reversal entry.
|
|
263
|
+
*/
|
|
264
|
+
export interface ReverseReputationTransactionResult {
|
|
265
|
+
original: ReputationTransaction;
|
|
266
|
+
reversal: ReputationTransaction;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// =============================================================================
|
|
270
|
+
// INPUT TYPES (mirror packages/api/src/schemas/reputation.schemas.ts)
|
|
271
|
+
// =============================================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Input for `awardReputation`. Awarding is restricted to service tokens (the
|
|
275
|
+
* canonical path) and platform staff; regular users may NOT award reputation.
|
|
276
|
+
* When called with a service token, `applicationId` / `credentialId` are
|
|
277
|
+
* resolved from the token and any client-supplied values are ignored.
|
|
278
|
+
*/
|
|
279
|
+
export interface AwardReputationInput {
|
|
280
|
+
/** The subject whose reputation changes (`_id` or publicKey). */
|
|
281
|
+
userId: string;
|
|
282
|
+
/** The enabled rule's action key (e.g. `post_created`). */
|
|
283
|
+
actionType: string;
|
|
284
|
+
/** Source application id (ignored for service tokens). */
|
|
285
|
+
applicationId?: string;
|
|
286
|
+
/** Source credential id (ignored for service tokens). */
|
|
287
|
+
credentialId?: string;
|
|
288
|
+
/** Opaque originating-action id used as the idempotency key. */
|
|
289
|
+
sourceActionId?: string;
|
|
290
|
+
/** Source-system action type. */
|
|
291
|
+
sourceActionType?: string;
|
|
292
|
+
/** Id of the targeted entity. */
|
|
293
|
+
targetEntityId?: string;
|
|
294
|
+
/** Kind of the targeted entity. */
|
|
295
|
+
targetEntityType?: ReputationTargetEntityType;
|
|
296
|
+
/** Optional human-readable reason (max 500 chars). */
|
|
297
|
+
reason?: string;
|
|
298
|
+
/** Free-form structured metadata from the source system. */
|
|
299
|
+
metadata?: Record<string, unknown>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Input for `createReputationDispute`. The disputer is the authenticated user. */
|
|
303
|
+
export interface CreateReputationDisputeInput {
|
|
304
|
+
/** The transaction being disputed. */
|
|
305
|
+
transactionId: string;
|
|
306
|
+
/** Why the transaction is believed to be wrong (1..1000 chars). */
|
|
307
|
+
reason: string;
|
|
308
|
+
/** Optional supporting evidence (URLs / references; max 20). */
|
|
309
|
+
evidence?: string[];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Input for `resolveReputationDispute` (staff). */
|
|
313
|
+
export interface ResolveReputationDisputeInput {
|
|
314
|
+
/** Accepting reverses the disputed transaction; rejecting restores it. */
|
|
315
|
+
status: 'accepted' | 'rejected';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Input for `upsertReputationRule` (staff). Keyed by `actionType`. */
|
|
319
|
+
export interface UpsertReputationRuleInput {
|
|
320
|
+
/** Unique action key (e.g. `post_created`). */
|
|
321
|
+
actionType: string;
|
|
322
|
+
/** Signed points the rule awards (may be negative). */
|
|
323
|
+
points: number;
|
|
324
|
+
/** Category the resulting transaction is filed under. */
|
|
325
|
+
category: ReputationCategory;
|
|
326
|
+
/** Human-readable description (1..500 chars). */
|
|
327
|
+
description: string;
|
|
328
|
+
/** Per (user, actionType) cooldown in minutes; defaults to 0. */
|
|
329
|
+
cooldownInMinutes?: number;
|
|
330
|
+
/** Whether the rule is active; defaults to true. */
|
|
331
|
+
isEnabled?: boolean;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Input for `reverseReputationTransaction` / `voidReputationTransaction`
|
|
336
|
+
* (staff). The reviewing principal is the authenticated user.
|
|
337
|
+
*/
|
|
338
|
+
export interface ReverseReputationTransactionInput {
|
|
339
|
+
/** Optional human-readable reason (max 500 chars). */
|
|
340
|
+
reason?: string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Cache-key prefix for every cached `GET /reputation/...` response. */
|
|
344
|
+
const REPUTATION_CACHE_PREFIX = 'GET:/reputation/';
|
|
345
|
+
|
|
346
|
+
export function OxyServicesReputationMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
347
|
+
return class extends Base {
|
|
348
|
+
constructor(...args: any[]) {
|
|
349
|
+
super(...(args as [any]));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get a user's cached reputation balance — derived totals, per-category
|
|
354
|
+
* breakdown, trust tier, capped influence weights, and reliability signals.
|
|
355
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
356
|
+
*/
|
|
357
|
+
async getReputationBalance(userId: string): Promise<ReputationBalance> {
|
|
358
|
+
try {
|
|
359
|
+
return await this.makeRequest<ReputationBalance>(
|
|
360
|
+
'GET',
|
|
361
|
+
`/reputation/${encodeURIComponent(userId)}/balance`,
|
|
362
|
+
undefined,
|
|
363
|
+
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
364
|
+
);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw this.handleError(error);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get the reputation leaderboard, ordered by lifetime total descending.
|
|
372
|
+
* @param limit - Page size (server-capped).
|
|
373
|
+
* @param offset - Page offset.
|
|
374
|
+
*/
|
|
375
|
+
async getReputationLeaderboard(
|
|
376
|
+
limit?: number,
|
|
377
|
+
offset?: number,
|
|
378
|
+
): Promise<ReputationLeaderboardEntry[]> {
|
|
379
|
+
try {
|
|
380
|
+
const params: { limit?: number; offset?: number } = {};
|
|
381
|
+
if (limit !== undefined) params.limit = limit;
|
|
382
|
+
if (offset !== undefined) params.offset = offset;
|
|
383
|
+
const res = await this.makeRequest<{ data?: ReputationLeaderboardEntry[] }>(
|
|
384
|
+
'GET',
|
|
385
|
+
'/reputation/leaderboard',
|
|
386
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
387
|
+
{ cache: true, cacheTTL: CACHE_TIMES.LONG },
|
|
388
|
+
);
|
|
389
|
+
return res.data ?? [];
|
|
390
|
+
} catch (error) {
|
|
391
|
+
throw this.handleError(error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* List the enabled reputation rules (for client display).
|
|
397
|
+
*/
|
|
398
|
+
async getReputationRules(): Promise<ReputationRule[]> {
|
|
399
|
+
try {
|
|
400
|
+
const res = await this.makeRequest<{ rules?: ReputationRule[] }>(
|
|
401
|
+
'GET',
|
|
402
|
+
'/reputation/rules',
|
|
403
|
+
undefined,
|
|
404
|
+
{ cache: true, cacheTTL: CACHE_TIMES.EXTRA_LONG },
|
|
405
|
+
);
|
|
406
|
+
return res.rules ?? [];
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw this.handleError(error);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get a user's paginated reputation ledger, newest first (auth required).
|
|
414
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
415
|
+
* @param limit - Page size (server-capped).
|
|
416
|
+
* @param offset - Page offset.
|
|
417
|
+
*/
|
|
418
|
+
async getReputationTransactions(
|
|
419
|
+
userId: string,
|
|
420
|
+
limit?: number,
|
|
421
|
+
offset?: number,
|
|
422
|
+
): Promise<ReputationTransaction[]> {
|
|
423
|
+
try {
|
|
424
|
+
const params: { limit?: number; offset?: number } = {};
|
|
425
|
+
if (limit !== undefined) params.limit = limit;
|
|
426
|
+
if (offset !== undefined) params.offset = offset;
|
|
427
|
+
const res = await this.makeRequest<{ data?: ReputationTransaction[] }>(
|
|
428
|
+
'GET',
|
|
429
|
+
`/reputation/${encodeURIComponent(userId)}/transactions`,
|
|
430
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
431
|
+
{ cache: true, cacheTTL: CACHE_TIMES.SHORT },
|
|
432
|
+
);
|
|
433
|
+
return res.data ?? [];
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw this.handleError(error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get a user's capped influence weight for a given context (auth required).
|
|
441
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
442
|
+
* @param context - The weight axis to read (defaults server-side to `default`).
|
|
443
|
+
*/
|
|
444
|
+
async getReputationInfluence(
|
|
445
|
+
userId: string,
|
|
446
|
+
context?: ReputationInfluenceContext,
|
|
447
|
+
): Promise<ReputationInfluenceResult> {
|
|
448
|
+
try {
|
|
449
|
+
return await this.makeRequest<ReputationInfluenceResult>(
|
|
450
|
+
'GET',
|
|
451
|
+
`/reputation/${encodeURIComponent(userId)}/influence`,
|
|
452
|
+
context ? { context } : undefined,
|
|
453
|
+
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
454
|
+
);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
throw this.handleError(error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Award (or penalise) reputation to a user by `actionType`. Restricted to
|
|
462
|
+
* service tokens and platform staff. Invalidates cached reputation reads.
|
|
463
|
+
* @param input - The award payload (subject, action, source, target, etc.).
|
|
464
|
+
*/
|
|
465
|
+
async awardReputation(input: AwardReputationInput): Promise<ReputationTransaction> {
|
|
466
|
+
try {
|
|
467
|
+
const res = await this.makeRequest<{ transaction: ReputationTransaction }>(
|
|
468
|
+
'POST',
|
|
469
|
+
'/reputation/award',
|
|
470
|
+
input,
|
|
471
|
+
{ cache: false },
|
|
472
|
+
);
|
|
473
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
474
|
+
return res.transaction;
|
|
475
|
+
} catch (error) {
|
|
476
|
+
throw this.handleError(error);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Open a dispute against a transaction (auth required; the disputer is the
|
|
482
|
+
* authenticated user and must own the transaction).
|
|
483
|
+
* @param input - The transaction id, reason, and optional evidence.
|
|
484
|
+
*/
|
|
485
|
+
async createReputationDispute(
|
|
486
|
+
input: CreateReputationDisputeInput,
|
|
487
|
+
): Promise<ReputationDispute> {
|
|
488
|
+
try {
|
|
489
|
+
const res = await this.makeRequest<{ dispute: ReputationDispute }>(
|
|
490
|
+
'POST',
|
|
491
|
+
'/reputation/disputes',
|
|
492
|
+
input,
|
|
493
|
+
{ cache: false },
|
|
494
|
+
);
|
|
495
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
496
|
+
return res.dispute;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
throw this.handleError(error);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* List a user's own reputation disputes (auth required; the caller must be
|
|
504
|
+
* the subject or platform staff).
|
|
505
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
506
|
+
* @param limit - Page size (server-capped).
|
|
507
|
+
* @param offset - Page offset.
|
|
508
|
+
*/
|
|
509
|
+
async getUserReputationDisputes(
|
|
510
|
+
userId: string,
|
|
511
|
+
limit?: number,
|
|
512
|
+
offset?: number,
|
|
513
|
+
): Promise<ReputationDispute[]> {
|
|
514
|
+
try {
|
|
515
|
+
const params: { limit?: number; offset?: number } = {};
|
|
516
|
+
if (limit !== undefined) params.limit = limit;
|
|
517
|
+
if (offset !== undefined) params.offset = offset;
|
|
518
|
+
const res = await this.makeRequest<{ data?: ReputationDispute[] }>(
|
|
519
|
+
'GET',
|
|
520
|
+
`/reputation/${encodeURIComponent(userId)}/disputes`,
|
|
521
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
522
|
+
{ cache: true, cacheTTL: CACHE_TIMES.SHORT },
|
|
523
|
+
);
|
|
524
|
+
return res.data ?? [];
|
|
525
|
+
} catch (error) {
|
|
526
|
+
throw this.handleError(error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// =========================================================================
|
|
531
|
+
// STAFF / ADMIN METHODS (require staff privileges server-side)
|
|
532
|
+
// =========================================================================
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Create or update a reputation rule, keyed by `actionType` (staff only).
|
|
536
|
+
* Invalidates the cached rule list.
|
|
537
|
+
* @param input - The rule definition.
|
|
538
|
+
*/
|
|
539
|
+
async upsertReputationRule(input: UpsertReputationRuleInput): Promise<ReputationRule> {
|
|
540
|
+
try {
|
|
541
|
+
const res = await this.makeRequest<{ rule: ReputationRule }>(
|
|
542
|
+
'POST',
|
|
543
|
+
'/reputation/rules',
|
|
544
|
+
input,
|
|
545
|
+
{ cache: false },
|
|
546
|
+
);
|
|
547
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
548
|
+
return res.rule;
|
|
549
|
+
} catch (error) {
|
|
550
|
+
throw this.handleError(error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Reverse a transaction (staff only): mark the original `reversed` and append
|
|
556
|
+
* a compensating `active` reversal with negated points. Invalidates cached
|
|
557
|
+
* reputation reads.
|
|
558
|
+
* @param transactionId - The transaction's id.
|
|
559
|
+
* @param input - Optional reason for the reversal.
|
|
560
|
+
*/
|
|
561
|
+
async reverseReputationTransaction(
|
|
562
|
+
transactionId: string,
|
|
563
|
+
input?: ReverseReputationTransactionInput,
|
|
564
|
+
): Promise<ReverseReputationTransactionResult> {
|
|
565
|
+
try {
|
|
566
|
+
const res = await this.makeRequest<ReverseReputationTransactionResult>(
|
|
567
|
+
'POST',
|
|
568
|
+
`/reputation/transactions/${encodeURIComponent(transactionId)}/reverse`,
|
|
569
|
+
input ?? {},
|
|
570
|
+
{ cache: false },
|
|
571
|
+
);
|
|
572
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
573
|
+
return res;
|
|
574
|
+
} catch (error) {
|
|
575
|
+
throw this.handleError(error);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Void a transaction (staff only): mark it `voided` so it is excluded from
|
|
581
|
+
* the balance, with NO compensating entry. Invalidates cached reputation
|
|
582
|
+
* reads.
|
|
583
|
+
* @param transactionId - The transaction's id.
|
|
584
|
+
* @param input - Optional reason for the void.
|
|
585
|
+
*/
|
|
586
|
+
async voidReputationTransaction(
|
|
587
|
+
transactionId: string,
|
|
588
|
+
input?: ReverseReputationTransactionInput,
|
|
589
|
+
): Promise<ReputationTransaction> {
|
|
590
|
+
try {
|
|
591
|
+
const res = await this.makeRequest<{ transaction: ReputationTransaction }>(
|
|
592
|
+
'POST',
|
|
593
|
+
`/reputation/transactions/${encodeURIComponent(transactionId)}/void`,
|
|
594
|
+
input ?? {},
|
|
595
|
+
{ cache: false },
|
|
596
|
+
);
|
|
597
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
598
|
+
return res.transaction;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
throw this.handleError(error);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Force a recompute of a user's balance snapshot from their active ledger
|
|
606
|
+
* (staff only). Invalidates cached reputation reads.
|
|
607
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
608
|
+
*/
|
|
609
|
+
async recalculateReputation(userId: string): Promise<ReputationBalance> {
|
|
610
|
+
try {
|
|
611
|
+
const res = await this.makeRequest<ReputationBalance>(
|
|
612
|
+
'POST',
|
|
613
|
+
`/reputation/${encodeURIComponent(userId)}/recalculate`,
|
|
614
|
+
undefined,
|
|
615
|
+
{ cache: false },
|
|
616
|
+
);
|
|
617
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
618
|
+
return res;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
throw this.handleError(error);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Get the open dispute queue across all users (staff only).
|
|
626
|
+
* @param limit - Page size (server-capped).
|
|
627
|
+
* @param offset - Page offset.
|
|
628
|
+
*/
|
|
629
|
+
async getReputationDisputeQueue(
|
|
630
|
+
limit?: number,
|
|
631
|
+
offset?: number,
|
|
632
|
+
): Promise<ReputationDispute[]> {
|
|
633
|
+
try {
|
|
634
|
+
const params: { limit?: number; offset?: number } = {};
|
|
635
|
+
if (limit !== undefined) params.limit = limit;
|
|
636
|
+
if (offset !== undefined) params.offset = offset;
|
|
637
|
+
const res = await this.makeRequest<{ data?: ReputationDispute[] }>(
|
|
638
|
+
'GET',
|
|
639
|
+
'/reputation/disputes',
|
|
640
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
641
|
+
{ cache: true, cacheTTL: CACHE_TIMES.SHORT },
|
|
642
|
+
);
|
|
643
|
+
return res.data ?? [];
|
|
644
|
+
} catch (error) {
|
|
645
|
+
throw this.handleError(error);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Resolve a dispute (staff only). Accepting reverses the disputed
|
|
651
|
+
* transaction; rejecting restores it to `active`. Invalidates cached
|
|
652
|
+
* reputation reads.
|
|
653
|
+
* @param disputeId - The dispute's id.
|
|
654
|
+
* @param input - The resolution (`accepted` or `rejected`).
|
|
655
|
+
*/
|
|
656
|
+
async resolveReputationDispute(
|
|
657
|
+
disputeId: string,
|
|
658
|
+
input: ResolveReputationDisputeInput,
|
|
659
|
+
): Promise<ReputationDispute> {
|
|
660
|
+
try {
|
|
661
|
+
const res = await this.makeRequest<{ dispute: ReputationDispute }>(
|
|
662
|
+
'POST',
|
|
663
|
+
`/reputation/disputes/${encodeURIComponent(disputeId)}/resolve`,
|
|
664
|
+
input,
|
|
665
|
+
{ cache: false },
|
|
666
|
+
);
|
|
667
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
668
|
+
return res.dispute;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
throw this.handleError(error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
@@ -82,7 +82,11 @@ export interface UpdateWorkspaceInput {
|
|
|
82
82
|
|
|
83
83
|
/** Input accepted by `inviteWorkspaceMember`. The owner role cannot be invited. */
|
|
84
84
|
export interface InviteWorkspaceMemberInput {
|
|
85
|
-
|
|
85
|
+
/**
|
|
86
|
+
* The username or email of the user to invite. Resolved to a user server-side;
|
|
87
|
+
* an unknown value yields a 404 "User not found".
|
|
88
|
+
*/
|
|
89
|
+
usernameOrEmail: string;
|
|
86
90
|
role: Exclude<WorkspaceRole, 'owner'>;
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -220,7 +224,9 @@ export function OxyServicesWorkspacesMixin<T extends typeof OxyServicesBase>(Bas
|
|
|
220
224
|
/**
|
|
221
225
|
* Add a member to a workspace.
|
|
222
226
|
* @param workspaceId - The workspace's Mongo `_id`.
|
|
223
|
-
* @param data - Target user
|
|
227
|
+
* @param data - Target user's username or email and role (never `owner`).
|
|
228
|
+
* The server resolves `usernameOrEmail` to a user; an unknown value yields
|
|
229
|
+
* a 404 "User not found".
|
|
224
230
|
*/
|
|
225
231
|
async inviteWorkspaceMember(
|
|
226
232
|
workspaceId: string,
|