@atproto/bsync 0.0.31 → 0.0.33
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 +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/package.json +17 -13
- package/bin/migration-create.ts +0 -38
- package/buf.gen.yaml +0 -12
- package/jest.config.cjs +0 -21
- package/proto/bsync.proto +0 -134
- package/src/client.ts +0 -25
- package/src/config.ts +0 -90
- package/src/context.ts +0 -48
- package/src/db/index.ts +0 -200
- package/src/db/migrations/20240108T220751294Z-init.ts +0 -26
- package/src/db/migrations/20240717T224303472Z-notif-ops.ts +0 -24
- package/src/db/migrations/20250527T022203400Z-add-operation.ts +0 -20
- package/src/db/migrations/20250603T163446567Z-alter-operation.ts +0 -19
- package/src/db/migrations/index.ts +0 -8
- package/src/db/migrations/provider.ts +0 -8
- package/src/db/schema/index.ts +0 -16
- package/src/db/schema/mute_item.ts +0 -13
- package/src/db/schema/mute_op.ts +0 -18
- package/src/db/schema/notif_item.ts +0 -13
- package/src/db/schema/notif_op.ts +0 -16
- package/src/db/schema/operation.ts +0 -20
- package/src/db/types.ts +0 -19
- package/src/index.ts +0 -130
- package/src/logger.ts +0 -26
- package/src/routes/add-mute-operation.ts +0 -154
- package/src/routes/add-notif-operation.ts +0 -80
- package/src/routes/auth.ts +0 -15
- package/src/routes/delete-operations.ts +0 -45
- package/src/routes/index.ts +0 -28
- package/src/routes/put-operation.ts +0 -115
- package/src/routes/scan-mute-operations.ts +0 -65
- package/src/routes/scan-notif-operations.ts +0 -64
- package/src/routes/scan-operations.ts +0 -67
- package/src/routes/util.ts +0 -67
- package/tests/delete-operations.test.ts +0 -108
- package/tests/mutes.test.ts +0 -352
- package/tests/notifications.test.ts +0 -209
- package/tests/operations.test.ts +0 -327
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
-
import { sql } from 'kysely'
|
|
3
|
-
import { AtUri } from '@atproto/syntax'
|
|
4
|
-
import { AppContext } from '../context.js'
|
|
5
|
-
import { Database } from '../db/index.js'
|
|
6
|
-
import { createMuteOpChannel } from '../db/schema/mute_op.js'
|
|
7
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
8
|
-
import {
|
|
9
|
-
AddMuteOperationResponse,
|
|
10
|
-
MuteOperation_Type,
|
|
11
|
-
} from '../proto/bsync_pb.js'
|
|
12
|
-
import { authWithApiKey } from './auth.js'
|
|
13
|
-
import { isValidAtUri, isValidDid } from './util.js'
|
|
14
|
-
|
|
15
|
-
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
16
|
-
async addMuteOperation(req, handlerCtx) {
|
|
17
|
-
authWithApiKey(ctx, handlerCtx)
|
|
18
|
-
const { db } = ctx
|
|
19
|
-
const op = validMuteOp(req)
|
|
20
|
-
const id = await db.transaction(async (txn) => {
|
|
21
|
-
// create mute op
|
|
22
|
-
const id = await createMuteOp(txn, op)
|
|
23
|
-
// update mute state
|
|
24
|
-
if (op.type === MuteOperation_Type.ADD) {
|
|
25
|
-
await addMuteItem(txn, id, op)
|
|
26
|
-
} else if (op.type === MuteOperation_Type.REMOVE) {
|
|
27
|
-
await removeMuteItem(txn, op)
|
|
28
|
-
} else if (op.type === MuteOperation_Type.CLEAR) {
|
|
29
|
-
await clearMuteItems(txn, op)
|
|
30
|
-
} else {
|
|
31
|
-
const exhaustiveCheck: never = op.type
|
|
32
|
-
throw new Error(`unreachable: ${exhaustiveCheck}`)
|
|
33
|
-
}
|
|
34
|
-
return id
|
|
35
|
-
})
|
|
36
|
-
return new AddMuteOperationResponse({
|
|
37
|
-
operation: {
|
|
38
|
-
id: String(id),
|
|
39
|
-
type: op.type,
|
|
40
|
-
actorDid: op.actorDid,
|
|
41
|
-
subject: op.subject,
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const createMuteOp = async (db: Database, op: MuteOpInfo) => {
|
|
48
|
-
const { ref } = db.db.dynamic
|
|
49
|
-
const { id } = await db.db
|
|
50
|
-
.insertInto('mute_op')
|
|
51
|
-
.values({
|
|
52
|
-
type: op.type,
|
|
53
|
-
actorDid: op.actorDid,
|
|
54
|
-
subject: op.subject,
|
|
55
|
-
})
|
|
56
|
-
.returning('id')
|
|
57
|
-
.executeTakeFirstOrThrow()
|
|
58
|
-
await sql`notify ${ref(createMuteOpChannel)}`.execute(db.db) // emitted transactionally
|
|
59
|
-
return id
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const addMuteItem = async (db: Database, fromId: number, op: MuteOpInfo) => {
|
|
63
|
-
const { ref } = db.db.dynamic
|
|
64
|
-
await db.db
|
|
65
|
-
.insertInto('mute_item')
|
|
66
|
-
.values({
|
|
67
|
-
actorDid: op.actorDid,
|
|
68
|
-
subject: op.subject,
|
|
69
|
-
fromId,
|
|
70
|
-
})
|
|
71
|
-
.onConflict((oc) =>
|
|
72
|
-
oc
|
|
73
|
-
.constraint('mute_item_pkey')
|
|
74
|
-
.doUpdateSet({ fromId: sql`${ref('excluded.fromId')}` }),
|
|
75
|
-
)
|
|
76
|
-
.execute()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const removeMuteItem = async (db: Database, op: MuteOpInfo) => {
|
|
80
|
-
await db.db
|
|
81
|
-
.deleteFrom('mute_item')
|
|
82
|
-
.where('actorDid', '=', op.actorDid)
|
|
83
|
-
.where('subject', '=', op.subject)
|
|
84
|
-
.execute()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const clearMuteItems = async (db: Database, op: MuteOpInfo) => {
|
|
88
|
-
await db.db
|
|
89
|
-
.deleteFrom('mute_item')
|
|
90
|
-
.where('actorDid', '=', op.actorDid)
|
|
91
|
-
.execute()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const validMuteOp = (op: MuteOpInfo): MuteOpInfoValid => {
|
|
95
|
-
if (!Object.values(MuteOperation_Type).includes(op.type)) {
|
|
96
|
-
throw new ConnectError('bad mute operation type', Code.InvalidArgument)
|
|
97
|
-
}
|
|
98
|
-
if (op.type === MuteOperation_Type.UNSPECIFIED) {
|
|
99
|
-
throw new ConnectError(
|
|
100
|
-
'unspecified mute operation type',
|
|
101
|
-
Code.InvalidArgument,
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
if (!isValidDid(op.actorDid)) {
|
|
105
|
-
throw new ConnectError(
|
|
106
|
-
'actor_did must be a valid did',
|
|
107
|
-
Code.InvalidArgument,
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
if (op.type === MuteOperation_Type.CLEAR) {
|
|
111
|
-
if (op.subject !== '') {
|
|
112
|
-
throw new ConnectError(
|
|
113
|
-
'subject must not be set on a clear op',
|
|
114
|
-
Code.InvalidArgument,
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
if (isValidDid(op.subject)) {
|
|
119
|
-
// all good
|
|
120
|
-
} else if (isValidAtUri(op.subject)) {
|
|
121
|
-
const uri = new AtUri(op.subject)
|
|
122
|
-
if (
|
|
123
|
-
uri.collection !== 'app.bsky.graph.list' &&
|
|
124
|
-
uri.collection !== 'app.bsky.feed.post'
|
|
125
|
-
) {
|
|
126
|
-
throw new ConnectError(
|
|
127
|
-
'subject aturis must reference a list or post record',
|
|
128
|
-
Code.InvalidArgument,
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
throw new ConnectError(
|
|
133
|
-
'subject must be a did or aturi on add or remove op',
|
|
134
|
-
Code.InvalidArgument,
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return op as MuteOpInfoValid // op.type has been checked
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
type MuteOpInfo = {
|
|
142
|
-
type: MuteOperation_Type
|
|
143
|
-
actorDid: string
|
|
144
|
-
subject: string
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
type MuteOpInfoValid = {
|
|
148
|
-
type:
|
|
149
|
-
| MuteOperation_Type.ADD
|
|
150
|
-
| MuteOperation_Type.REMOVE
|
|
151
|
-
| MuteOperation_Type.CLEAR
|
|
152
|
-
actorDid: string
|
|
153
|
-
subject: string
|
|
154
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
-
import { sql } from 'kysely'
|
|
3
|
-
import { AppContext } from '../context.js'
|
|
4
|
-
import { Database } from '../db/index.js'
|
|
5
|
-
import { createNotifOpChannel } from '../db/schema/notif_op.js'
|
|
6
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
7
|
-
import { AddNotifOperationResponse } from '../proto/bsync_pb.js'
|
|
8
|
-
import { authWithApiKey } from './auth.js'
|
|
9
|
-
import { isValidDid } from './util.js'
|
|
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/auth.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Code, ConnectError, HandlerContext } from '@connectrpc/connect'
|
|
2
|
-
import { AppContext } from '../context.js'
|
|
3
|
-
|
|
4
|
-
const BEARER = 'Bearer '
|
|
5
|
-
|
|
6
|
-
export const authWithApiKey = (ctx: AppContext, handlerCtx: HandlerContext) => {
|
|
7
|
-
const authorization = handlerCtx.requestHeader.get('authorization')
|
|
8
|
-
if (!authorization?.startsWith(BEARER)) {
|
|
9
|
-
throw new ConnectError('missing auth', Code.Unauthenticated)
|
|
10
|
-
}
|
|
11
|
-
const key = authorization.slice(BEARER.length)
|
|
12
|
-
if (!ctx.cfg.auth.apiKeys.has(key)) {
|
|
13
|
-
throw new ConnectError('invalid api key', Code.Unauthenticated)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
-
import { AppContext } from '../context.js'
|
|
3
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
4
|
-
import { DeleteOperationsByActorAndNamespaceResponse } from '../proto/bsync_pb.js'
|
|
5
|
-
import { authWithApiKey } from './auth.js'
|
|
6
|
-
import { isValidDid, validateNamespace } from './util.js'
|
|
7
|
-
|
|
8
|
-
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
9
|
-
/**
|
|
10
|
-
* This method is responsible for deleting log rows from the bsync db, it has
|
|
11
|
-
* no other downstream effects. This method is called from the dataplane in
|
|
12
|
-
* response to a data deletion request initiated by a moderator in Ozone.
|
|
13
|
-
* It's the final step of the deletion process, basically cleaning up the
|
|
14
|
-
* breadcrumbs that resulted in the state we store in the dataplane.
|
|
15
|
-
*/
|
|
16
|
-
async deleteOperationsByActorAndNamespace(req, handlerCtx) {
|
|
17
|
-
authWithApiKey(ctx, handlerCtx)
|
|
18
|
-
const { db } = ctx
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
validateNamespace(req.namespace)
|
|
22
|
-
} catch (error) {
|
|
23
|
-
throw new ConnectError(
|
|
24
|
-
'requested namespace for deletion is invalid NSID',
|
|
25
|
-
Code.InvalidArgument,
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
if (!isValidDid(req.actorDid)) {
|
|
29
|
-
throw new ConnectError(
|
|
30
|
-
'requested actor_did for deletion is invalid DID',
|
|
31
|
-
Code.InvalidArgument,
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const deletedRows = await db.db
|
|
36
|
-
.deleteFrom('operation')
|
|
37
|
-
.where('actorDid', '=', req.actorDid)
|
|
38
|
-
.where('namespace', '=', req.namespace)
|
|
39
|
-
.returning('id')
|
|
40
|
-
.execute()
|
|
41
|
-
return new DeleteOperationsByActorAndNamespaceResponse({
|
|
42
|
-
deletedCount: deletedRows.length,
|
|
43
|
-
})
|
|
44
|
-
},
|
|
45
|
-
})
|
package/src/routes/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { ConnectRouter } from '@connectrpc/connect'
|
|
2
|
-
import { sql } from 'kysely'
|
|
3
|
-
import { AppContext } from '../context.js'
|
|
4
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
5
|
-
import addMuteOperation from './add-mute-operation.js'
|
|
6
|
-
import addNotifOperation from './add-notif-operation.js'
|
|
7
|
-
import deleteOperations from './delete-operations.js'
|
|
8
|
-
import putOperation from './put-operation.js'
|
|
9
|
-
import scanMuteOperations from './scan-mute-operations.js'
|
|
10
|
-
import scanNotifOperations from './scan-notif-operations.js'
|
|
11
|
-
import scanOperations from './scan-operations.js'
|
|
12
|
-
|
|
13
|
-
export default (ctx: AppContext) => (router: ConnectRouter) => {
|
|
14
|
-
return router.service(Service, {
|
|
15
|
-
...addMuteOperation(ctx),
|
|
16
|
-
...scanMuteOperations(ctx),
|
|
17
|
-
...addNotifOperation(ctx),
|
|
18
|
-
...scanNotifOperations(ctx),
|
|
19
|
-
...putOperation(ctx),
|
|
20
|
-
...scanOperations(ctx),
|
|
21
|
-
...deleteOperations(ctx),
|
|
22
|
-
async ping() {
|
|
23
|
-
const { db } = ctx
|
|
24
|
-
await sql`select 1`.execute(db.db)
|
|
25
|
-
return {}
|
|
26
|
-
},
|
|
27
|
-
})
|
|
28
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
-
import { sql } from 'kysely'
|
|
3
|
-
import { ensureValidRecordKey } from '@atproto/syntax'
|
|
4
|
-
import { AppContext } from '../context.js'
|
|
5
|
-
import { Database } from '../db/index.js'
|
|
6
|
-
import {
|
|
7
|
-
OperationMethod,
|
|
8
|
-
createOperationChannel,
|
|
9
|
-
} from '../db/schema/operation.js'
|
|
10
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
11
|
-
import {
|
|
12
|
-
Method,
|
|
13
|
-
PutOperationRequest,
|
|
14
|
-
PutOperationResponse,
|
|
15
|
-
} from '../proto/bsync_pb.js'
|
|
16
|
-
import { authWithApiKey } from './auth.js'
|
|
17
|
-
import { isValidDid, validateNamespace } from './util.js'
|
|
18
|
-
|
|
19
|
-
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
20
|
-
async putOperation(req, handlerCtx) {
|
|
21
|
-
authWithApiKey(ctx, handlerCtx)
|
|
22
|
-
const { db } = ctx
|
|
23
|
-
const op = validateOp(req)
|
|
24
|
-
const id = await db.transaction(async (txn) => {
|
|
25
|
-
return putOp(txn, op)
|
|
26
|
-
})
|
|
27
|
-
return new PutOperationResponse({
|
|
28
|
-
operation: {
|
|
29
|
-
id: String(id),
|
|
30
|
-
actorDid: op.actorDid,
|
|
31
|
-
namespace: op.namespace,
|
|
32
|
-
key: op.key,
|
|
33
|
-
method: op.method,
|
|
34
|
-
payload: op.payload,
|
|
35
|
-
},
|
|
36
|
-
})
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const putOp = async (db: Database, op: Operation) => {
|
|
41
|
-
const { ref } = db.db.dynamic
|
|
42
|
-
const { id } = await db.db
|
|
43
|
-
.insertInto('operation')
|
|
44
|
-
.values({
|
|
45
|
-
actorDid: op.actorDid,
|
|
46
|
-
namespace: op.namespace,
|
|
47
|
-
key: op.key,
|
|
48
|
-
method: op.method,
|
|
49
|
-
payload: op.payload,
|
|
50
|
-
})
|
|
51
|
-
.returning('id')
|
|
52
|
-
.executeTakeFirstOrThrow()
|
|
53
|
-
await sql`notify ${ref(createOperationChannel)}`.execute(db.db) // emitted transactionally
|
|
54
|
-
return id
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const validateOp = (req: PutOperationRequest): Operation => {
|
|
58
|
-
try {
|
|
59
|
-
validateNamespace(req.namespace)
|
|
60
|
-
} catch (error) {
|
|
61
|
-
throw new ConnectError(
|
|
62
|
-
'operation namespace is invalid NSID',
|
|
63
|
-
Code.InvalidArgument,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!isValidDid(req.actorDid)) {
|
|
68
|
-
throw new ConnectError(
|
|
69
|
-
'operation actor_did is invalid DID',
|
|
70
|
-
Code.InvalidArgument,
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
ensureValidRecordKey(req.key)
|
|
76
|
-
} catch (error) {
|
|
77
|
-
throw new ConnectError('operation key is required', Code.InvalidArgument)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
req.method !== Method.CREATE &&
|
|
82
|
-
req.method !== Method.UPDATE &&
|
|
83
|
-
req.method !== Method.DELETE
|
|
84
|
-
) {
|
|
85
|
-
throw new ConnectError('operation method is invalid', Code.InvalidArgument)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (req.method === Method.CREATE || req.method === Method.UPDATE) {
|
|
89
|
-
try {
|
|
90
|
-
JSON.parse(new TextDecoder().decode(req.payload))
|
|
91
|
-
} catch (error) {
|
|
92
|
-
throw new ConnectError(
|
|
93
|
-
'payload must be a valid JSON when method is CREATE or UPDATE',
|
|
94
|
-
Code.InvalidArgument,
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (req.method === Method.DELETE && req.payload.length > 0) {
|
|
100
|
-
throw new ConnectError(
|
|
101
|
-
'cannot specify a payload when method is DELETE',
|
|
102
|
-
Code.InvalidArgument,
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return req as Operation
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type Operation = {
|
|
110
|
-
actorDid: string
|
|
111
|
-
namespace: string
|
|
112
|
-
key: string
|
|
113
|
-
payload: Uint8Array<ArrayBuffer>
|
|
114
|
-
method: OperationMethod
|
|
115
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { once } from 'node:events'
|
|
2
|
-
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
|
-
import { AppContext } from '../context.js'
|
|
4
|
-
import { createMuteOpChannel } from '../db/schema/mute_op.js'
|
|
5
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
6
|
-
import { ScanMuteOperationsResponse } from '../proto/bsync_pb.js'
|
|
7
|
-
import { authWithApiKey } from './auth.js'
|
|
8
|
-
import { combineSignals, validCursor } from './util.js'
|
|
9
|
-
|
|
10
|
-
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
11
|
-
async scanMuteOperations(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 nextMuteOpPromise = once(events, createMuteOpChannel, {
|
|
17
|
-
signal: combineSignals(
|
|
18
|
-
ctx.shutdown,
|
|
19
|
-
AbortSignal.timeout(ctx.cfg.service.longPollTimeoutMs),
|
|
20
|
-
),
|
|
21
|
-
})
|
|
22
|
-
nextMuteOpPromise.catch(() => null) // ensure timeout is always handled
|
|
23
|
-
|
|
24
|
-
const nextMuteOpPageQb = db.db
|
|
25
|
-
.selectFrom('mute_op')
|
|
26
|
-
.selectAll()
|
|
27
|
-
.where('id', '>', cursor ?? -1)
|
|
28
|
-
.orderBy('id', 'asc')
|
|
29
|
-
.limit(limit)
|
|
30
|
-
|
|
31
|
-
let ops = await nextMuteOpPageQb.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 nextMuteOpPromise
|
|
37
|
-
} catch (err) {
|
|
38
|
-
ctx.shutdown.throwIfAborted()
|
|
39
|
-
return new ScanMuteOperationsResponse({
|
|
40
|
-
operations: [],
|
|
41
|
-
cursor: req.cursor,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
ops = await nextMuteOpPageQb.execute()
|
|
45
|
-
if (!ops.length) {
|
|
46
|
-
return new ScanMuteOperationsResponse({
|
|
47
|
-
operations: [],
|
|
48
|
-
cursor: req.cursor,
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const lastOp = ops[ops.length - 1]
|
|
54
|
-
|
|
55
|
-
return new ScanMuteOperationsResponse({
|
|
56
|
-
operations: ops.map((op) => ({
|
|
57
|
-
id: op.id.toString(),
|
|
58
|
-
type: op.type,
|
|
59
|
-
actorDid: op.actorDid,
|
|
60
|
-
subject: op.subject,
|
|
61
|
-
})),
|
|
62
|
-
cursor: lastOp.id.toString(),
|
|
63
|
-
})
|
|
64
|
-
},
|
|
65
|
-
})
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { once } from 'node:events'
|
|
2
|
-
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
|
-
import { AppContext } from '../context.js'
|
|
4
|
-
import { createNotifOpChannel } from '../db/schema/notif_op.js'
|
|
5
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
6
|
-
import { ScanNotifOperationsResponse } from '../proto/bsync_pb.js'
|
|
7
|
-
import { authWithApiKey } from './auth.js'
|
|
8
|
-
import { combineSignals, validCursor } from './util.js'
|
|
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
|
-
})
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { once } from 'node:events'
|
|
2
|
-
import { ServiceImpl } from '@connectrpc/connect'
|
|
3
|
-
import { AppContext } from '../context.js'
|
|
4
|
-
import { createOperationChannel } from '../db/schema/operation.js'
|
|
5
|
-
import { Service } from '../proto/bsync_connect.js'
|
|
6
|
-
import { ScanOperationsResponse } from '../proto/bsync_pb.js'
|
|
7
|
-
import { authWithApiKey } from './auth.js'
|
|
8
|
-
import { combineSignals, validCursor } from './util.js'
|
|
9
|
-
|
|
10
|
-
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
11
|
-
async scanOperations(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 nextOpPromise = once(events, createOperationChannel, {
|
|
17
|
-
signal: combineSignals(
|
|
18
|
-
ctx.shutdown,
|
|
19
|
-
AbortSignal.timeout(ctx.cfg.service.longPollTimeoutMs),
|
|
20
|
-
),
|
|
21
|
-
})
|
|
22
|
-
nextOpPromise.catch(() => null) // ensure timeout is always handled
|
|
23
|
-
|
|
24
|
-
const nextOpPageQb = db.db
|
|
25
|
-
.selectFrom('operation')
|
|
26
|
-
.selectAll()
|
|
27
|
-
.where('id', '>', cursor ?? -1)
|
|
28
|
-
.orderBy('id', 'asc')
|
|
29
|
-
.limit(limit)
|
|
30
|
-
|
|
31
|
-
let ops = await nextOpPageQb.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 nextOpPromise
|
|
37
|
-
} catch (err) {
|
|
38
|
-
ctx.shutdown.throwIfAborted()
|
|
39
|
-
return new ScanOperationsResponse({
|
|
40
|
-
operations: [],
|
|
41
|
-
cursor: req.cursor,
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
ops = await nextOpPageQb.execute()
|
|
45
|
-
if (!ops.length) {
|
|
46
|
-
return new ScanOperationsResponse({
|
|
47
|
-
operations: [],
|
|
48
|
-
cursor: req.cursor,
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const lastOp = ops[ops.length - 1]
|
|
54
|
-
|
|
55
|
-
return new ScanOperationsResponse({
|
|
56
|
-
operations: ops.map((op) => ({
|
|
57
|
-
id: op.id.toString(),
|
|
58
|
-
actorDid: op.actorDid,
|
|
59
|
-
namespace: op.namespace,
|
|
60
|
-
key: op.key,
|
|
61
|
-
method: op.method,
|
|
62
|
-
payload: op.payload,
|
|
63
|
-
})),
|
|
64
|
-
cursor: lastOp.id.toString(),
|
|
65
|
-
})
|
|
66
|
-
},
|
|
67
|
-
})
|