convex-feedback 0.1.4 → 0.2.0-beta.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 +87 -27
- package/dist/.tsbuildinfo +1 -1
- package/dist/client/api.d.ts +80 -1
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/config.d.ts +1 -1
- package/dist/client/index.d.ts +9 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +261 -8
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +8 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +295 -5
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/admin.d.ts +130 -0
- package/dist/component/admin.d.ts.map +1 -0
- package/dist/component/admin.js +125 -0
- package/dist/component/admin.js.map +1 -0
- package/dist/component/comments.d.ts +7 -5
- package/dist/component/comments.d.ts.map +1 -1
- package/dist/component/comments.js +4 -3
- package/dist/component/comments.js.map +1 -1
- package/dist/component/crons.d.ts +3 -0
- package/dist/component/crons.d.ts.map +1 -0
- package/dist/component/crons.js +8 -0
- package/dist/component/crons.js.map +1 -0
- package/dist/component/entries.d.ts +29 -16
- package/dist/component/entries.d.ts.map +1 -1
- package/dist/component/entries.js +136 -12
- package/dist/component/entries.js.map +1 -1
- package/dist/component/helpers.d.ts +3 -1
- package/dist/component/helpers.d.ts.map +1 -1
- package/dist/component/helpers.js +28 -0
- package/dist/component/helpers.js.map +1 -1
- package/dist/component/migrations.d.ts +11 -0
- package/dist/component/migrations.d.ts.map +1 -0
- package/dist/component/migrations.js +35 -0
- package/dist/component/migrations.js.map +1 -0
- package/dist/component/model.d.ts +144 -24
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +34 -1
- package/dist/component/model.js.map +1 -1
- package/dist/component/roadmap.d.ts +166 -0
- package/dist/component/roadmap.d.ts.map +1 -0
- package/dist/component/roadmap.js +534 -0
- package/dist/component/roadmap.js.map +1 -0
- package/dist/component/schema.d.ts +67 -5
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +57 -2
- package/dist/component/schema.js.map +1 -1
- package/dist/component/tags.d.ts +58 -0
- package/dist/component/tags.d.ts.map +1 -0
- package/dist/component/tags.js +216 -0
- package/dist/component/tags.js.map +1 -0
- package/dist/react/index.d.ts +173 -12
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +78 -1
- package/dist/react/index.js.map +1 -1
- package/dist/test.d.ts +67 -5
- package/dist/test.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/client/README.md +1 -1
- package/src/client/api.ts +183 -0
- package/src/client/config.ts +1 -1
- package/src/client/index.ts +340 -13
- package/src/component/README.md +3 -1
- package/src/component/_generated/api.ts +8 -0
- package/src/component/_generated/component.ts +373 -9
- package/src/component/admin.ts +180 -0
- package/src/component/comments.ts +5 -2
- package/src/component/crons.ts +16 -0
- package/src/component/entries.ts +175 -11
- package/src/component/helpers.ts +44 -1
- package/src/component/migrations.ts +44 -0
- package/src/component/model.ts +79 -4
- package/src/component/roadmap.ts +627 -0
- package/src/component/schema.ts +61 -1
- package/src/react/README.md +2 -2
- package/src/react/index.ts +152 -4
package/src/client/api.ts
CHANGED
|
@@ -9,9 +9,14 @@ import type {
|
|
|
9
9
|
EntryKind,
|
|
10
10
|
EntrySort,
|
|
11
11
|
EntryStatus,
|
|
12
|
+
EntryStatusFilter,
|
|
12
13
|
FeedbackComment,
|
|
13
14
|
FeedbackEntry,
|
|
14
15
|
FeedbackMetadata,
|
|
16
|
+
AdminFeedbackEntry,
|
|
17
|
+
EntryPriority,
|
|
18
|
+
RoadmapItem,
|
|
19
|
+
RoadmapStatus,
|
|
15
20
|
SimilarEntriesResult,
|
|
16
21
|
} from "../component/model.js";
|
|
17
22
|
|
|
@@ -40,6 +45,9 @@ export type ListEntriesArgs = {
|
|
|
40
45
|
/** Restricts entries to this workflow status. */
|
|
41
46
|
status?: EntryStatus;
|
|
42
47
|
|
|
48
|
+
/** Restricts entries to the indexed open or closed board bucket. */
|
|
49
|
+
statusFilter?: EntryStatusFilter;
|
|
50
|
+
|
|
43
51
|
/**
|
|
44
52
|
* Server-side ordering strategy.
|
|
45
53
|
*
|
|
@@ -74,6 +82,9 @@ export type SearchEntriesArgs = {
|
|
|
74
82
|
/** Optional workflow-status restriction. */
|
|
75
83
|
status?: EntryStatus;
|
|
76
84
|
|
|
85
|
+
/** Optional indexed open or closed board bucket. */
|
|
86
|
+
statusFilter?: EntryStatusFilter;
|
|
87
|
+
|
|
77
88
|
/**
|
|
78
89
|
* Maximum number of results to return.
|
|
79
90
|
*
|
|
@@ -165,6 +176,60 @@ export type SetEntryStatusArgs = {
|
|
|
165
176
|
status: EntryStatus;
|
|
166
177
|
};
|
|
167
178
|
|
|
179
|
+
export type AdminListEntriesArgs = {
|
|
180
|
+
/** Convex cursor-pagination options. */
|
|
181
|
+
paginationOpts: PaginationOptions;
|
|
182
|
+
kinds?: EntryKind[];
|
|
183
|
+
status?: EntryStatus;
|
|
184
|
+
priority?: EntryPriority;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export type AdminSearchEntriesArgs = AdminListEntriesArgs & {
|
|
188
|
+
searchQuery: string;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type SetEntryPriorityArgs = {
|
|
192
|
+
entryId: string;
|
|
193
|
+
priority: EntryPriority | null;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type ListRoadmapArgs = {
|
|
197
|
+
/** Convex cursor-pagination options. */
|
|
198
|
+
paginationOpts: PaginationOptions;
|
|
199
|
+
status?: RoadmapStatus;
|
|
200
|
+
};
|
|
201
|
+
export type SearchRoadmapArgs = { searchQuery: string; limit?: number };
|
|
202
|
+
export type CreateRoadmapArgs = {
|
|
203
|
+
title: string;
|
|
204
|
+
description?: string;
|
|
205
|
+
status: RoadmapStatus;
|
|
206
|
+
};
|
|
207
|
+
export type CreateRoadmapForEntryArgs = CreateRoadmapArgs & {
|
|
208
|
+
entryId: string;
|
|
209
|
+
};
|
|
210
|
+
export type UpdateRoadmapArgs = {
|
|
211
|
+
roadmapId: string;
|
|
212
|
+
title: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
};
|
|
215
|
+
export type DeleteRoadmapArgs = { roadmapId: string };
|
|
216
|
+
export type MoveRoadmapArgs = {
|
|
217
|
+
roadmapId: string;
|
|
218
|
+
status: RoadmapStatus;
|
|
219
|
+
previousItemId?: string;
|
|
220
|
+
nextItemId?: string;
|
|
221
|
+
};
|
|
222
|
+
export type AttachFeedbackToRoadmapArgs = {
|
|
223
|
+
roadmapId: string;
|
|
224
|
+
entryId: string;
|
|
225
|
+
};
|
|
226
|
+
export type DetachFeedbackFromRoadmapArgs = { entryId: string };
|
|
227
|
+
export type ListRoadmapFeedbackArgs = {
|
|
228
|
+
/** Convex cursor-pagination options. */
|
|
229
|
+
paginationOpts: PaginationOptions;
|
|
230
|
+
roadmapId: string;
|
|
231
|
+
};
|
|
232
|
+
|
|
168
233
|
/**
|
|
169
234
|
* Arguments for setting the current actor's entry-upvote state.
|
|
170
235
|
*/
|
|
@@ -301,6 +366,24 @@ export interface FeedbackPublicApi<
|
|
|
301
366
|
Name extends string | undefined = string | undefined,
|
|
302
367
|
RateLimitResult = never,
|
|
303
368
|
> {
|
|
369
|
+
/** Returns whether the host actor has admin permissions. */
|
|
370
|
+
isAdmin: FunctionReference<
|
|
371
|
+
"query",
|
|
372
|
+
"public",
|
|
373
|
+
Record<string, never>,
|
|
374
|
+
boolean,
|
|
375
|
+
Name
|
|
376
|
+
>;
|
|
377
|
+
|
|
378
|
+
/** Returns whether the current request has an authenticated host actor. */
|
|
379
|
+
isAuthenticated: FunctionReference<
|
|
380
|
+
"query",
|
|
381
|
+
"public",
|
|
382
|
+
Record<string, never>,
|
|
383
|
+
boolean,
|
|
384
|
+
Name
|
|
385
|
+
>;
|
|
386
|
+
|
|
304
387
|
/** Returns a cursor-paginated entry list. */
|
|
305
388
|
listEntries: FunctionReference<
|
|
306
389
|
"query",
|
|
@@ -364,6 +447,106 @@ export interface FeedbackPublicApi<
|
|
|
364
447
|
Name
|
|
365
448
|
>;
|
|
366
449
|
|
|
450
|
+
adminListEntries: FunctionReference<
|
|
451
|
+
"query",
|
|
452
|
+
"public",
|
|
453
|
+
AdminListEntriesArgs,
|
|
454
|
+
PaginationResult<AdminFeedbackEntry>,
|
|
455
|
+
Name
|
|
456
|
+
>;
|
|
457
|
+
adminGetEntry: FunctionReference<
|
|
458
|
+
"query",
|
|
459
|
+
"public",
|
|
460
|
+
GetEntryArgs,
|
|
461
|
+
AdminFeedbackEntry | null,
|
|
462
|
+
Name
|
|
463
|
+
>;
|
|
464
|
+
adminSearchEntries: FunctionReference<
|
|
465
|
+
"query",
|
|
466
|
+
"public",
|
|
467
|
+
AdminSearchEntriesArgs,
|
|
468
|
+
PaginationResult<AdminFeedbackEntry>,
|
|
469
|
+
Name
|
|
470
|
+
>;
|
|
471
|
+
setEntryPriority: FunctionReference<
|
|
472
|
+
"mutation",
|
|
473
|
+
"public",
|
|
474
|
+
SetEntryPriorityArgs,
|
|
475
|
+
null | RateLimitResult,
|
|
476
|
+
Name
|
|
477
|
+
>;
|
|
478
|
+
|
|
479
|
+
listRoadmap: FunctionReference<
|
|
480
|
+
"query",
|
|
481
|
+
"public",
|
|
482
|
+
ListRoadmapArgs,
|
|
483
|
+
PaginationResult<RoadmapItem>,
|
|
484
|
+
Name
|
|
485
|
+
>;
|
|
486
|
+
searchRoadmap: FunctionReference<
|
|
487
|
+
"query",
|
|
488
|
+
"public",
|
|
489
|
+
SearchRoadmapArgs,
|
|
490
|
+
RoadmapItem[],
|
|
491
|
+
Name
|
|
492
|
+
>;
|
|
493
|
+
createRoadmap: FunctionReference<
|
|
494
|
+
"mutation",
|
|
495
|
+
"public",
|
|
496
|
+
CreateRoadmapArgs,
|
|
497
|
+
string | RateLimitResult,
|
|
498
|
+
Name
|
|
499
|
+
>;
|
|
500
|
+
createRoadmapForEntry: FunctionReference<
|
|
501
|
+
"mutation",
|
|
502
|
+
"public",
|
|
503
|
+
CreateRoadmapForEntryArgs,
|
|
504
|
+
string | RateLimitResult,
|
|
505
|
+
Name
|
|
506
|
+
>;
|
|
507
|
+
updateRoadmap: FunctionReference<
|
|
508
|
+
"mutation",
|
|
509
|
+
"public",
|
|
510
|
+
UpdateRoadmapArgs,
|
|
511
|
+
null | RateLimitResult,
|
|
512
|
+
Name
|
|
513
|
+
>;
|
|
514
|
+
deleteRoadmap: FunctionReference<
|
|
515
|
+
"mutation",
|
|
516
|
+
"public",
|
|
517
|
+
DeleteRoadmapArgs,
|
|
518
|
+
null | RateLimitResult,
|
|
519
|
+
Name
|
|
520
|
+
>;
|
|
521
|
+
moveRoadmapItem: FunctionReference<
|
|
522
|
+
"mutation",
|
|
523
|
+
"public",
|
|
524
|
+
MoveRoadmapArgs,
|
|
525
|
+
number | RateLimitResult,
|
|
526
|
+
Name
|
|
527
|
+
>;
|
|
528
|
+
attachFeedbackToRoadmap: FunctionReference<
|
|
529
|
+
"mutation",
|
|
530
|
+
"public",
|
|
531
|
+
AttachFeedbackToRoadmapArgs,
|
|
532
|
+
null | RateLimitResult,
|
|
533
|
+
Name
|
|
534
|
+
>;
|
|
535
|
+
detachFeedbackFromRoadmap: FunctionReference<
|
|
536
|
+
"mutation",
|
|
537
|
+
"public",
|
|
538
|
+
DetachFeedbackFromRoadmapArgs,
|
|
539
|
+
null | RateLimitResult,
|
|
540
|
+
Name
|
|
541
|
+
>;
|
|
542
|
+
listRoadmapFeedback: FunctionReference<
|
|
543
|
+
"query",
|
|
544
|
+
"public",
|
|
545
|
+
ListRoadmapFeedbackArgs,
|
|
546
|
+
PaginationResult<FeedbackEntry>,
|
|
547
|
+
Name
|
|
548
|
+
>;
|
|
549
|
+
|
|
367
550
|
/** Sets entry-upvote state, or returns a configured rate-limit rejection. */
|
|
368
551
|
setEntryUpvote: FunctionReference<
|
|
369
552
|
"mutation",
|
package/src/client/config.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface FeedbackEntriesConfig {
|
|
|
42
42
|
/**
|
|
43
43
|
* Whether entry authors may edit their own title and body.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
45
|
+
* Admins are governed separately by the resolved actor permissions.
|
|
46
46
|
*
|
|
47
47
|
* @default true
|
|
48
48
|
*/
|
package/src/client/index.ts
CHANGED
|
@@ -23,14 +23,20 @@ import {
|
|
|
23
23
|
|
|
24
24
|
import type { ComponentApi } from "../component/_generated/component.js";
|
|
25
25
|
import {
|
|
26
|
+
adminEntryValidator,
|
|
26
27
|
commentSortValidator,
|
|
27
28
|
entryKindValidator,
|
|
29
|
+
entryPriorityValidator,
|
|
28
30
|
entrySortValidator,
|
|
31
|
+
entryStatusFilterValidator,
|
|
29
32
|
entryStatusValidator,
|
|
30
33
|
feedbackMetadataValidator,
|
|
31
34
|
publicCommentValidator,
|
|
32
35
|
publicEntryValidator,
|
|
36
|
+
roadmapItemValidator,
|
|
37
|
+
roadmapStatusValidator,
|
|
33
38
|
similarEntriesValidator,
|
|
39
|
+
actorIsAdmin,
|
|
34
40
|
type FeedbackActor,
|
|
35
41
|
} from "../component/model.js";
|
|
36
42
|
import type { FeedbackPublicApi } from "./api.js";
|
|
@@ -40,15 +46,20 @@ import {
|
|
|
40
46
|
} from "./config.js";
|
|
41
47
|
|
|
42
48
|
export type {
|
|
49
|
+
AdminFeedbackEntry,
|
|
43
50
|
CommentSort,
|
|
44
51
|
EntryKind,
|
|
52
|
+
EntryPriority,
|
|
45
53
|
EntrySort,
|
|
46
54
|
EntryStatus,
|
|
55
|
+
EntryStatusFilter,
|
|
47
56
|
FeedbackActor,
|
|
48
57
|
FeedbackComment,
|
|
49
58
|
FeedbackEntry,
|
|
50
59
|
FeedbackMetadata,
|
|
51
60
|
FeedbackMetadataValue,
|
|
61
|
+
RoadmapItem,
|
|
62
|
+
RoadmapStatus,
|
|
52
63
|
SimilarEntriesResult,
|
|
53
64
|
} from "../component/model.js";
|
|
54
65
|
export type { FeedbackPublicApi } from "./api.js";
|
|
@@ -125,10 +136,13 @@ export interface ThrowingFeedbackRateLimitConfig {
|
|
|
125
136
|
behavior?: "throw";
|
|
126
137
|
|
|
127
138
|
/**
|
|
128
|
-
* Whether all configured limiter groups apply to
|
|
139
|
+
* Whether all configured limiter groups apply to admins.
|
|
129
140
|
*
|
|
130
141
|
* @default false
|
|
131
142
|
*/
|
|
143
|
+
limitAdmins?: boolean;
|
|
144
|
+
|
|
145
|
+
/** @deprecated Use `limitAdmins`. */
|
|
132
146
|
limitModerators?: boolean;
|
|
133
147
|
|
|
134
148
|
/** Not accepted in throwing mode; select `"return"` to provide a validator. */
|
|
@@ -168,10 +182,13 @@ export interface ReturningFeedbackRateLimitConfig<
|
|
|
168
182
|
returns: ReturnsValidator;
|
|
169
183
|
|
|
170
184
|
/**
|
|
171
|
-
* Whether all configured limiter groups apply to
|
|
185
|
+
* Whether all configured limiter groups apply to admins.
|
|
172
186
|
*
|
|
173
187
|
* @default false
|
|
174
188
|
*/
|
|
189
|
+
limitAdmins?: boolean;
|
|
190
|
+
|
|
191
|
+
/** @deprecated Use `limitAdmins`. */
|
|
175
192
|
limitModerators?: boolean;
|
|
176
193
|
}
|
|
177
194
|
|
|
@@ -260,10 +277,10 @@ export type ThrowingExposeFeedbackOptions = Omit<
|
|
|
260
277
|
/** Optional component behavior and throwing rate-limit overrides. */
|
|
261
278
|
config?: FeedbackConfigOverrides & {
|
|
262
279
|
/**
|
|
263
|
-
* Controls throwing behavior and the
|
|
280
|
+
* Controls throwing behavior and the admin exemption.
|
|
264
281
|
*
|
|
265
282
|
* Omit this object to use `behavior: "throw"` and
|
|
266
|
-
* `
|
|
283
|
+
* `limitAdmins: false`.
|
|
267
284
|
*/
|
|
268
285
|
rateLimiting?: FeedbackRateLimitConfig;
|
|
269
286
|
};
|
|
@@ -314,9 +331,9 @@ function requireActor(actor: FeedbackActor | null): FeedbackActor {
|
|
|
314
331
|
return actor;
|
|
315
332
|
}
|
|
316
333
|
|
|
317
|
-
function
|
|
318
|
-
if (!actor
|
|
319
|
-
throw new ConvexError("
|
|
334
|
+
function requireAdmin(actor: FeedbackActor): void {
|
|
335
|
+
if (!actorIsAdmin(actor)) {
|
|
336
|
+
throw new ConvexError("Admin permissions are required.");
|
|
320
337
|
}
|
|
321
338
|
}
|
|
322
339
|
|
|
@@ -347,10 +364,9 @@ async function applyRateLimiter<
|
|
|
347
364
|
| ReturningFeedbackRateLimitConfig<FeedbackRateLimitReturnValidator>
|
|
348
365
|
| undefined,
|
|
349
366
|
): Promise<RateLimitResult<ReturnsValidator> | undefined> {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
) {
|
|
367
|
+
const limitAdmins =
|
|
368
|
+
rateLimitConfig?.limitAdmins ?? rateLimitConfig?.limitModerators ?? false;
|
|
369
|
+
if (limiter === undefined || (actorIsAdmin(actor) && !limitAdmins)) {
|
|
354
370
|
return undefined;
|
|
355
371
|
}
|
|
356
372
|
|
|
@@ -421,13 +437,50 @@ function buildFeedbackApi<
|
|
|
421
437
|
v.object({ active: v.boolean(), likeCount: v.number() }),
|
|
422
438
|
rateLimitReturnValidator,
|
|
423
439
|
);
|
|
440
|
+
const numberReturns = rateLimitedReturns(
|
|
441
|
+
v.number(),
|
|
442
|
+
rateLimitReturnValidator,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const requireAdminActor = async (ctx: FeedbackAuthContext) => {
|
|
446
|
+
const actor = requireActor(await options.actor(ctx));
|
|
447
|
+
requireAdmin(actor);
|
|
448
|
+
return actor;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const applyAdminEditLimit = async (
|
|
452
|
+
ctx: FeedbackRateLimitContext,
|
|
453
|
+
actor: FeedbackActor,
|
|
454
|
+
) =>
|
|
455
|
+
await applyRateLimiter(
|
|
456
|
+
ctx,
|
|
457
|
+
actor,
|
|
458
|
+
options.rateLimiters?.editContent,
|
|
459
|
+
rateLimitConfig,
|
|
460
|
+
);
|
|
424
461
|
|
|
425
462
|
return {
|
|
463
|
+
isAdmin: queryGeneric({
|
|
464
|
+
args: {},
|
|
465
|
+
returns: v.boolean(),
|
|
466
|
+
handler: async (ctx) => {
|
|
467
|
+
const actor = await options.actor(ctx);
|
|
468
|
+
return actor !== null && actorIsAdmin(actor);
|
|
469
|
+
},
|
|
470
|
+
}),
|
|
471
|
+
|
|
472
|
+
isAuthenticated: queryGeneric({
|
|
473
|
+
args: {},
|
|
474
|
+
returns: v.boolean(),
|
|
475
|
+
handler: async (ctx) => (await options.actor(ctx)) !== null,
|
|
476
|
+
}),
|
|
477
|
+
|
|
426
478
|
listEntries: queryGeneric({
|
|
427
479
|
args: {
|
|
428
480
|
paginationOpts: paginationOptsValidator,
|
|
429
481
|
kinds: v.optional(v.array(entryKindValidator)),
|
|
430
482
|
status: v.optional(entryStatusValidator),
|
|
483
|
+
statusFilter: v.optional(entryStatusFilterValidator),
|
|
431
484
|
sort: v.optional(entrySortValidator),
|
|
432
485
|
},
|
|
433
486
|
returns: paginationResultValidator(publicEntryValidator),
|
|
@@ -441,6 +494,9 @@ function buildFeedbackApi<
|
|
|
441
494
|
),
|
|
442
495
|
...(args.kinds === undefined ? {} : { kinds: args.kinds }),
|
|
443
496
|
...(args.status === undefined ? {} : { status: args.status }),
|
|
497
|
+
...(args.statusFilter === undefined
|
|
498
|
+
? {}
|
|
499
|
+
: { statusFilter: args.statusFilter }),
|
|
444
500
|
sort: args.sort ?? config.entries.defaultSort,
|
|
445
501
|
...actorIdFields(actor),
|
|
446
502
|
});
|
|
@@ -455,7 +511,9 @@ function buildFeedbackApi<
|
|
|
455
511
|
return await ctx.runQuery(component.entries.get, {
|
|
456
512
|
entryId: args.entryId,
|
|
457
513
|
...actorIdFields(actor),
|
|
458
|
-
...(actor
|
|
514
|
+
...(actor !== null && actorIsAdmin(actor)
|
|
515
|
+
? { viewerIsAdmin: true }
|
|
516
|
+
: {}),
|
|
459
517
|
});
|
|
460
518
|
},
|
|
461
519
|
}),
|
|
@@ -465,6 +523,7 @@ function buildFeedbackApi<
|
|
|
465
523
|
searchQuery: v.string(),
|
|
466
524
|
kinds: v.optional(v.array(entryKindValidator)),
|
|
467
525
|
status: v.optional(entryStatusValidator),
|
|
526
|
+
statusFilter: v.optional(entryStatusFilterValidator),
|
|
468
527
|
limit: v.optional(v.number()),
|
|
469
528
|
},
|
|
470
529
|
returns: v.array(publicEntryValidator),
|
|
@@ -475,6 +534,9 @@ function buildFeedbackApi<
|
|
|
475
534
|
searchQuery: args.searchQuery,
|
|
476
535
|
...(args.kinds === undefined ? {} : { kinds: args.kinds }),
|
|
477
536
|
...(args.status === undefined ? {} : { status: args.status }),
|
|
537
|
+
...(args.statusFilter === undefined
|
|
538
|
+
? {}
|
|
539
|
+
: { statusFilter: args.statusFilter }),
|
|
478
540
|
limit: clampPositive(
|
|
479
541
|
args.limit,
|
|
480
542
|
config.search.defaultLimit,
|
|
@@ -578,7 +640,7 @@ function buildFeedbackApi<
|
|
|
578
640
|
returns: nullReturns,
|
|
579
641
|
handler: async (ctx, args) => {
|
|
580
642
|
const actor = requireActor(await options.actor(ctx));
|
|
581
|
-
|
|
643
|
+
requireAdmin(actor);
|
|
582
644
|
const limited = await applyRateLimiter(
|
|
583
645
|
asRateLimitContext(ctx),
|
|
584
646
|
actor,
|
|
@@ -594,6 +656,87 @@ function buildFeedbackApi<
|
|
|
594
656
|
},
|
|
595
657
|
}),
|
|
596
658
|
|
|
659
|
+
adminListEntries: queryGeneric({
|
|
660
|
+
args: {
|
|
661
|
+
paginationOpts: paginationOptsValidator,
|
|
662
|
+
kinds: v.optional(v.array(entryKindValidator)),
|
|
663
|
+
status: v.optional(entryStatusValidator),
|
|
664
|
+
priority: v.optional(entryPriorityValidator),
|
|
665
|
+
},
|
|
666
|
+
returns: paginationResultValidator(adminEntryValidator),
|
|
667
|
+
handler: async (ctx, args) => {
|
|
668
|
+
const actor = await requireAdminActor(ctx);
|
|
669
|
+
return await ctx.runQuery(component.admin.listEntries, {
|
|
670
|
+
...(args.kinds === undefined ? {} : { kinds: args.kinds }),
|
|
671
|
+
...(args.status === undefined ? {} : { status: args.status }),
|
|
672
|
+
...(args.priority === undefined ? {} : { priority: args.priority }),
|
|
673
|
+
paginationOpts: clampPagination(
|
|
674
|
+
args.paginationOpts,
|
|
675
|
+
config.entries.maxPageSize,
|
|
676
|
+
),
|
|
677
|
+
viewerActorId: actor.id,
|
|
678
|
+
});
|
|
679
|
+
},
|
|
680
|
+
}),
|
|
681
|
+
|
|
682
|
+
adminGetEntry: queryGeneric({
|
|
683
|
+
args: { entryId: v.string() },
|
|
684
|
+
returns: v.union(adminEntryValidator, v.null()),
|
|
685
|
+
handler: async (ctx, args) => {
|
|
686
|
+
const actor = await requireAdminActor(ctx);
|
|
687
|
+
return await ctx.runQuery(component.admin.getEntry, {
|
|
688
|
+
entryId: args.entryId,
|
|
689
|
+
viewerActorId: actor.id,
|
|
690
|
+
});
|
|
691
|
+
},
|
|
692
|
+
}),
|
|
693
|
+
|
|
694
|
+
adminSearchEntries: queryGeneric({
|
|
695
|
+
args: {
|
|
696
|
+
paginationOpts: paginationOptsValidator,
|
|
697
|
+
searchQuery: v.string(),
|
|
698
|
+
kinds: v.optional(v.array(entryKindValidator)),
|
|
699
|
+
status: v.optional(entryStatusValidator),
|
|
700
|
+
priority: v.optional(entryPriorityValidator),
|
|
701
|
+
},
|
|
702
|
+
returns: paginationResultValidator(adminEntryValidator),
|
|
703
|
+
handler: async (ctx, args) => {
|
|
704
|
+
const actor = await requireAdminActor(ctx);
|
|
705
|
+
return await ctx.runQuery(component.admin.searchEntries, {
|
|
706
|
+
searchQuery: args.searchQuery,
|
|
707
|
+
...(args.kinds === undefined ? {} : { kinds: args.kinds }),
|
|
708
|
+
...(args.status === undefined ? {} : { status: args.status }),
|
|
709
|
+
...(args.priority === undefined ? {} : { priority: args.priority }),
|
|
710
|
+
paginationOpts: clampPagination(
|
|
711
|
+
args.paginationOpts,
|
|
712
|
+
config.entries.maxPageSize,
|
|
713
|
+
),
|
|
714
|
+
viewerActorId: actor.id,
|
|
715
|
+
});
|
|
716
|
+
},
|
|
717
|
+
}),
|
|
718
|
+
|
|
719
|
+
setEntryPriority: mutationGeneric({
|
|
720
|
+
args: {
|
|
721
|
+
entryId: v.string(),
|
|
722
|
+
priority: v.union(entryPriorityValidator, v.null()),
|
|
723
|
+
},
|
|
724
|
+
returns: nullReturns,
|
|
725
|
+
handler: async (ctx, args) => {
|
|
726
|
+
const actor = await requireAdminActor(ctx);
|
|
727
|
+
const limited = await applyAdminEditLimit(
|
|
728
|
+
asRateLimitContext(ctx),
|
|
729
|
+
actor,
|
|
730
|
+
);
|
|
731
|
+
if (limited !== undefined) return limited;
|
|
732
|
+
return await ctx.runMutation(component.entries.setPriority, {
|
|
733
|
+
actor,
|
|
734
|
+
entryId: args.entryId,
|
|
735
|
+
priority: args.priority,
|
|
736
|
+
});
|
|
737
|
+
},
|
|
738
|
+
}),
|
|
739
|
+
|
|
597
740
|
setEntryUpvote: mutationGeneric({
|
|
598
741
|
args: { entryId: v.string(), desiredState: v.boolean() },
|
|
599
742
|
returns: entryUpvoteReturns,
|
|
@@ -729,6 +872,190 @@ function buildFeedbackApi<
|
|
|
729
872
|
});
|
|
730
873
|
},
|
|
731
874
|
}),
|
|
875
|
+
|
|
876
|
+
listRoadmap: queryGeneric({
|
|
877
|
+
args: {
|
|
878
|
+
paginationOpts: paginationOptsValidator,
|
|
879
|
+
status: v.optional(roadmapStatusValidator),
|
|
880
|
+
},
|
|
881
|
+
returns: paginationResultValidator(roadmapItemValidator),
|
|
882
|
+
handler: async (ctx, args) => {
|
|
883
|
+
return await ctx.runQuery(component.roadmap.list, {
|
|
884
|
+
...args,
|
|
885
|
+
paginationOpts: clampPagination(
|
|
886
|
+
args.paginationOpts,
|
|
887
|
+
config.entries.maxPageSize,
|
|
888
|
+
),
|
|
889
|
+
});
|
|
890
|
+
},
|
|
891
|
+
}),
|
|
892
|
+
|
|
893
|
+
searchRoadmap: queryGeneric({
|
|
894
|
+
args: { searchQuery: v.string(), limit: v.optional(v.number()) },
|
|
895
|
+
returns: v.array(roadmapItemValidator),
|
|
896
|
+
handler: async (ctx, args) => {
|
|
897
|
+
return await ctx.runQuery(component.roadmap.search, {
|
|
898
|
+
searchQuery: args.searchQuery,
|
|
899
|
+
limit: clampPositive(args.limit, 10, 50),
|
|
900
|
+
});
|
|
901
|
+
},
|
|
902
|
+
}),
|
|
903
|
+
|
|
904
|
+
createRoadmap: mutationGeneric({
|
|
905
|
+
args: {
|
|
906
|
+
title: v.string(),
|
|
907
|
+
description: v.optional(v.string()),
|
|
908
|
+
status: roadmapStatusValidator,
|
|
909
|
+
},
|
|
910
|
+
returns: idReturns,
|
|
911
|
+
handler: async (ctx, args) => {
|
|
912
|
+
const actor = await requireAdminActor(ctx);
|
|
913
|
+
const limited = await applyAdminEditLimit(
|
|
914
|
+
asRateLimitContext(ctx),
|
|
915
|
+
actor,
|
|
916
|
+
);
|
|
917
|
+
if (limited !== undefined) return limited;
|
|
918
|
+
return await ctx.runMutation(component.roadmap.create, {
|
|
919
|
+
actor,
|
|
920
|
+
...args,
|
|
921
|
+
});
|
|
922
|
+
},
|
|
923
|
+
}),
|
|
924
|
+
|
|
925
|
+
createRoadmapForEntry: mutationGeneric({
|
|
926
|
+
args: {
|
|
927
|
+
title: v.string(),
|
|
928
|
+
description: v.optional(v.string()),
|
|
929
|
+
status: roadmapStatusValidator,
|
|
930
|
+
entryId: v.string(),
|
|
931
|
+
},
|
|
932
|
+
returns: idReturns,
|
|
933
|
+
handler: async (ctx, args) => {
|
|
934
|
+
const actor = await requireAdminActor(ctx);
|
|
935
|
+
const limited = await applyAdminEditLimit(
|
|
936
|
+
asRateLimitContext(ctx),
|
|
937
|
+
actor,
|
|
938
|
+
);
|
|
939
|
+
if (limited !== undefined) return limited;
|
|
940
|
+
return await ctx.runMutation(component.roadmap.createForEntry, {
|
|
941
|
+
actor,
|
|
942
|
+
...args,
|
|
943
|
+
});
|
|
944
|
+
},
|
|
945
|
+
}),
|
|
946
|
+
|
|
947
|
+
updateRoadmap: mutationGeneric({
|
|
948
|
+
args: {
|
|
949
|
+
roadmapId: v.string(),
|
|
950
|
+
title: v.string(),
|
|
951
|
+
description: v.optional(v.string()),
|
|
952
|
+
},
|
|
953
|
+
returns: nullReturns,
|
|
954
|
+
handler: async (ctx, args) => {
|
|
955
|
+
const actor = await requireAdminActor(ctx);
|
|
956
|
+
const limited = await applyAdminEditLimit(
|
|
957
|
+
asRateLimitContext(ctx),
|
|
958
|
+
actor,
|
|
959
|
+
);
|
|
960
|
+
if (limited !== undefined) return limited;
|
|
961
|
+
return await ctx.runMutation(component.roadmap.update, {
|
|
962
|
+
actor,
|
|
963
|
+
...args,
|
|
964
|
+
});
|
|
965
|
+
},
|
|
966
|
+
}),
|
|
967
|
+
|
|
968
|
+
deleteRoadmap: mutationGeneric({
|
|
969
|
+
args: { roadmapId: v.string() },
|
|
970
|
+
returns: nullReturns,
|
|
971
|
+
handler: async (ctx, args) => {
|
|
972
|
+
const actor = await requireAdminActor(ctx);
|
|
973
|
+
const limited = await applyAdminEditLimit(
|
|
974
|
+
asRateLimitContext(ctx),
|
|
975
|
+
actor,
|
|
976
|
+
);
|
|
977
|
+
if (limited !== undefined) return limited;
|
|
978
|
+
return await ctx.runMutation(component.roadmap.remove, {
|
|
979
|
+
actor,
|
|
980
|
+
...args,
|
|
981
|
+
});
|
|
982
|
+
},
|
|
983
|
+
}),
|
|
984
|
+
|
|
985
|
+
moveRoadmapItem: mutationGeneric({
|
|
986
|
+
args: {
|
|
987
|
+
roadmapId: v.string(),
|
|
988
|
+
status: roadmapStatusValidator,
|
|
989
|
+
previousItemId: v.optional(v.string()),
|
|
990
|
+
nextItemId: v.optional(v.string()),
|
|
991
|
+
},
|
|
992
|
+
returns: numberReturns,
|
|
993
|
+
handler: async (ctx, args) => {
|
|
994
|
+
const actor = await requireAdminActor(ctx);
|
|
995
|
+
const limited = await applyAdminEditLimit(
|
|
996
|
+
asRateLimitContext(ctx),
|
|
997
|
+
actor,
|
|
998
|
+
);
|
|
999
|
+
if (limited !== undefined) return limited;
|
|
1000
|
+
return await ctx.runMutation(component.roadmap.move, {
|
|
1001
|
+
actor,
|
|
1002
|
+
...args,
|
|
1003
|
+
});
|
|
1004
|
+
},
|
|
1005
|
+
}),
|
|
1006
|
+
|
|
1007
|
+
attachFeedbackToRoadmap: mutationGeneric({
|
|
1008
|
+
args: { roadmapId: v.string(), entryId: v.string() },
|
|
1009
|
+
returns: nullReturns,
|
|
1010
|
+
handler: async (ctx, args) => {
|
|
1011
|
+
const actor = await requireAdminActor(ctx);
|
|
1012
|
+
const limited = await applyAdminEditLimit(
|
|
1013
|
+
asRateLimitContext(ctx),
|
|
1014
|
+
actor,
|
|
1015
|
+
);
|
|
1016
|
+
if (limited !== undefined) return limited;
|
|
1017
|
+
return await ctx.runMutation(component.roadmap.attachFeedback, {
|
|
1018
|
+
actor,
|
|
1019
|
+
...args,
|
|
1020
|
+
});
|
|
1021
|
+
},
|
|
1022
|
+
}),
|
|
1023
|
+
|
|
1024
|
+
detachFeedbackFromRoadmap: mutationGeneric({
|
|
1025
|
+
args: { entryId: v.string() },
|
|
1026
|
+
returns: nullReturns,
|
|
1027
|
+
handler: async (ctx, args) => {
|
|
1028
|
+
const actor = await requireAdminActor(ctx);
|
|
1029
|
+
const limited = await applyAdminEditLimit(
|
|
1030
|
+
asRateLimitContext(ctx),
|
|
1031
|
+
actor,
|
|
1032
|
+
);
|
|
1033
|
+
if (limited !== undefined) return limited;
|
|
1034
|
+
return await ctx.runMutation(component.roadmap.detachFeedback, {
|
|
1035
|
+
actor,
|
|
1036
|
+
...args,
|
|
1037
|
+
});
|
|
1038
|
+
},
|
|
1039
|
+
}),
|
|
1040
|
+
|
|
1041
|
+
listRoadmapFeedback: queryGeneric({
|
|
1042
|
+
args: {
|
|
1043
|
+
paginationOpts: paginationOptsValidator,
|
|
1044
|
+
roadmapId: v.string(),
|
|
1045
|
+
},
|
|
1046
|
+
returns: paginationResultValidator(publicEntryValidator),
|
|
1047
|
+
handler: async (ctx, args) => {
|
|
1048
|
+
const actor = await options.actor(ctx);
|
|
1049
|
+
return await ctx.runQuery(component.roadmap.listFeedback, {
|
|
1050
|
+
...args,
|
|
1051
|
+
paginationOpts: clampPagination(
|
|
1052
|
+
args.paginationOpts,
|
|
1053
|
+
config.entries.maxPageSize,
|
|
1054
|
+
),
|
|
1055
|
+
...(actor === null ? {} : { viewerActorId: actor.id }),
|
|
1056
|
+
});
|
|
1057
|
+
},
|
|
1058
|
+
}),
|
|
732
1059
|
} satisfies Record<keyof FeedbackPublicApi, unknown>;
|
|
733
1060
|
}
|
|
734
1061
|
|