@atproto/bsync 0.0.3 → 0.0.5
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/CHANGELOG.md +15 -0
- package/buf.gen.yaml +2 -2
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/db/migrations/20240717T224303472Z-notif-ops.d.ts +4 -0
- package/dist/db/migrations/20240717T224303472Z-notif-ops.d.ts.map +1 -0
- package/dist/db/migrations/20240717T224303472Z-notif-ops.js +26 -0
- package/dist/db/migrations/20240717T224303472Z-notif-ops.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/index.d.ts +3 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/notif_item.d.ts +12 -0
- package/dist/db/schema/notif_item.d.ts.map +1 -0
- package/dist/db/schema/notif_item.js +5 -0
- package/dist/db/schema/notif_item.js.map +1 -0
- package/dist/db/schema/notif_op.d.ts +14 -0
- package/dist/db/schema/notif_op.d.ts.map +1 -0
- package/dist/db/schema/notif_op.js +6 -0
- package/dist/db/schema/notif_op.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +4 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +31 -8
- package/dist/logger.js.map +1 -1
- package/dist/proto/bsync_connect.d.ts +19 -1
- package/dist/proto/bsync_connect.d.ts.map +1 -1
- package/dist/proto/bsync_connect.js +19 -1
- package/dist/proto/bsync_connect.js.map +1 -1
- package/dist/proto/bsync_pb.d.ts +105 -0
- package/dist/proto/bsync_pb.d.ts.map +1 -1
- package/dist/proto/bsync_pb.js +325 -2
- package/dist/proto/bsync_pb.js.map +1 -1
- package/dist/routes/add-mute-operation.d.ts.map +1 -1
- package/dist/routes/add-mute-operation.js +7 -26
- package/dist/routes/add-mute-operation.js.map +1 -1
- package/dist/routes/add-notif-operation.d.ts +6 -0
- package/dist/routes/add-notif-operation.d.ts.map +1 -0
- package/dist/routes/add-notif-operation.js +63 -0
- package/dist/routes/add-notif-operation.js.map +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +4 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/scan-mute-operations.d.ts.map +1 -1
- package/dist/routes/scan-mute-operations.js +3 -26
- package/dist/routes/scan-mute-operations.js.map +1 -1
- package/dist/routes/scan-notif-operations.d.ts +6 -0
- package/dist/routes/scan-notif-operations.d.ts.map +1 -0
- package/dist/routes/scan-notif-operations.js +56 -0
- package/dist/routes/scan-notif-operations.js.map +1 -0
- package/dist/routes/util.d.ts +6 -0
- package/dist/routes/util.d.ts.map +1 -0
- package/dist/routes/util.js +54 -0
- package/dist/routes/util.js.map +1 -0
- package/package.json +3 -2
- package/proto/bsync.proto +29 -0
- package/src/context.ts +2 -0
- package/src/db/migrations/20240717T224303472Z-notif-ops.ts +24 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/index.ts +6 -1
- package/src/db/schema/notif_item.ts +13 -0
- package/src/db/schema/notif_op.ts +16 -0
- package/src/index.ts +8 -2
- package/src/logger.ts +11 -7
- package/src/proto/bsync_connect.ts +23 -1
- package/src/proto/bsync_pb.ts +318 -1
- package/src/routes/add-mute-operation.ts +7 -29
- package/src/routes/add-notif-operation.ts +80 -0
- package/src/routes/index.ts +4 -0
- package/src/routes/scan-mute-operations.ts +2 -25
- package/src/routes/scan-notif-operations.ts +64 -0
- package/src/routes/util.ts +51 -0
- package/tests/mutes.test.ts +2 -0
- package/tests/notifications.test.ts +209 -0
package/src/proto/bsync_pb.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension
|
|
1
|
+
// @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension="
|
|
2
2
|
// @generated from file bsync.proto (package bsync, syntax proto3)
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
// @ts-nocheck
|
|
@@ -372,6 +372,323 @@ export class ScanMuteOperationsResponse extends Message<ScanMuteOperationsRespon
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
/**
|
|
376
|
+
* @generated from message bsync.NotifOperation
|
|
377
|
+
*/
|
|
378
|
+
export class NotifOperation extends Message<NotifOperation> {
|
|
379
|
+
/**
|
|
380
|
+
* @generated from field: string id = 1;
|
|
381
|
+
*/
|
|
382
|
+
id = ''
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @generated from field: string actor_did = 2;
|
|
386
|
+
*/
|
|
387
|
+
actorDid = ''
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @generated from field: optional bool priority = 3;
|
|
391
|
+
*/
|
|
392
|
+
priority?: boolean
|
|
393
|
+
|
|
394
|
+
constructor(data?: PartialMessage<NotifOperation>) {
|
|
395
|
+
super()
|
|
396
|
+
proto3.util.initPartial(data, this)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
static readonly runtime: typeof proto3 = proto3
|
|
400
|
+
static readonly typeName = 'bsync.NotifOperation'
|
|
401
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
402
|
+
{ no: 1, name: 'id', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
403
|
+
{ no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
404
|
+
{
|
|
405
|
+
no: 3,
|
|
406
|
+
name: 'priority',
|
|
407
|
+
kind: 'scalar',
|
|
408
|
+
T: 8 /* ScalarType.BOOL */,
|
|
409
|
+
opt: true,
|
|
410
|
+
},
|
|
411
|
+
])
|
|
412
|
+
|
|
413
|
+
static fromBinary(
|
|
414
|
+
bytes: Uint8Array,
|
|
415
|
+
options?: Partial<BinaryReadOptions>,
|
|
416
|
+
): NotifOperation {
|
|
417
|
+
return new NotifOperation().fromBinary(bytes, options)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
static fromJson(
|
|
421
|
+
jsonValue: JsonValue,
|
|
422
|
+
options?: Partial<JsonReadOptions>,
|
|
423
|
+
): NotifOperation {
|
|
424
|
+
return new NotifOperation().fromJson(jsonValue, options)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static fromJsonString(
|
|
428
|
+
jsonString: string,
|
|
429
|
+
options?: Partial<JsonReadOptions>,
|
|
430
|
+
): NotifOperation {
|
|
431
|
+
return new NotifOperation().fromJsonString(jsonString, options)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static equals(
|
|
435
|
+
a: NotifOperation | PlainMessage<NotifOperation> | undefined,
|
|
436
|
+
b: NotifOperation | PlainMessage<NotifOperation> | undefined,
|
|
437
|
+
): boolean {
|
|
438
|
+
return proto3.util.equals(NotifOperation, a, b)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @generated from message bsync.AddNotifOperationRequest
|
|
444
|
+
*/
|
|
445
|
+
export class AddNotifOperationRequest extends Message<AddNotifOperationRequest> {
|
|
446
|
+
/**
|
|
447
|
+
* @generated from field: string actor_did = 1;
|
|
448
|
+
*/
|
|
449
|
+
actorDid = ''
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* @generated from field: optional bool priority = 2;
|
|
453
|
+
*/
|
|
454
|
+
priority?: boolean
|
|
455
|
+
|
|
456
|
+
constructor(data?: PartialMessage<AddNotifOperationRequest>) {
|
|
457
|
+
super()
|
|
458
|
+
proto3.util.initPartial(data, this)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static readonly runtime: typeof proto3 = proto3
|
|
462
|
+
static readonly typeName = 'bsync.AddNotifOperationRequest'
|
|
463
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
464
|
+
{ no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
465
|
+
{
|
|
466
|
+
no: 2,
|
|
467
|
+
name: 'priority',
|
|
468
|
+
kind: 'scalar',
|
|
469
|
+
T: 8 /* ScalarType.BOOL */,
|
|
470
|
+
opt: true,
|
|
471
|
+
},
|
|
472
|
+
])
|
|
473
|
+
|
|
474
|
+
static fromBinary(
|
|
475
|
+
bytes: Uint8Array,
|
|
476
|
+
options?: Partial<BinaryReadOptions>,
|
|
477
|
+
): AddNotifOperationRequest {
|
|
478
|
+
return new AddNotifOperationRequest().fromBinary(bytes, options)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
static fromJson(
|
|
482
|
+
jsonValue: JsonValue,
|
|
483
|
+
options?: Partial<JsonReadOptions>,
|
|
484
|
+
): AddNotifOperationRequest {
|
|
485
|
+
return new AddNotifOperationRequest().fromJson(jsonValue, options)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
static fromJsonString(
|
|
489
|
+
jsonString: string,
|
|
490
|
+
options?: Partial<JsonReadOptions>,
|
|
491
|
+
): AddNotifOperationRequest {
|
|
492
|
+
return new AddNotifOperationRequest().fromJsonString(jsonString, options)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
static equals(
|
|
496
|
+
a:
|
|
497
|
+
| AddNotifOperationRequest
|
|
498
|
+
| PlainMessage<AddNotifOperationRequest>
|
|
499
|
+
| undefined,
|
|
500
|
+
b:
|
|
501
|
+
| AddNotifOperationRequest
|
|
502
|
+
| PlainMessage<AddNotifOperationRequest>
|
|
503
|
+
| undefined,
|
|
504
|
+
): boolean {
|
|
505
|
+
return proto3.util.equals(AddNotifOperationRequest, a, b)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @generated from message bsync.AddNotifOperationResponse
|
|
511
|
+
*/
|
|
512
|
+
export class AddNotifOperationResponse extends Message<AddNotifOperationResponse> {
|
|
513
|
+
/**
|
|
514
|
+
* @generated from field: bsync.NotifOperation operation = 1;
|
|
515
|
+
*/
|
|
516
|
+
operation?: NotifOperation
|
|
517
|
+
|
|
518
|
+
constructor(data?: PartialMessage<AddNotifOperationResponse>) {
|
|
519
|
+
super()
|
|
520
|
+
proto3.util.initPartial(data, this)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
static readonly runtime: typeof proto3 = proto3
|
|
524
|
+
static readonly typeName = 'bsync.AddNotifOperationResponse'
|
|
525
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
526
|
+
{ no: 1, name: 'operation', kind: 'message', T: NotifOperation },
|
|
527
|
+
])
|
|
528
|
+
|
|
529
|
+
static fromBinary(
|
|
530
|
+
bytes: Uint8Array,
|
|
531
|
+
options?: Partial<BinaryReadOptions>,
|
|
532
|
+
): AddNotifOperationResponse {
|
|
533
|
+
return new AddNotifOperationResponse().fromBinary(bytes, options)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
static fromJson(
|
|
537
|
+
jsonValue: JsonValue,
|
|
538
|
+
options?: Partial<JsonReadOptions>,
|
|
539
|
+
): AddNotifOperationResponse {
|
|
540
|
+
return new AddNotifOperationResponse().fromJson(jsonValue, options)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
static fromJsonString(
|
|
544
|
+
jsonString: string,
|
|
545
|
+
options?: Partial<JsonReadOptions>,
|
|
546
|
+
): AddNotifOperationResponse {
|
|
547
|
+
return new AddNotifOperationResponse().fromJsonString(jsonString, options)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static equals(
|
|
551
|
+
a:
|
|
552
|
+
| AddNotifOperationResponse
|
|
553
|
+
| PlainMessage<AddNotifOperationResponse>
|
|
554
|
+
| undefined,
|
|
555
|
+
b:
|
|
556
|
+
| AddNotifOperationResponse
|
|
557
|
+
| PlainMessage<AddNotifOperationResponse>
|
|
558
|
+
| undefined,
|
|
559
|
+
): boolean {
|
|
560
|
+
return proto3.util.equals(AddNotifOperationResponse, a, b)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* @generated from message bsync.ScanNotifOperationsRequest
|
|
566
|
+
*/
|
|
567
|
+
export class ScanNotifOperationsRequest extends Message<ScanNotifOperationsRequest> {
|
|
568
|
+
/**
|
|
569
|
+
* @generated from field: string cursor = 1;
|
|
570
|
+
*/
|
|
571
|
+
cursor = ''
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* @generated from field: int32 limit = 2;
|
|
575
|
+
*/
|
|
576
|
+
limit = 0
|
|
577
|
+
|
|
578
|
+
constructor(data?: PartialMessage<ScanNotifOperationsRequest>) {
|
|
579
|
+
super()
|
|
580
|
+
proto3.util.initPartial(data, this)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
static readonly runtime: typeof proto3 = proto3
|
|
584
|
+
static readonly typeName = 'bsync.ScanNotifOperationsRequest'
|
|
585
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
586
|
+
{ no: 1, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
587
|
+
{ no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ },
|
|
588
|
+
])
|
|
589
|
+
|
|
590
|
+
static fromBinary(
|
|
591
|
+
bytes: Uint8Array,
|
|
592
|
+
options?: Partial<BinaryReadOptions>,
|
|
593
|
+
): ScanNotifOperationsRequest {
|
|
594
|
+
return new ScanNotifOperationsRequest().fromBinary(bytes, options)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
static fromJson(
|
|
598
|
+
jsonValue: JsonValue,
|
|
599
|
+
options?: Partial<JsonReadOptions>,
|
|
600
|
+
): ScanNotifOperationsRequest {
|
|
601
|
+
return new ScanNotifOperationsRequest().fromJson(jsonValue, options)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
static fromJsonString(
|
|
605
|
+
jsonString: string,
|
|
606
|
+
options?: Partial<JsonReadOptions>,
|
|
607
|
+
): ScanNotifOperationsRequest {
|
|
608
|
+
return new ScanNotifOperationsRequest().fromJsonString(jsonString, options)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
static equals(
|
|
612
|
+
a:
|
|
613
|
+
| ScanNotifOperationsRequest
|
|
614
|
+
| PlainMessage<ScanNotifOperationsRequest>
|
|
615
|
+
| undefined,
|
|
616
|
+
b:
|
|
617
|
+
| ScanNotifOperationsRequest
|
|
618
|
+
| PlainMessage<ScanNotifOperationsRequest>
|
|
619
|
+
| undefined,
|
|
620
|
+
): boolean {
|
|
621
|
+
return proto3.util.equals(ScanNotifOperationsRequest, a, b)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* @generated from message bsync.ScanNotifOperationsResponse
|
|
627
|
+
*/
|
|
628
|
+
export class ScanNotifOperationsResponse extends Message<ScanNotifOperationsResponse> {
|
|
629
|
+
/**
|
|
630
|
+
* @generated from field: repeated bsync.NotifOperation operations = 1;
|
|
631
|
+
*/
|
|
632
|
+
operations: NotifOperation[] = []
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* @generated from field: string cursor = 2;
|
|
636
|
+
*/
|
|
637
|
+
cursor = ''
|
|
638
|
+
|
|
639
|
+
constructor(data?: PartialMessage<ScanNotifOperationsResponse>) {
|
|
640
|
+
super()
|
|
641
|
+
proto3.util.initPartial(data, this)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
static readonly runtime: typeof proto3 = proto3
|
|
645
|
+
static readonly typeName = 'bsync.ScanNotifOperationsResponse'
|
|
646
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
647
|
+
{
|
|
648
|
+
no: 1,
|
|
649
|
+
name: 'operations',
|
|
650
|
+
kind: 'message',
|
|
651
|
+
T: NotifOperation,
|
|
652
|
+
repeated: true,
|
|
653
|
+
},
|
|
654
|
+
{ no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
|
|
655
|
+
])
|
|
656
|
+
|
|
657
|
+
static fromBinary(
|
|
658
|
+
bytes: Uint8Array,
|
|
659
|
+
options?: Partial<BinaryReadOptions>,
|
|
660
|
+
): ScanNotifOperationsResponse {
|
|
661
|
+
return new ScanNotifOperationsResponse().fromBinary(bytes, options)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
static fromJson(
|
|
665
|
+
jsonValue: JsonValue,
|
|
666
|
+
options?: Partial<JsonReadOptions>,
|
|
667
|
+
): ScanNotifOperationsResponse {
|
|
668
|
+
return new ScanNotifOperationsResponse().fromJson(jsonValue, options)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
static fromJsonString(
|
|
672
|
+
jsonString: string,
|
|
673
|
+
options?: Partial<JsonReadOptions>,
|
|
674
|
+
): ScanNotifOperationsResponse {
|
|
675
|
+
return new ScanNotifOperationsResponse().fromJsonString(jsonString, options)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
static equals(
|
|
679
|
+
a:
|
|
680
|
+
| ScanNotifOperationsResponse
|
|
681
|
+
| PlainMessage<ScanNotifOperationsResponse>
|
|
682
|
+
| undefined,
|
|
683
|
+
b:
|
|
684
|
+
| ScanNotifOperationsResponse
|
|
685
|
+
| PlainMessage<ScanNotifOperationsResponse>
|
|
686
|
+
| undefined,
|
|
687
|
+
): boolean {
|
|
688
|
+
return proto3.util.equals(ScanNotifOperationsResponse, a, b)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
375
692
|
/**
|
|
376
693
|
* Ping
|
|
377
694
|
*
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { sql } from 'kysely'
|
|
2
|
-
import {
|
|
3
|
-
AtUri,
|
|
4
|
-
InvalidDidError,
|
|
5
|
-
ensureValidAtUri,
|
|
6
|
-
ensureValidDid,
|
|
7
|
-
} from '@atproto/syntax'
|
|
2
|
+
import { AtUri } from '@atproto/syntax'
|
|
8
3
|
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
9
4
|
import { Service } from '../proto/bsync_connect'
|
|
10
5
|
import { AddMuteOperationResponse, MuteOperation_Type } from '../proto/bsync_pb'
|
|
@@ -12,6 +7,7 @@ import AppContext from '../context'
|
|
|
12
7
|
import { createMuteOpChannel } from '../db/schema/mute_op'
|
|
13
8
|
import { authWithApiKey } from './auth'
|
|
14
9
|
import Database from '../db'
|
|
10
|
+
import { isValidAtUri, isValidDid } from './util'
|
|
15
11
|
|
|
16
12
|
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
17
13
|
async addMuteOperation(req, handlerCtx) {
|
|
@@ -120,9 +116,12 @@ const validMuteOp = (op: MuteOpInfo): MuteOpInfoValid => {
|
|
|
120
116
|
// all good
|
|
121
117
|
} else if (isValidAtUri(op.subject)) {
|
|
122
118
|
const uri = new AtUri(op.subject)
|
|
123
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
uri.collection !== 'app.bsky.graph.list' &&
|
|
121
|
+
uri.collection !== 'app.bsky.feed.post'
|
|
122
|
+
) {
|
|
124
123
|
throw new ConnectError(
|
|
125
|
-
'subject aturis must reference a list record',
|
|
124
|
+
'subject aturis must reference a list or post record',
|
|
126
125
|
Code.InvalidArgument,
|
|
127
126
|
)
|
|
128
127
|
}
|
|
@@ -136,27 +135,6 @@ const validMuteOp = (op: MuteOpInfo): MuteOpInfoValid => {
|
|
|
136
135
|
return op as MuteOpInfoValid // op.type has been checked
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
const isValidDid = (did: string) => {
|
|
140
|
-
try {
|
|
141
|
-
ensureValidDid(did)
|
|
142
|
-
return true
|
|
143
|
-
} catch (err) {
|
|
144
|
-
if (err instanceof InvalidDidError) {
|
|
145
|
-
return false
|
|
146
|
-
}
|
|
147
|
-
throw err
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const isValidAtUri = (uri: string) => {
|
|
152
|
-
try {
|
|
153
|
-
ensureValidAtUri(uri)
|
|
154
|
-
return true
|
|
155
|
-
} catch {
|
|
156
|
-
return false
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
138
|
type MuteOpInfo = {
|
|
161
139
|
type: MuteOperation_Type
|
|
162
140
|
actorDid: string
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
2
|
+
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
3
|
+
import { Service } from '../proto/bsync_connect'
|
|
4
|
+
import { AddNotifOperationResponse } from '../proto/bsync_pb'
|
|
5
|
+
import AppContext from '../context'
|
|
6
|
+
import { authWithApiKey } from './auth'
|
|
7
|
+
import Database from '../db'
|
|
8
|
+
import { createNotifOpChannel } from '../db/schema/notif_op'
|
|
9
|
+
import { isValidDid } from './util'
|
|
10
|
+
|
|
11
|
+
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
12
|
+
async addNotifOperation(req, handlerCtx) {
|
|
13
|
+
authWithApiKey(ctx, handlerCtx)
|
|
14
|
+
const { db } = ctx
|
|
15
|
+
const { actorDid, priority } = req
|
|
16
|
+
if (!isValidDid(actorDid)) {
|
|
17
|
+
throw new ConnectError(
|
|
18
|
+
'actor_did must be a valid did',
|
|
19
|
+
Code.InvalidArgument,
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
const id = await db.transaction(async (txn) => {
|
|
23
|
+
// create notif op
|
|
24
|
+
const id = await createNotifOp(txn, actorDid, priority)
|
|
25
|
+
// update notif state
|
|
26
|
+
if (priority !== undefined) {
|
|
27
|
+
await updateNotifItem(txn, id, actorDid, priority)
|
|
28
|
+
}
|
|
29
|
+
return id
|
|
30
|
+
})
|
|
31
|
+
return new AddNotifOperationResponse({
|
|
32
|
+
operation: {
|
|
33
|
+
id: String(id),
|
|
34
|
+
actorDid,
|
|
35
|
+
priority,
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const createNotifOp = async (
|
|
42
|
+
db: Database,
|
|
43
|
+
actorDid: string,
|
|
44
|
+
priority: boolean | undefined,
|
|
45
|
+
) => {
|
|
46
|
+
const { ref } = db.db.dynamic
|
|
47
|
+
const { id } = await db.db
|
|
48
|
+
.insertInto('notif_op')
|
|
49
|
+
.values({
|
|
50
|
+
actorDid,
|
|
51
|
+
priority,
|
|
52
|
+
})
|
|
53
|
+
.returning('id')
|
|
54
|
+
.executeTakeFirstOrThrow()
|
|
55
|
+
await sql`notify ${ref(createNotifOpChannel)}`.execute(db.db) // emitted transactionally
|
|
56
|
+
return id
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const updateNotifItem = async (
|
|
60
|
+
db: Database,
|
|
61
|
+
fromId: number,
|
|
62
|
+
actorDid: string,
|
|
63
|
+
priority: boolean,
|
|
64
|
+
) => {
|
|
65
|
+
const { ref } = db.db.dynamic
|
|
66
|
+
await db.db
|
|
67
|
+
.insertInto('notif_item')
|
|
68
|
+
.values({
|
|
69
|
+
actorDid,
|
|
70
|
+
priority,
|
|
71
|
+
fromId,
|
|
72
|
+
})
|
|
73
|
+
.onConflict((oc) =>
|
|
74
|
+
oc.column('actorDid').doUpdateSet({
|
|
75
|
+
priority: sql`${ref('excluded.priority')}`,
|
|
76
|
+
fromId: sql`${ref('excluded.fromId')}`,
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
.execute()
|
|
80
|
+
}
|
package/src/routes/index.ts
CHANGED
|
@@ -4,11 +4,15 @@ import { Service } from '../proto/bsync_connect'
|
|
|
4
4
|
import AppContext from '../context'
|
|
5
5
|
import addMuteOperation from './add-mute-operation'
|
|
6
6
|
import scanMuteOperations from './scan-mute-operations'
|
|
7
|
+
import addNotifOperation from './add-notif-operation'
|
|
8
|
+
import scanNotifOperations from './scan-notif-operations'
|
|
7
9
|
|
|
8
10
|
export default (ctx: AppContext) => (router: ConnectRouter) => {
|
|
9
11
|
return router.service(Service, {
|
|
10
12
|
...addMuteOperation(ctx),
|
|
11
13
|
...scanMuteOperations(ctx),
|
|
14
|
+
...addNotifOperation(ctx),
|
|
15
|
+
...scanNotifOperations(ctx),
|
|
12
16
|
async ping() {
|
|
13
17
|
const { db } = ctx
|
|
14
18
|
await sql`select 1`.execute(db.db)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { once } from 'node:events'
|
|
2
|
-
import {
|
|
2
|
+
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
3
|
import { Service } from '../proto/bsync_connect'
|
|
4
4
|
import { ScanMuteOperationsResponse } from '../proto/bsync_pb'
|
|
5
5
|
import AppContext from '../context'
|
|
6
6
|
import { createMuteOpChannel } from '../db/schema/mute_op'
|
|
7
7
|
import { authWithApiKey } from './auth'
|
|
8
|
+
import { combineSignals, validCursor } from './util'
|
|
8
9
|
|
|
9
10
|
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
10
11
|
async scanMuteOperations(req, handlerCtx) {
|
|
@@ -62,27 +63,3 @@ export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
|
62
63
|
})
|
|
63
64
|
},
|
|
64
65
|
})
|
|
65
|
-
|
|
66
|
-
const validCursor = (cursor: string): number | null => {
|
|
67
|
-
if (cursor === '') return null
|
|
68
|
-
const int = parseInt(cursor, 10)
|
|
69
|
-
if (isNaN(int) || int < 0) {
|
|
70
|
-
throw new ConnectError('invalid cursor', Code.InvalidArgument)
|
|
71
|
-
}
|
|
72
|
-
return int
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const combineSignals = (a: AbortSignal, b: AbortSignal) => {
|
|
76
|
-
const controller = new AbortController()
|
|
77
|
-
for (const signal of [a, b]) {
|
|
78
|
-
if (signal.aborted) {
|
|
79
|
-
controller.abort()
|
|
80
|
-
return signal
|
|
81
|
-
}
|
|
82
|
-
signal.addEventListener('abort', () => controller.abort(signal.reason), {
|
|
83
|
-
// @ts-ignore https://github.com/DefinitelyTyped/DefinitelyTyped/pull/68625
|
|
84
|
-
signal: controller.signal,
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
return controller.signal
|
|
88
|
-
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
|
+
import { Service } from '../proto/bsync_connect'
|
|
4
|
+
import { ScanNotifOperationsResponse } from '../proto/bsync_pb'
|
|
5
|
+
import AppContext from '../context'
|
|
6
|
+
import { authWithApiKey } from './auth'
|
|
7
|
+
import { combineSignals, validCursor } from './util'
|
|
8
|
+
import { createNotifOpChannel } from '../db/schema/notif_op'
|
|
9
|
+
|
|
10
|
+
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
11
|
+
async scanNotifOperations(req, handlerCtx) {
|
|
12
|
+
authWithApiKey(ctx, handlerCtx)
|
|
13
|
+
const { db, events } = ctx
|
|
14
|
+
const limit = req.limit || 1000
|
|
15
|
+
const cursor = validCursor(req.cursor)
|
|
16
|
+
const nextNotifOpPromise = once(events, createNotifOpChannel, {
|
|
17
|
+
signal: combineSignals(
|
|
18
|
+
ctx.shutdown,
|
|
19
|
+
AbortSignal.timeout(ctx.cfg.service.longPollTimeoutMs),
|
|
20
|
+
),
|
|
21
|
+
})
|
|
22
|
+
nextNotifOpPromise.catch(() => null) // ensure timeout is always handled
|
|
23
|
+
|
|
24
|
+
const nextNotifOpPageQb = db.db
|
|
25
|
+
.selectFrom('notif_op')
|
|
26
|
+
.selectAll()
|
|
27
|
+
.where('id', '>', cursor ?? -1)
|
|
28
|
+
.orderBy('id', 'asc')
|
|
29
|
+
.limit(limit)
|
|
30
|
+
|
|
31
|
+
let ops = await nextNotifOpPageQb.execute()
|
|
32
|
+
|
|
33
|
+
if (!ops.length) {
|
|
34
|
+
// if there were no ops on the page, wait for an event then try again.
|
|
35
|
+
try {
|
|
36
|
+
await nextNotifOpPromise
|
|
37
|
+
} catch (err) {
|
|
38
|
+
ctx.shutdown.throwIfAborted()
|
|
39
|
+
return new ScanNotifOperationsResponse({
|
|
40
|
+
operations: [],
|
|
41
|
+
cursor: req.cursor,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
ops = await nextNotifOpPageQb.execute()
|
|
45
|
+
if (!ops.length) {
|
|
46
|
+
return new ScanNotifOperationsResponse({
|
|
47
|
+
operations: [],
|
|
48
|
+
cursor: req.cursor,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lastOp = ops[ops.length - 1]
|
|
54
|
+
|
|
55
|
+
return new ScanNotifOperationsResponse({
|
|
56
|
+
operations: ops.map((op) => ({
|
|
57
|
+
id: op.id.toString(),
|
|
58
|
+
actorDid: op.actorDid,
|
|
59
|
+
priority: op.priority ?? undefined,
|
|
60
|
+
})),
|
|
61
|
+
cursor: lastOp.id.toString(),
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Code, ConnectError } from '@connectrpc/connect'
|
|
2
|
+
import {
|
|
3
|
+
InvalidDidError,
|
|
4
|
+
ensureValidAtUri,
|
|
5
|
+
ensureValidDid,
|
|
6
|
+
} from '@atproto/syntax'
|
|
7
|
+
|
|
8
|
+
export const validCursor = (cursor: string): number | null => {
|
|
9
|
+
if (cursor === '') return null
|
|
10
|
+
const int = parseInt(cursor, 10)
|
|
11
|
+
if (isNaN(int) || int < 0) {
|
|
12
|
+
throw new ConnectError('invalid cursor', Code.InvalidArgument)
|
|
13
|
+
}
|
|
14
|
+
return int
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const combineSignals = (a: AbortSignal, b: AbortSignal) => {
|
|
18
|
+
const controller = new AbortController()
|
|
19
|
+
for (const signal of [a, b]) {
|
|
20
|
+
if (signal.aborted) {
|
|
21
|
+
controller.abort()
|
|
22
|
+
return signal
|
|
23
|
+
}
|
|
24
|
+
signal.addEventListener('abort', () => controller.abort(signal.reason), {
|
|
25
|
+
// @ts-ignore https://github.com/DefinitelyTyped/DefinitelyTyped/pull/68625
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
return controller.signal
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const isValidDid = (did: string) => {
|
|
33
|
+
try {
|
|
34
|
+
ensureValidDid(did)
|
|
35
|
+
return true
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err instanceof InvalidDidError) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
throw err
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const isValidAtUri = (uri: string) => {
|
|
45
|
+
try {
|
|
46
|
+
ensureValidAtUri(uri)
|
|
47
|
+
return true
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
package/tests/mutes.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wait } from '@atproto/common'
|
|
2
|
+
import getPort from 'get-port'
|
|
2
3
|
import { Code, ConnectError } from '@connectrpc/connect'
|
|
3
4
|
import {
|
|
4
5
|
BsyncClient,
|
|
@@ -17,6 +18,7 @@ describe('mutes', () => {
|
|
|
17
18
|
beforeAll(async () => {
|
|
18
19
|
bsync = await BsyncService.create(
|
|
19
20
|
envToCfg({
|
|
21
|
+
port: await getPort(),
|
|
20
22
|
dbUrl: process.env.DB_POSTGRES_URL,
|
|
21
23
|
dbSchema: 'bsync_mutes',
|
|
22
24
|
apiKeys: ['key-1'],
|