convex-feedback 0.2.0-beta.8 → 0.2.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 +398 -7
- package/dist/.tsbuildinfo +1 -1
- package/dist/client/api.d.ts +49 -9
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/config.d.ts +1 -3
- package/dist/client/config.d.ts.map +1 -1
- package/dist/client/config.js.map +1 -1
- package/dist/client/index.d.ts +707 -15
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +243 -12
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -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 +222 -4
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/admin.d.ts +5 -90
- package/dist/component/admin.d.ts.map +1 -1
- package/dist/component/admin.js +59 -29
- package/dist/component/admin.js.map +1 -1
- package/dist/component/comments.d.ts +107 -15
- package/dist/component/comments.d.ts.map +1 -1
- package/dist/component/comments.js +342 -28
- package/dist/component/comments.js.map +1 -1
- package/dist/component/crons.d.ts.map +1 -1
- package/dist/component/crons.js +4 -0
- package/dist/component/crons.js.map +1 -1
- package/dist/component/entries.d.ts +119 -91
- package/dist/component/entries.d.ts.map +1 -1
- package/dist/component/entries.js +348 -29
- package/dist/component/entries.js.map +1 -1
- package/dist/component/helpers.d.ts +32 -1
- package/dist/component/helpers.d.ts.map +1 -1
- package/dist/component/helpers.js +121 -4
- package/dist/component/helpers.js.map +1 -1
- package/dist/component/migrations.d.ts +24 -0
- package/dist/component/migrations.d.ts.map +1 -1
- package/dist/component/migrations.js +318 -0
- package/dist/component/migrations.js.map +1 -1
- package/dist/component/model.d.ts +455 -120
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +70 -2
- package/dist/component/model.js.map +1 -1
- package/dist/component/reactions.d.ts +37 -0
- package/dist/component/reactions.d.ts.map +1 -0
- package/dist/component/reactions.js +52 -0
- package/dist/component/reactions.js.map +1 -0
- package/dist/component/roadmap.d.ts +10 -46
- package/dist/component/roadmap.d.ts.map +1 -1
- package/dist/component/roadmap.js +239 -21
- package/dist/component/roadmap.js.map +1 -1
- package/dist/component/schema.d.ts +33 -5
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +40 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/react/index.d.ts +26 -238
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +65 -2
- package/dist/react/index.js.map +1 -1
- package/dist/test.d.ts +23 -5
- package/dist/test.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/README.md +6 -1
- package/src/client/api.ts +89 -7
- package/src/client/config.ts +1 -3
- package/src/client/index.ts +1237 -34
- package/src/component/README.md +32 -4
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +290 -8
- package/src/component/admin.ts +88 -30
- package/src/component/comments.ts +405 -26
- package/src/component/crons.ts +10 -0
- package/src/component/entries.ts +388 -27
- package/src/component/helpers.ts +163 -4
- package/src/component/migrations.ts +415 -0
- package/src/component/model.ts +381 -117
- package/src/component/reactions.ts +60 -0
- package/src/component/roadmap.ts +357 -35
- package/src/component/schema.ts +40 -4
- package/src/react/README.md +5 -0
- package/src/react/index.ts +90 -3
package/src/component/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
This directory is the isolated Convex component backend.
|
|
4
4
|
|
|
5
|
-
- `schema.ts`:
|
|
6
|
-
- `entries.ts`: entry CRUD, full-text search, duplicate suggestions, entry voting, indexed pagination.
|
|
7
|
-
- `comments.ts`: direct-child pagination, recursive write validation,
|
|
8
|
-
- `
|
|
5
|
+
- `schema.ts`: durable feedback domain tables plus internal roadmap-rebalance bookkeeping, all with query-driven indexes.
|
|
6
|
+
- `entries.ts`: entry CRUD, full-text search, duplicate suggestions, entry voting, indexed pagination, actor activity pagination, and bounded permanent-deletion cleanup.
|
|
7
|
+
- `comments.ts`: direct-child pagination, recursive write validation, bounded subtree deletion, comment likes, and actor activity pagination.
|
|
8
|
+
- `reactions.ts`: actor activity pagination with live entry/comment target resolution.
|
|
9
|
+
- `migrations.ts`: bounded, idempotent status backfills and legacy comment
|
|
10
|
+
cleanup.
|
|
9
11
|
- `crons.ts`: immediate upgrade migration and temporary 30-day self-healing schedule.
|
|
10
12
|
- `model.ts`: reusable validators and public model types.
|
|
11
13
|
- `helpers.ts`: normalization, validation, and serializers.
|
|
@@ -13,3 +15,29 @@ This directory is the isolated Convex component backend.
|
|
|
13
15
|
- `_generated/`: generated component API/server/data-model types. Regenerate after backend changes.
|
|
14
16
|
|
|
15
17
|
Important: component public functions become internal references when installed by a host app. The host must wrap them before exposing them to clients. Component IDs become strings at that boundary.
|
|
18
|
+
|
|
19
|
+
Lifecycle callbacks belong to the host wrapper, not this component. The wrapper
|
|
20
|
+
may transform or reject creation input before calling a component mutation and
|
|
21
|
+
may run after callbacks with the mutation's returned context. Keep authentication,
|
|
22
|
+
host business rules, and notification providers outside the component; component
|
|
23
|
+
validation remains authoritative.
|
|
24
|
+
|
|
25
|
+
Actor activity component queries accept a trusted `actorId` and use the
|
|
26
|
+
`by_actor` indexes. `entries.listByActor` can additionally receive
|
|
27
|
+
`includeAdminContext: true` for server-side consumers that need retained
|
|
28
|
+
metadata, priority, or roadmap context. The host `listUser*` wrappers resolve
|
|
29
|
+
the actor from the host callback and expose only their public activity shapes.
|
|
30
|
+
|
|
31
|
+
Entry deletion is an asynchronous permanent cleanup. The entry is marked as
|
|
32
|
+
deleting and hidden immediately, its roadmap link is detached, and scheduled
|
|
33
|
+
batches remove entry reactions, comment reactions, and all comments before the
|
|
34
|
+
entry document is hard-deleted. Comment deletion also marks its subtree and
|
|
35
|
+
removes descendants and reactions in bounded scheduled batches. Activity reads
|
|
36
|
+
omit pending and completed deletions.
|
|
37
|
+
|
|
38
|
+
During the upgrade window, `comments.deletedAt` remains an optional internal
|
|
39
|
+
schema field only so older installations can deploy safely. The legacy comment
|
|
40
|
+
migration permanently removes tombstones, their descendants, and orphaned
|
|
41
|
+
reactions in bounded batches. Its progress markers and `deletedAt` field are
|
|
42
|
+
internal only. After supported installations have migrated, a future release
|
|
43
|
+
can remove those compatibility fields from the schema.
|
|
@@ -15,6 +15,7 @@ import type * as entries from "../entries.js";
|
|
|
15
15
|
import type * as helpers from "../helpers.js";
|
|
16
16
|
import type * as migrations from "../migrations.js";
|
|
17
17
|
import type * as model from "../model.js";
|
|
18
|
+
import type * as reactions from "../reactions.js";
|
|
18
19
|
import type * as roadmap from "../roadmap.js";
|
|
19
20
|
import type * as types from "../types.js";
|
|
20
21
|
|
|
@@ -33,6 +34,7 @@ const fullApi: ApiFromModules<{
|
|
|
33
34
|
helpers: typeof helpers;
|
|
34
35
|
migrations: typeof migrations;
|
|
35
36
|
model: typeof model;
|
|
37
|
+
reactions: typeof reactions;
|
|
36
38
|
roadmap: typeof roadmap;
|
|
37
39
|
types: typeof types;
|
|
38
40
|
}> = anyApi as any;
|
|
@@ -87,6 +87,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
87
87
|
| "in_progress"
|
|
88
88
|
| "completed"
|
|
89
89
|
| "closed";
|
|
90
|
+
statusFilter?: "open" | "closed";
|
|
90
91
|
viewerActorId: string;
|
|
91
92
|
},
|
|
92
93
|
{
|
|
@@ -155,6 +156,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
155
156
|
| "in_progress"
|
|
156
157
|
| "completed"
|
|
157
158
|
| "closed";
|
|
159
|
+
statusFilter?: "open" | "closed";
|
|
158
160
|
viewerActorId: string;
|
|
159
161
|
},
|
|
160
162
|
{
|
|
@@ -210,11 +212,37 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
210
212
|
actorId: string;
|
|
211
213
|
body: string;
|
|
212
214
|
entryId: string;
|
|
215
|
+
includeCallbackContext?: boolean;
|
|
213
216
|
maxCommentLength: number;
|
|
214
217
|
maxDepth: number;
|
|
215
218
|
parentCommentId?: string;
|
|
216
219
|
},
|
|
217
|
-
string
|
|
220
|
+
| { id: string }
|
|
221
|
+
| {
|
|
222
|
+
comment: {
|
|
223
|
+
actorId: string;
|
|
224
|
+
body: string;
|
|
225
|
+
depth: number;
|
|
226
|
+
entryId: string;
|
|
227
|
+
id: string;
|
|
228
|
+
parentCommentId?: string;
|
|
229
|
+
};
|
|
230
|
+
entry: {
|
|
231
|
+
actorId: string;
|
|
232
|
+
id: string;
|
|
233
|
+
kind: "feedback" | "feature_request" | "bug_report";
|
|
234
|
+
status:
|
|
235
|
+
| "open"
|
|
236
|
+
| "under_review"
|
|
237
|
+
| "planned"
|
|
238
|
+
| "in_progress"
|
|
239
|
+
| "completed"
|
|
240
|
+
| "closed";
|
|
241
|
+
title: string;
|
|
242
|
+
};
|
|
243
|
+
id: string;
|
|
244
|
+
parentComment?: { actorId: string; id: string };
|
|
245
|
+
},
|
|
218
246
|
Name
|
|
219
247
|
>;
|
|
220
248
|
list: FunctionReference<
|
|
@@ -239,9 +267,8 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
239
267
|
isDone: boolean;
|
|
240
268
|
page: Array<{
|
|
241
269
|
actorId: string;
|
|
242
|
-
body: string
|
|
270
|
+
body: string;
|
|
243
271
|
creationTime: number;
|
|
244
|
-
deletedAt?: number;
|
|
245
272
|
depth: number;
|
|
246
273
|
entryId: string;
|
|
247
274
|
id: string;
|
|
@@ -256,6 +283,41 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
256
283
|
},
|
|
257
284
|
Name
|
|
258
285
|
>;
|
|
286
|
+
listByActor: FunctionReference<
|
|
287
|
+
"query",
|
|
288
|
+
"internal",
|
|
289
|
+
{
|
|
290
|
+
actorId: string;
|
|
291
|
+
paginationOpts: {
|
|
292
|
+
cursor: string | null;
|
|
293
|
+
endCursor?: string | null;
|
|
294
|
+
id?: number;
|
|
295
|
+
maximumBytesRead?: number;
|
|
296
|
+
maximumRowsRead?: number;
|
|
297
|
+
numItems: number;
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
continueCursor: string;
|
|
302
|
+
isDone: boolean;
|
|
303
|
+
page: Array<{
|
|
304
|
+
actorId: string;
|
|
305
|
+
body: string;
|
|
306
|
+
creationTime: number;
|
|
307
|
+
depth: number;
|
|
308
|
+
entryId: string;
|
|
309
|
+
entryTitle: string;
|
|
310
|
+
id: string;
|
|
311
|
+
likeCount: number;
|
|
312
|
+
parentCommentId?: string;
|
|
313
|
+
replyCount: number;
|
|
314
|
+
updatedAt?: number;
|
|
315
|
+
}>;
|
|
316
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
317
|
+
splitCursor?: string | null;
|
|
318
|
+
},
|
|
319
|
+
Name
|
|
320
|
+
>;
|
|
259
321
|
remove: FunctionReference<
|
|
260
322
|
"mutation",
|
|
261
323
|
"internal",
|
|
@@ -270,8 +332,34 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
270
332
|
setLike: FunctionReference<
|
|
271
333
|
"mutation",
|
|
272
334
|
"internal",
|
|
273
|
-
{
|
|
274
|
-
|
|
335
|
+
{
|
|
336
|
+
actorId: string;
|
|
337
|
+
commentId: string;
|
|
338
|
+
desiredState: boolean;
|
|
339
|
+
includeCallbackContext?: boolean;
|
|
340
|
+
},
|
|
341
|
+
| { active: boolean; likeCount: number }
|
|
342
|
+
| {
|
|
343
|
+
active: boolean;
|
|
344
|
+
changed: false;
|
|
345
|
+
count: number;
|
|
346
|
+
previousCount: number;
|
|
347
|
+
transition: null;
|
|
348
|
+
}
|
|
349
|
+
| {
|
|
350
|
+
active: boolean;
|
|
351
|
+
changed: true;
|
|
352
|
+
comment: {
|
|
353
|
+
actorId: string;
|
|
354
|
+
body: string;
|
|
355
|
+
entryId: string;
|
|
356
|
+
id: string;
|
|
357
|
+
parentCommentId?: string;
|
|
358
|
+
};
|
|
359
|
+
count: number;
|
|
360
|
+
previousCount: number;
|
|
361
|
+
transition: "added" | "removed";
|
|
362
|
+
},
|
|
275
363
|
Name
|
|
276
364
|
>;
|
|
277
365
|
update: FunctionReference<
|
|
@@ -303,6 +391,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
303
391
|
| "completed"
|
|
304
392
|
| "closed";
|
|
305
393
|
enabledKinds: Array<"feedback" | "feature_request" | "bug_report">;
|
|
394
|
+
includeCallbackContext?: boolean;
|
|
306
395
|
kind: "feedback" | "feature_request" | "bug_report";
|
|
307
396
|
maxBodyLength: number;
|
|
308
397
|
maxTitleLength: number;
|
|
@@ -312,7 +401,30 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
312
401
|
};
|
|
313
402
|
title: string;
|
|
314
403
|
},
|
|
315
|
-
string
|
|
404
|
+
| { id: string }
|
|
405
|
+
| {
|
|
406
|
+
entry: {
|
|
407
|
+
actorId: string;
|
|
408
|
+
body: string;
|
|
409
|
+
commentCount: number;
|
|
410
|
+
id: string;
|
|
411
|
+
kind: "feedback" | "feature_request" | "bug_report";
|
|
412
|
+
metadata?: {
|
|
413
|
+
additional?: Record<string, string | number | boolean>;
|
|
414
|
+
standard?: Record<string, string | number | boolean>;
|
|
415
|
+
};
|
|
416
|
+
status:
|
|
417
|
+
| "open"
|
|
418
|
+
| "under_review"
|
|
419
|
+
| "planned"
|
|
420
|
+
| "in_progress"
|
|
421
|
+
| "completed"
|
|
422
|
+
| "closed";
|
|
423
|
+
title: string;
|
|
424
|
+
upvoteCount: number;
|
|
425
|
+
};
|
|
426
|
+
id: string;
|
|
427
|
+
},
|
|
316
428
|
Name
|
|
317
429
|
>;
|
|
318
430
|
get: FunctionReference<
|
|
@@ -401,6 +513,73 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
401
513
|
},
|
|
402
514
|
Name
|
|
403
515
|
>;
|
|
516
|
+
listByActor: FunctionReference<
|
|
517
|
+
"query",
|
|
518
|
+
"internal",
|
|
519
|
+
{
|
|
520
|
+
actorId: string;
|
|
521
|
+
includeAdminContext?: boolean;
|
|
522
|
+
paginationOpts: {
|
|
523
|
+
cursor: string | null;
|
|
524
|
+
endCursor?: string | null;
|
|
525
|
+
id?: number;
|
|
526
|
+
maximumBytesRead?: number;
|
|
527
|
+
maximumRowsRead?: number;
|
|
528
|
+
numItems: number;
|
|
529
|
+
};
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
continueCursor: string;
|
|
533
|
+
isDone: boolean;
|
|
534
|
+
page: Array<{
|
|
535
|
+
actorId: string;
|
|
536
|
+
body: string;
|
|
537
|
+
commentCount: number;
|
|
538
|
+
creationTime: number;
|
|
539
|
+
id: string;
|
|
540
|
+
kind: "feedback" | "feature_request" | "bug_report";
|
|
541
|
+
metadata?: {
|
|
542
|
+
additional?: Record<string, string | number | boolean>;
|
|
543
|
+
standard?: Record<string, string | number | boolean>;
|
|
544
|
+
};
|
|
545
|
+
priority?: "low" | "medium" | "high";
|
|
546
|
+
roadmap?: {
|
|
547
|
+
createdAt: number;
|
|
548
|
+
creationTime: number;
|
|
549
|
+
description?: string;
|
|
550
|
+
feedbackCount: number;
|
|
551
|
+
id: string;
|
|
552
|
+
position: number;
|
|
553
|
+
status: "planned" | "in_progress" | "shipped";
|
|
554
|
+
title: string;
|
|
555
|
+
updatedAt: number;
|
|
556
|
+
};
|
|
557
|
+
status:
|
|
558
|
+
| "open"
|
|
559
|
+
| "under_review"
|
|
560
|
+
| "planned"
|
|
561
|
+
| "in_progress"
|
|
562
|
+
| "completed"
|
|
563
|
+
| "closed";
|
|
564
|
+
title: string;
|
|
565
|
+
updatedAt?: number;
|
|
566
|
+
upvoteCount: number;
|
|
567
|
+
}>;
|
|
568
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
569
|
+
splitCursor?: string | null;
|
|
570
|
+
},
|
|
571
|
+
Name
|
|
572
|
+
>;
|
|
573
|
+
remove: FunctionReference<
|
|
574
|
+
"mutation",
|
|
575
|
+
"internal",
|
|
576
|
+
{
|
|
577
|
+
actor: { id: string; isAdmin?: boolean; isModerator?: boolean };
|
|
578
|
+
entryId: string;
|
|
579
|
+
},
|
|
580
|
+
null,
|
|
581
|
+
Name
|
|
582
|
+
>;
|
|
404
583
|
search: FunctionReference<
|
|
405
584
|
"query",
|
|
406
585
|
"internal",
|
|
@@ -475,8 +654,40 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
475
654
|
setUpvote: FunctionReference<
|
|
476
655
|
"mutation",
|
|
477
656
|
"internal",
|
|
478
|
-
{
|
|
479
|
-
|
|
657
|
+
{
|
|
658
|
+
actorId: string;
|
|
659
|
+
desiredState: boolean;
|
|
660
|
+
entryId: string;
|
|
661
|
+
includeCallbackContext?: boolean;
|
|
662
|
+
},
|
|
663
|
+
| { active: boolean; upvoteCount: number }
|
|
664
|
+
| {
|
|
665
|
+
active: boolean;
|
|
666
|
+
changed: false;
|
|
667
|
+
count: number;
|
|
668
|
+
previousCount: number;
|
|
669
|
+
transition: null;
|
|
670
|
+
}
|
|
671
|
+
| {
|
|
672
|
+
active: boolean;
|
|
673
|
+
changed: true;
|
|
674
|
+
count: number;
|
|
675
|
+
entry: {
|
|
676
|
+
actorId: string;
|
|
677
|
+
id: string;
|
|
678
|
+
kind: "feedback" | "feature_request" | "bug_report";
|
|
679
|
+
status:
|
|
680
|
+
| "open"
|
|
681
|
+
| "under_review"
|
|
682
|
+
| "planned"
|
|
683
|
+
| "in_progress"
|
|
684
|
+
| "completed"
|
|
685
|
+
| "closed";
|
|
686
|
+
title: string;
|
|
687
|
+
};
|
|
688
|
+
previousCount: number;
|
|
689
|
+
transition: "added" | "removed";
|
|
690
|
+
},
|
|
480
691
|
Name
|
|
481
692
|
>;
|
|
482
693
|
similar: FunctionReference<
|
|
@@ -558,6 +769,60 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
558
769
|
Name
|
|
559
770
|
>;
|
|
560
771
|
};
|
|
772
|
+
reactions: {
|
|
773
|
+
listByActor: FunctionReference<
|
|
774
|
+
"query",
|
|
775
|
+
"internal",
|
|
776
|
+
{
|
|
777
|
+
actorId: string;
|
|
778
|
+
paginationOpts: {
|
|
779
|
+
cursor: string | null;
|
|
780
|
+
endCursor?: string | null;
|
|
781
|
+
id?: number;
|
|
782
|
+
maximumBytesRead?: number;
|
|
783
|
+
maximumRowsRead?: number;
|
|
784
|
+
numItems: number;
|
|
785
|
+
};
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
continueCursor: string;
|
|
789
|
+
isDone: boolean;
|
|
790
|
+
page: Array<
|
|
791
|
+
| {
|
|
792
|
+
creationTime: number;
|
|
793
|
+
entry: {
|
|
794
|
+
id: string;
|
|
795
|
+
kind: "feedback" | "feature_request" | "bug_report";
|
|
796
|
+
status:
|
|
797
|
+
| "open"
|
|
798
|
+
| "under_review"
|
|
799
|
+
| "planned"
|
|
800
|
+
| "in_progress"
|
|
801
|
+
| "completed"
|
|
802
|
+
| "closed";
|
|
803
|
+
title: string;
|
|
804
|
+
};
|
|
805
|
+
id: string;
|
|
806
|
+
type: "entry_upvote";
|
|
807
|
+
}
|
|
808
|
+
| {
|
|
809
|
+
comment: {
|
|
810
|
+
body: string;
|
|
811
|
+
entryId: string;
|
|
812
|
+
entryTitle: string;
|
|
813
|
+
id: string;
|
|
814
|
+
};
|
|
815
|
+
creationTime: number;
|
|
816
|
+
id: string;
|
|
817
|
+
type: "comment_like";
|
|
818
|
+
}
|
|
819
|
+
>;
|
|
820
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
821
|
+
splitCursor?: string | null;
|
|
822
|
+
},
|
|
823
|
+
Name
|
|
824
|
+
>;
|
|
825
|
+
};
|
|
561
826
|
roadmap: {
|
|
562
827
|
attachFeedback: FunctionReference<
|
|
563
828
|
"mutation",
|
|
@@ -605,6 +870,23 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
605
870
|
null,
|
|
606
871
|
Name
|
|
607
872
|
>;
|
|
873
|
+
get: FunctionReference<
|
|
874
|
+
"query",
|
|
875
|
+
"internal",
|
|
876
|
+
{ roadmapId: string },
|
|
877
|
+
{
|
|
878
|
+
createdAt: number;
|
|
879
|
+
creationTime: number;
|
|
880
|
+
description?: string;
|
|
881
|
+
feedbackCount: number;
|
|
882
|
+
id: string;
|
|
883
|
+
position: number;
|
|
884
|
+
status: "planned" | "in_progress" | "shipped";
|
|
885
|
+
title: string;
|
|
886
|
+
updatedAt: number;
|
|
887
|
+
} | null,
|
|
888
|
+
Name
|
|
889
|
+
>;
|
|
608
890
|
list: FunctionReference<
|
|
609
891
|
"query",
|
|
610
892
|
"internal",
|
package/src/component/admin.ts
CHANGED
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
adminEntryValidator,
|
|
15
15
|
entryKindValidator,
|
|
16
16
|
entryPriorityValidator,
|
|
17
|
+
entryStatusFilterValidator,
|
|
17
18
|
entryStatusValidator,
|
|
18
19
|
type EntryKind,
|
|
19
20
|
type EntryPriority,
|
|
20
21
|
type EntryStatus,
|
|
22
|
+
type EntryStatusFilter,
|
|
21
23
|
} from "./model.js";
|
|
22
24
|
import schema from "./schema.js";
|
|
23
25
|
|
|
@@ -87,11 +89,14 @@ function matchesFilters(
|
|
|
87
89
|
entry: Doc<"entries">,
|
|
88
90
|
kinds: EntryKind[] | undefined,
|
|
89
91
|
status: EntryStatus | undefined,
|
|
92
|
+
statusFilter: EntryStatusFilter | undefined,
|
|
90
93
|
priority: EntryPriority | undefined,
|
|
91
94
|
): boolean {
|
|
92
95
|
return (
|
|
96
|
+
entry.deletingAt === undefined &&
|
|
93
97
|
(kinds === undefined || kinds.includes(entry.kind)) &&
|
|
94
98
|
(status === undefined || entry.status === status) &&
|
|
99
|
+
(statusFilter === undefined || entry.statusFilter === statusFilter) &&
|
|
95
100
|
(priority === undefined || entry.priority === priority)
|
|
96
101
|
);
|
|
97
102
|
}
|
|
@@ -110,7 +115,7 @@ export const getEntry = query({
|
|
|
110
115
|
returns: v.union(adminEntryValidator, v.null()),
|
|
111
116
|
handler: async (ctx, args) => {
|
|
112
117
|
const entry = await ctx.db.get("entries", args.entryId);
|
|
113
|
-
return entry === null
|
|
118
|
+
return entry === null || entry.deletingAt !== undefined
|
|
114
119
|
? null
|
|
115
120
|
: await serializeAdminEntry(ctx, entry, args.viewerActorId);
|
|
116
121
|
},
|
|
@@ -120,6 +125,7 @@ export const listEntries = query({
|
|
|
120
125
|
args: {
|
|
121
126
|
kinds: v.optional(v.array(entryKindValidator)),
|
|
122
127
|
status: v.optional(entryStatusValidator),
|
|
128
|
+
statusFilter: v.optional(entryStatusFilterValidator),
|
|
123
129
|
priority: v.optional(entryPriorityValidator),
|
|
124
130
|
paginationOpts: paginationOptsValidator,
|
|
125
131
|
viewerActorId: v.string(),
|
|
@@ -131,27 +137,50 @@ export const listEntries = query({
|
|
|
131
137
|
if (kinds?.length === 0) {
|
|
132
138
|
throw new ConvexError("`kinds` must contain at least one kind.");
|
|
133
139
|
}
|
|
140
|
+
if (args.status !== undefined && args.statusFilter !== undefined) {
|
|
141
|
+
throw new ConvexError(
|
|
142
|
+
"`status` and `statusFilter` cannot be used together.",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
134
145
|
|
|
135
146
|
const entriesStream =
|
|
136
|
-
args.
|
|
137
|
-
?
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
args.statusFilter !== undefined
|
|
148
|
+
? kinds?.length === 1
|
|
149
|
+
? stream(ctx.db, schema)
|
|
150
|
+
.query("entries")
|
|
151
|
+
.withIndex("by_kind_status_filter", (q) =>
|
|
152
|
+
q.eq("kind", kinds[0]!).eq("statusFilter", args.statusFilter),
|
|
153
|
+
)
|
|
154
|
+
: stream(ctx.db, schema)
|
|
155
|
+
.query("entries")
|
|
156
|
+
.withIndex("by_status_filter", (q) =>
|
|
157
|
+
q.eq("statusFilter", args.statusFilter),
|
|
158
|
+
)
|
|
159
|
+
: args.priority !== undefined
|
|
141
160
|
? stream(ctx.db, schema)
|
|
142
161
|
.query("entries")
|
|
143
|
-
.withIndex("
|
|
144
|
-
:
|
|
162
|
+
.withIndex("by_priority", (q) => q.eq("priority", args.priority))
|
|
163
|
+
: args.status !== undefined
|
|
145
164
|
? stream(ctx.db, schema)
|
|
146
165
|
.query("entries")
|
|
147
|
-
.withIndex("
|
|
148
|
-
:
|
|
166
|
+
.withIndex("by_status", (q) => q.eq("status", args.status!))
|
|
167
|
+
: kinds?.length === 1
|
|
168
|
+
? stream(ctx.db, schema)
|
|
169
|
+
.query("entries")
|
|
170
|
+
.withIndex("by_kind", (q) => q.eq("kind", kinds[0]!))
|
|
171
|
+
: stream(ctx.db, schema).query("entries");
|
|
149
172
|
|
|
150
173
|
const result = await entriesStream
|
|
151
174
|
.order("desc")
|
|
152
175
|
.filterWith((entry) =>
|
|
153
176
|
Promise.resolve(
|
|
154
|
-
matchesFilters(
|
|
177
|
+
matchesFilters(
|
|
178
|
+
entry,
|
|
179
|
+
kinds,
|
|
180
|
+
args.status,
|
|
181
|
+
args.statusFilter,
|
|
182
|
+
args.priority,
|
|
183
|
+
),
|
|
155
184
|
),
|
|
156
185
|
)
|
|
157
186
|
.paginate(args.paginationOpts);
|
|
@@ -172,12 +201,18 @@ export const searchEntries = query({
|
|
|
172
201
|
searchQuery: v.string(),
|
|
173
202
|
kinds: v.optional(v.array(entryKindValidator)),
|
|
174
203
|
status: v.optional(entryStatusValidator),
|
|
204
|
+
statusFilter: v.optional(entryStatusFilterValidator),
|
|
175
205
|
priority: v.optional(entryPriorityValidator),
|
|
176
206
|
paginationOpts: paginationOptsValidator,
|
|
177
207
|
viewerActorId: v.string(),
|
|
178
208
|
},
|
|
179
209
|
returns: paginationResultValidator(adminEntryValidator),
|
|
180
210
|
handler: async (ctx, args) => {
|
|
211
|
+
if (args.status !== undefined && args.statusFilter !== undefined) {
|
|
212
|
+
throw new ConvexError(
|
|
213
|
+
"`status` and `statusFilter` cannot be used together.",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
181
216
|
const searchQuery = args.searchQuery.trim();
|
|
182
217
|
if (searchQuery.length === 0) {
|
|
183
218
|
return { page: [], isDone: true, continueCursor: "" };
|
|
@@ -197,37 +232,60 @@ export const searchEntries = query({
|
|
|
197
232
|
const withKind =
|
|
198
233
|
kinds?.length === 1 ? searched.eq("kind", kinds[0]!) : searched;
|
|
199
234
|
const withStatus =
|
|
200
|
-
args.
|
|
201
|
-
?
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
235
|
+
args.statusFilter === undefined
|
|
236
|
+
? args.status === undefined
|
|
237
|
+
? withKind
|
|
238
|
+
: withKind.eq("status", args.status)
|
|
239
|
+
: withKind.eq("statusFilter", args.statusFilter);
|
|
240
|
+
const withPriority =
|
|
241
|
+
args.priority === undefined
|
|
242
|
+
? withStatus
|
|
243
|
+
: withStatus.eq("priority", args.priority);
|
|
244
|
+
return withPriority.eq("deletingAt", undefined);
|
|
206
245
|
}),
|
|
207
246
|
args.paginationOpts,
|
|
208
247
|
)
|
|
209
248
|
: await (
|
|
210
|
-
args.
|
|
211
|
-
?
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
249
|
+
args.statusFilter !== undefined
|
|
250
|
+
? kinds?.length === 1
|
|
251
|
+
? stream(ctx.db, schema)
|
|
252
|
+
.query("entries")
|
|
253
|
+
.withIndex("by_kind_status_filter", (q) =>
|
|
254
|
+
q
|
|
255
|
+
.eq("kind", kinds[0]!)
|
|
256
|
+
.eq("statusFilter", args.statusFilter),
|
|
257
|
+
)
|
|
258
|
+
: stream(ctx.db, schema)
|
|
259
|
+
.query("entries")
|
|
260
|
+
.withIndex("by_status_filter", (q) =>
|
|
261
|
+
q.eq("statusFilter", args.statusFilter),
|
|
262
|
+
)
|
|
263
|
+
: args.priority !== undefined
|
|
217
264
|
? stream(ctx.db, schema)
|
|
218
265
|
.query("entries")
|
|
219
|
-
.withIndex("
|
|
220
|
-
|
|
266
|
+
.withIndex("by_priority", (q) =>
|
|
267
|
+
q.eq("priority", args.priority),
|
|
268
|
+
)
|
|
269
|
+
: args.status !== undefined
|
|
221
270
|
? stream(ctx.db, schema)
|
|
222
271
|
.query("entries")
|
|
223
|
-
.withIndex("
|
|
224
|
-
:
|
|
272
|
+
.withIndex("by_status", (q) => q.eq("status", args.status!))
|
|
273
|
+
: kinds?.length === 1
|
|
274
|
+
? stream(ctx.db, schema)
|
|
275
|
+
.query("entries")
|
|
276
|
+
.withIndex("by_kind", (q) => q.eq("kind", kinds[0]!))
|
|
277
|
+
: stream(ctx.db, schema).query("entries")
|
|
225
278
|
)
|
|
226
279
|
.order("desc")
|
|
227
280
|
.filterWith((entry) =>
|
|
228
281
|
Promise.resolve(
|
|
229
|
-
matchesFilters(
|
|
230
|
-
|
|
282
|
+
matchesFilters(
|
|
283
|
+
entry,
|
|
284
|
+
kinds,
|
|
285
|
+
args.status,
|
|
286
|
+
args.statusFilter,
|
|
287
|
+
args.priority,
|
|
288
|
+
) && matchesSearch(entry, searchQuery),
|
|
231
289
|
),
|
|
232
290
|
)
|
|
233
291
|
.paginate(args.paginationOpts);
|