@atproto/bsync 0.0.23 → 0.0.25
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 +17 -0
- package/LICENSE.txt +1 -1
- package/dist/proto/bsync_connect.d.ts +10 -1
- package/dist/proto/bsync_connect.d.ts.map +1 -1
- package/dist/proto/bsync_connect.js +9 -0
- package/dist/proto/bsync_connect.js.map +1 -1
- package/dist/proto/bsync_pb.d.ts +38 -0
- package/dist/proto/bsync_pb.d.ts.map +1 -1
- package/dist/proto/bsync_pb.js +113 -1
- package/dist/proto/bsync_pb.js.map +1 -1
- package/dist/routes/delete-operations.d.ts +6 -0
- package/dist/routes/delete-operations.d.ts.map +1 -0
- package/dist/routes/delete-operations.js +38 -0
- package/dist/routes/delete-operations.js.map +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/put-operation.js +1 -12
- package/dist/routes/put-operation.js.map +1 -1
- package/dist/routes/util.d.ts +1 -0
- package/dist/routes/util.d.ts.map +1 -1
- package/dist/routes/util.js +13 -1
- package/dist/routes/util.js.map +1 -1
- package/package.json +3 -3
- package/proto/bsync.proto +10 -0
- package/src/proto/bsync_connect.ts +10 -1
- package/src/proto/bsync_pb.ts +80 -0
- package/src/routes/delete-operations.ts +45 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/put-operation.ts +2 -17
- package/src/routes/util.ts +16 -0
- package/tests/delete-operations.test.ts +108 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.tests.tsbuildinfo +0 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const connect_1 = require("@connectrpc/connect");
|
|
4
|
+
const bsync_pb_1 = require("../proto/bsync_pb");
|
|
5
|
+
const auth_1 = require("./auth");
|
|
6
|
+
const util_1 = require("./util");
|
|
7
|
+
exports.default = (ctx) => ({
|
|
8
|
+
/**
|
|
9
|
+
* This method is responsible for deleting log rows from the bsync db, it has
|
|
10
|
+
* no other downstream effects. This method is called from the dataplane in
|
|
11
|
+
* response to a data deletion request initiated by a moderator in Ozone.
|
|
12
|
+
* It's the final step of the deletion process, basically cleaning up the
|
|
13
|
+
* breadcrumbs that resulted in the state we store in the dataplane.
|
|
14
|
+
*/
|
|
15
|
+
async deleteOperationsByActorAndNamespace(req, handlerCtx) {
|
|
16
|
+
(0, auth_1.authWithApiKey)(ctx, handlerCtx);
|
|
17
|
+
const { db } = ctx;
|
|
18
|
+
try {
|
|
19
|
+
(0, util_1.validateNamespace)(req.namespace);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new connect_1.ConnectError('requested namespace for deletion is invalid NSID', connect_1.Code.InvalidArgument);
|
|
23
|
+
}
|
|
24
|
+
if (!(0, util_1.isValidDid)(req.actorDid)) {
|
|
25
|
+
throw new connect_1.ConnectError('requested actor_did for deletion is invalid DID', connect_1.Code.InvalidArgument);
|
|
26
|
+
}
|
|
27
|
+
const deletedRows = await db.db
|
|
28
|
+
.deleteFrom('operation')
|
|
29
|
+
.where('actorDid', '=', req.actorDid)
|
|
30
|
+
.where('namespace', '=', req.namespace)
|
|
31
|
+
.returning('id')
|
|
32
|
+
.execute();
|
|
33
|
+
return new bsync_pb_1.DeleteOperationsByActorAndNamespaceResponse({
|
|
34
|
+
deletedCount: deletedRows.length,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=delete-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-operations.js","sourceRoot":"","sources":["../../src/routes/delete-operations.ts"],"names":[],"mappings":";;AAAA,iDAAqE;AAGrE,gDAA+E;AAC/E,iCAAuC;AACvC,iCAAsD;AAEtD,kBAAe,CAAC,GAAe,EAAwC,EAAE,CAAC,CAAC;IACzE;;;;;;OAMG;IACH,KAAK,CAAC,mCAAmC,CAAC,GAAG,EAAE,UAAU;QACvD,IAAA,qBAAc,EAAC,GAAG,EAAE,UAAU,CAAC,CAAA;QAC/B,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAA;QAElB,IAAI,CAAC;YACH,IAAA,wBAAiB,EAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,sBAAY,CACpB,kDAAkD,EAClD,cAAI,CAAC,eAAe,CACrB,CAAA;QACH,CAAC;QACD,IAAI,CAAC,IAAA,iBAAU,EAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,sBAAY,CACpB,iDAAiD,EACjD,cAAI,CAAC,eAAe,CACrB,CAAA;QACH,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,EAAE;aAC5B,UAAU,CAAC,WAAW,CAAC;aACvB,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC;aACpC,KAAK,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC;aACtC,SAAS,CAAC,IAAI,CAAC;aACf,OAAO,EAAE,CAAA;QACZ,OAAO,IAAI,sDAA2C,CAAC;YACrD,YAAY,EAAE,WAAW,CAAC,MAAM;SACjC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA","sourcesContent":["import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'\nimport { AppContext } from '../context'\nimport { Service } from '../proto/bsync_connect'\nimport { DeleteOperationsByActorAndNamespaceResponse } from '../proto/bsync_pb'\nimport { authWithApiKey } from './auth'\nimport { isValidDid, validateNamespace } from './util'\n\nexport default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({\n /**\n * This method is responsible for deleting log rows from the bsync db, it has\n * no other downstream effects. This method is called from the dataplane in\n * response to a data deletion request initiated by a moderator in Ozone.\n * It's the final step of the deletion process, basically cleaning up the\n * breadcrumbs that resulted in the state we store in the dataplane.\n */\n async deleteOperationsByActorAndNamespace(req, handlerCtx) {\n authWithApiKey(ctx, handlerCtx)\n const { db } = ctx\n\n try {\n validateNamespace(req.namespace)\n } catch (error) {\n throw new ConnectError(\n 'requested namespace for deletion is invalid NSID',\n Code.InvalidArgument,\n )\n }\n if (!isValidDid(req.actorDid)) {\n throw new ConnectError(\n 'requested actor_did for deletion is invalid DID',\n Code.InvalidArgument,\n )\n }\n\n const deletedRows = await db.db\n .deleteFrom('operation')\n .where('actorDid', '=', req.actorDid)\n .where('namespace', '=', req.namespace)\n .returning('id')\n .execute()\n return new DeleteOperationsByActorAndNamespaceResponse({\n deletedCount: deletedRows.length,\n })\n },\n})\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;yBAUvB,KAAK,UAAU,MAAM,QAAQ,aAAa;AAA1D,wBAeC"}
|
package/dist/routes/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const kysely_1 = require("kysely");
|
|
|
7
7
|
const bsync_connect_1 = require("../proto/bsync_connect");
|
|
8
8
|
const add_mute_operation_1 = __importDefault(require("./add-mute-operation"));
|
|
9
9
|
const add_notif_operation_1 = __importDefault(require("./add-notif-operation"));
|
|
10
|
+
const delete_operations_1 = __importDefault(require("./delete-operations"));
|
|
10
11
|
const put_operation_1 = __importDefault(require("./put-operation"));
|
|
11
12
|
const scan_mute_operations_1 = __importDefault(require("./scan-mute-operations"));
|
|
12
13
|
const scan_notif_operations_1 = __importDefault(require("./scan-notif-operations"));
|
|
@@ -19,6 +20,7 @@ exports.default = (ctx) => (router) => {
|
|
|
19
20
|
...(0, scan_notif_operations_1.default)(ctx),
|
|
20
21
|
...(0, put_operation_1.default)(ctx),
|
|
21
22
|
...(0, scan_operations_1.default)(ctx),
|
|
23
|
+
...(0, delete_operations_1.default)(ctx),
|
|
22
24
|
async ping() {
|
|
23
25
|
const { db } = ctx;
|
|
24
26
|
await (0, kysely_1.sql) `select 1`.execute(db.db);
|
package/dist/routes/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":";;;;;AACA,mCAA4B;AAE5B,0DAAgD;AAChD,8EAAmD;AACnD,gFAAqD;AACrD,oEAA0C;AAC1C,kFAAuD;AACvD,oFAAyD;AACzD,wEAA8C;AAE9C,kBAAe,CAAC,GAAe,EAAE,EAAE,CAAC,CAAC,MAAqB,EAAE,EAAE;IAC5D,OAAO,MAAM,CAAC,OAAO,CAAC,uBAAO,EAAE;QAC7B,GAAG,IAAA,4BAAgB,EAAC,GAAG,CAAC;QACxB,GAAG,IAAA,8BAAkB,EAAC,GAAG,CAAC;QAC1B,GAAG,IAAA,6BAAiB,EAAC,GAAG,CAAC;QACzB,GAAG,IAAA,+BAAmB,EAAC,GAAG,CAAC;QAC3B,GAAG,IAAA,uBAAY,EAAC,GAAG,CAAC;QACpB,GAAG,IAAA,yBAAc,EAAC,GAAG,CAAC;QACtB,KAAK,CAAC,IAAI;YACR,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAA;YAClB,MAAM,IAAA,YAAG,EAAA,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;YAClC,OAAO,EAAE,CAAA;QACX,CAAC;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { ConnectRouter } from '@connectrpc/connect'\nimport { sql } from 'kysely'\nimport { AppContext } from '../context'\nimport { Service } from '../proto/bsync_connect'\nimport addMuteOperation from './add-mute-operation'\nimport addNotifOperation from './add-notif-operation'\nimport putOperation from './put-operation'\nimport scanMuteOperations from './scan-mute-operations'\nimport scanNotifOperations from './scan-notif-operations'\nimport scanOperations from './scan-operations'\n\nexport default (ctx: AppContext) => (router: ConnectRouter) => {\n return router.service(Service, {\n ...addMuteOperation(ctx),\n ...scanMuteOperations(ctx),\n ...addNotifOperation(ctx),\n ...scanNotifOperations(ctx),\n ...putOperation(ctx),\n ...scanOperations(ctx),\n async ping() {\n const { db } = ctx\n await sql`select 1`.execute(db.db)\n return {}\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":";;;;;AACA,mCAA4B;AAE5B,0DAAgD;AAChD,8EAAmD;AACnD,gFAAqD;AACrD,4EAAkD;AAClD,oEAA0C;AAC1C,kFAAuD;AACvD,oFAAyD;AACzD,wEAA8C;AAE9C,kBAAe,CAAC,GAAe,EAAE,EAAE,CAAC,CAAC,MAAqB,EAAE,EAAE;IAC5D,OAAO,MAAM,CAAC,OAAO,CAAC,uBAAO,EAAE;QAC7B,GAAG,IAAA,4BAAgB,EAAC,GAAG,CAAC;QACxB,GAAG,IAAA,8BAAkB,EAAC,GAAG,CAAC;QAC1B,GAAG,IAAA,6BAAiB,EAAC,GAAG,CAAC;QACzB,GAAG,IAAA,+BAAmB,EAAC,GAAG,CAAC;QAC3B,GAAG,IAAA,uBAAY,EAAC,GAAG,CAAC;QACpB,GAAG,IAAA,yBAAc,EAAC,GAAG,CAAC;QACtB,GAAG,IAAA,2BAAgB,EAAC,GAAG,CAAC;QACxB,KAAK,CAAC,IAAI;YACR,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAA;YAClB,MAAM,IAAA,YAAG,EAAA,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;YAClC,OAAO,EAAE,CAAA;QACX,CAAC;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { ConnectRouter } from '@connectrpc/connect'\nimport { sql } from 'kysely'\nimport { AppContext } from '../context'\nimport { Service } from '../proto/bsync_connect'\nimport addMuteOperation from './add-mute-operation'\nimport addNotifOperation from './add-notif-operation'\nimport deleteOperations from './delete-operations'\nimport putOperation from './put-operation'\nimport scanMuteOperations from './scan-mute-operations'\nimport scanNotifOperations from './scan-notif-operations'\nimport scanOperations from './scan-operations'\n\nexport default (ctx: AppContext) => (router: ConnectRouter) => {\n return router.service(Service, {\n ...addMuteOperation(ctx),\n ...scanMuteOperations(ctx),\n ...addNotifOperation(ctx),\n ...scanNotifOperations(ctx),\n ...putOperation(ctx),\n ...scanOperations(ctx),\n ...deleteOperations(ctx),\n async ping() {\n const { db } = ctx\n await sql`select 1`.execute(db.db)\n return {}\n },\n })\n}\n"]}
|
|
@@ -45,7 +45,7 @@ const putOp = async (db, op) => {
|
|
|
45
45
|
};
|
|
46
46
|
const validateOp = (req) => {
|
|
47
47
|
try {
|
|
48
|
-
validateNamespace(req.namespace);
|
|
48
|
+
(0, util_1.validateNamespace)(req.namespace);
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
51
51
|
throw new connect_1.ConnectError('operation namespace is invalid NSID', connect_1.Code.InvalidArgument);
|
|
@@ -77,15 +77,4 @@ const validateOp = (req) => {
|
|
|
77
77
|
}
|
|
78
78
|
return req;
|
|
79
79
|
};
|
|
80
|
-
const validateNamespace = (namespace) => {
|
|
81
|
-
const parts = namespace.split('#');
|
|
82
|
-
if (parts.length !== 1 && parts.length !== 2) {
|
|
83
|
-
throw new Error('namespace must be in the format "nsid[#fragment]"');
|
|
84
|
-
}
|
|
85
|
-
const [nsid, fragment] = parts;
|
|
86
|
-
(0, syntax_1.ensureValidNsid)(nsid);
|
|
87
|
-
if (fragment && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fragment)) {
|
|
88
|
-
throw new Error('namespace fragment must be a valid identifier');
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
80
|
//# sourceMappingURL=put-operation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put-operation.js","sourceRoot":"","sources":["../../src/routes/put-operation.ts"],"names":[],"mappings":";;AAAA,iDAAqE;AACrE,mCAA4B;AAC5B,
|
|
1
|
+
{"version":3,"file":"put-operation.js","sourceRoot":"","sources":["../../src/routes/put-operation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'\nimport { sql } from 'kysely'\nimport { ensureValidRecordKey } from '@atproto/syntax'\nimport { AppContext } from '../context'\nimport { Database } from '../db'\nimport { OperationMethod, createOperationChannel } from '../db/schema/operation'\nimport { Service } from '../proto/bsync_connect'\nimport {\n Method,\n PutOperationRequest,\n PutOperationResponse,\n} from '../proto/bsync_pb'\nimport { authWithApiKey } from './auth'\nimport { isValidDid, validateNamespace } from './util'\n\nexport default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({\n async putOperation(req, handlerCtx) {\n authWithApiKey(ctx, handlerCtx)\n const { db } = ctx\n const op = validateOp(req)\n const id = await db.transaction(async (txn) => {\n return putOp(txn, op)\n })\n return new PutOperationResponse({\n operation: {\n id: String(id),\n actorDid: op.actorDid,\n namespace: op.namespace,\n key: op.key,\n method: op.method,\n payload: op.payload,\n },\n })\n },\n})\n\nconst putOp = async (db: Database, op: Operation) => {\n const { ref } = db.db.dynamic\n const { id } = await db.db\n .insertInto('operation')\n .values({\n actorDid: op.actorDid,\n namespace: op.namespace,\n key: op.key,\n method: op.method,\n payload: op.payload,\n })\n .returning('id')\n .executeTakeFirstOrThrow()\n await sql`notify ${ref(createOperationChannel)}`.execute(db.db) // emitted transactionally\n return id\n}\n\nconst validateOp = (req: PutOperationRequest): Operation => {\n try {\n validateNamespace(req.namespace)\n } catch (error) {\n throw new ConnectError(\n 'operation namespace is invalid NSID',\n Code.InvalidArgument,\n )\n }\n\n if (!isValidDid(req.actorDid)) {\n throw new ConnectError(\n 'operation actor_did is invalid DID',\n Code.InvalidArgument,\n )\n }\n\n try {\n ensureValidRecordKey(req.key)\n } catch (error) {\n throw new ConnectError('operation key is required', Code.InvalidArgument)\n }\n\n if (\n req.method !== Method.CREATE &&\n req.method !== Method.UPDATE &&\n req.method !== Method.DELETE\n ) {\n throw new ConnectError('operation method is invalid', Code.InvalidArgument)\n }\n\n if (req.method === Method.CREATE || req.method === Method.UPDATE) {\n try {\n JSON.parse(new TextDecoder().decode(req.payload))\n } catch (error) {\n throw new ConnectError(\n 'payload must be a valid JSON when method is CREATE or UPDATE',\n Code.InvalidArgument,\n )\n }\n }\n\n if (req.method === Method.DELETE && req.payload.length > 0) {\n throw new ConnectError(\n 'cannot specify a payload when method is DELETE',\n Code.InvalidArgument,\n )\n }\n\n return req as Operation\n}\n\ntype Operation = {\n actorDid: string\n namespace: string\n key: string\n payload: Uint8Array\n method: OperationMethod\n}\n"]}
|
package/dist/routes/util.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export declare const validCursor: (cursor: string) => number | null;
|
|
|
2
2
|
export declare const combineSignals: (a: AbortSignal, b: AbortSignal) => AbortSignal;
|
|
3
3
|
export declare const isValidDid: (did: string) => boolean;
|
|
4
4
|
export declare const isValidAtUri: (uri: string) => boolean;
|
|
5
|
+
export declare const validateNamespace: (namespace: string) => void;
|
|
5
6
|
//# sourceMappingURL=util.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/routes/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/routes/util.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,IAOrD,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,GAAG,WAAW,EAAE,GAAG,WAAW,gBAa5D,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,YAUrC,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,YAOvC,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,IAarD,CAAA"}
|
package/dist/routes/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isValidAtUri = exports.isValidDid = exports.combineSignals = exports.validCursor = void 0;
|
|
3
|
+
exports.validateNamespace = exports.isValidAtUri = exports.isValidDid = exports.combineSignals = exports.validCursor = void 0;
|
|
4
4
|
const connect_1 = require("@connectrpc/connect");
|
|
5
5
|
const syntax_1 = require("@atproto/syntax");
|
|
6
6
|
const validCursor = (cursor) => {
|
|
@@ -51,4 +51,16 @@ const isValidAtUri = (uri) => {
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
exports.isValidAtUri = isValidAtUri;
|
|
54
|
+
const validateNamespace = (namespace) => {
|
|
55
|
+
const parts = namespace.split('#');
|
|
56
|
+
if (parts.length !== 1 && parts.length !== 2) {
|
|
57
|
+
throw new Error('namespace must be in the format "nsid[#fragment]"');
|
|
58
|
+
}
|
|
59
|
+
const [nsid, fragment] = parts;
|
|
60
|
+
(0, syntax_1.ensureValidNsid)(nsid);
|
|
61
|
+
if (fragment && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fragment)) {
|
|
62
|
+
throw new Error('namespace fragment must be a valid identifier');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.validateNamespace = validateNamespace;
|
|
54
66
|
//# sourceMappingURL=util.js.map
|
package/dist/routes/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/routes/util.ts"],"names":[],"mappings":";;;AAAA,iDAAwD;AACxD,
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/routes/util.ts"],"names":[],"mappings":";;;AAAA,iDAAwD;AACxD,4CAKwB;AAEjB,MAAM,WAAW,GAAG,CAAC,MAAc,EAAiB,EAAE;IAC3D,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAChC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,sBAAY,CAAC,gBAAgB,EAAE,cAAI,CAAC,eAAe,CAAC,CAAA;IAChE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAPY,QAAA,WAAW,eAOvB;AAEM,MAAM,cAAc,GAAG,CAAC,CAAc,EAAE,CAAc,EAAE,EAAE;IAC/D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,KAAK,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAC5B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,EAAE,CAAA;YAClB,OAAO,MAAM,CAAA;QACf,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;YACtE,2EAA2E;YAC3E,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAA;AAC1B,CAAC,CAAA;AAbY,QAAA,cAAc,kBAa1B;AAEM,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,EAAE;IACxC,IAAI,CAAC;QACH,IAAA,uBAAc,EAAC,GAAG,CAAC,CAAA;QACnB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,wBAAe,EAAE,CAAC;YACnC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC,CAAA;AAVY,QAAA,UAAU,cAUtB;AAEM,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,IAAA,yBAAgB,EAAC,GAAG,CAAC,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC,CAAA;AAPY,QAAA,YAAY,gBAOxB;AAEM,MAAM,iBAAiB,GAAG,CAAC,SAAiB,EAAQ,EAAE;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAElC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IACtE,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAA;IAE9B,IAAA,wBAAe,EAAC,IAAI,CAAC,CAAA;IACrB,IAAI,QAAQ,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;AACH,CAAC,CAAA;AAbY,QAAA,iBAAiB,qBAa7B","sourcesContent":["import { Code, ConnectError } from '@connectrpc/connect'\nimport {\n InvalidDidError,\n ensureValidAtUri,\n ensureValidDid,\n ensureValidNsid,\n} from '@atproto/syntax'\n\nexport const validCursor = (cursor: string): number | null => {\n if (cursor === '') return null\n const int = parseInt(cursor, 10)\n if (isNaN(int) || int < 0) {\n throw new ConnectError('invalid cursor', Code.InvalidArgument)\n }\n return int\n}\n\nexport const combineSignals = (a: AbortSignal, b: AbortSignal) => {\n const controller = new AbortController()\n for (const signal of [a, b]) {\n if (signal.aborted) {\n controller.abort()\n return signal\n }\n signal.addEventListener('abort', () => controller.abort(signal.reason), {\n // @ts-ignore https://github.com/DefinitelyTyped/DefinitelyTyped/pull/68625\n signal: controller.signal,\n })\n }\n return controller.signal\n}\n\nexport const isValidDid = (did: string) => {\n try {\n ensureValidDid(did)\n return true\n } catch (err) {\n if (err instanceof InvalidDidError) {\n return false\n }\n throw err\n }\n}\n\nexport const isValidAtUri = (uri: string) => {\n try {\n ensureValidAtUri(uri)\n return true\n } catch {\n return false\n }\n}\n\nexport const validateNamespace = (namespace: string): void => {\n const parts = namespace.split('#')\n\n if (parts.length !== 1 && parts.length !== 2) {\n throw new Error('namespace must be in the format \"nsid[#fragment]\"')\n }\n\n const [nsid, fragment] = parts\n\n ensureValidNsid(nsid)\n if (fragment && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fragment)) {\n throw new Error('namespace fragment must be a valid identifier')\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/bsync",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Sychronizing service for app.bsky App View (Bluesky API)",
|
|
6
6
|
"keywords": [
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"pg": "^8.10.0",
|
|
28
28
|
"pino-http": "^8.2.1",
|
|
29
29
|
"typed-emitter": "^2.1.0",
|
|
30
|
-
"@atproto/common": "^0.5.
|
|
31
|
-
"@atproto/syntax": "^0.
|
|
30
|
+
"@atproto/common": "^0.5.14",
|
|
31
|
+
"@atproto/syntax": "^0.5.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@bufbuild/buf": "^1.28.1",
|
package/proto/bsync.proto
CHANGED
|
@@ -105,6 +105,15 @@ message ScanOperationsResponse {
|
|
|
105
105
|
string cursor = 2;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
message DeleteOperationsByActorAndNamespaceRequest {
|
|
109
|
+
string actor_did = 1;
|
|
110
|
+
string namespace = 2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
message DeleteOperationsByActorAndNamespaceResponse {
|
|
114
|
+
int32 deleted_count = 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
|
|
109
118
|
// Ping
|
|
110
119
|
message PingRequest {}
|
|
@@ -119,6 +128,7 @@ service Service {
|
|
|
119
128
|
rpc ScanNotifOperations(ScanNotifOperationsRequest) returns (ScanNotifOperationsResponse);
|
|
120
129
|
rpc PutOperation(PutOperationRequest) returns (PutOperationResponse);
|
|
121
130
|
rpc ScanOperations(ScanOperationsRequest) returns (ScanOperationsResponse);
|
|
131
|
+
rpc DeleteOperationsByActorAndNamespace(DeleteOperationsByActorAndNamespaceRequest) returns (DeleteOperationsByActorAndNamespaceResponse);
|
|
122
132
|
// Ping
|
|
123
133
|
rpc Ping(PingRequest) returns (PingResponse);
|
|
124
134
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
// @ts-nocheck
|
|
5
5
|
|
|
6
|
-
import { AddMuteOperationRequest, AddMuteOperationResponse, AddNotifOperationRequest, AddNotifOperationResponse, PingRequest, PingResponse, PutOperationRequest, PutOperationResponse, ScanMuteOperationsRequest, ScanMuteOperationsResponse, ScanNotifOperationsRequest, ScanNotifOperationsResponse, ScanOperationsRequest, ScanOperationsResponse } from "./bsync_pb";
|
|
6
|
+
import { AddMuteOperationRequest, AddMuteOperationResponse, AddNotifOperationRequest, AddNotifOperationResponse, DeleteOperationsByActorAndNamespaceRequest, DeleteOperationsByActorAndNamespaceResponse, PingRequest, PingResponse, PutOperationRequest, PutOperationResponse, ScanMuteOperationsRequest, ScanMuteOperationsResponse, ScanNotifOperationsRequest, ScanNotifOperationsResponse, ScanOperationsRequest, ScanOperationsResponse } from "./bsync_pb";
|
|
7
7
|
import { MethodKind } from "@bufbuild/protobuf";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -68,6 +68,15 @@ export const Service = {
|
|
|
68
68
|
O: ScanOperationsResponse,
|
|
69
69
|
kind: MethodKind.Unary,
|
|
70
70
|
},
|
|
71
|
+
/**
|
|
72
|
+
* @generated from rpc bsync.Service.DeleteOperationsByActorAndNamespace
|
|
73
|
+
*/
|
|
74
|
+
deleteOperationsByActorAndNamespace: {
|
|
75
|
+
name: "DeleteOperationsByActorAndNamespace",
|
|
76
|
+
I: DeleteOperationsByActorAndNamespaceRequest,
|
|
77
|
+
O: DeleteOperationsByActorAndNamespaceResponse,
|
|
78
|
+
kind: MethodKind.Unary,
|
|
79
|
+
},
|
|
71
80
|
/**
|
|
72
81
|
* Ping
|
|
73
82
|
*
|
package/src/proto/bsync_pb.ts
CHANGED
|
@@ -763,6 +763,86 @@ export class ScanOperationsResponse extends Message<ScanOperationsResponse> {
|
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
+
/**
|
|
767
|
+
* @generated from message bsync.DeleteOperationsByActorAndNamespaceRequest
|
|
768
|
+
*/
|
|
769
|
+
export class DeleteOperationsByActorAndNamespaceRequest extends Message<DeleteOperationsByActorAndNamespaceRequest> {
|
|
770
|
+
/**
|
|
771
|
+
* @generated from field: string actor_did = 1;
|
|
772
|
+
*/
|
|
773
|
+
actorDid = "";
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* @generated from field: string namespace = 2;
|
|
777
|
+
*/
|
|
778
|
+
namespace = "";
|
|
779
|
+
|
|
780
|
+
constructor(data?: PartialMessage<DeleteOperationsByActorAndNamespaceRequest>) {
|
|
781
|
+
super();
|
|
782
|
+
proto3.util.initPartial(data, this);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
static readonly runtime: typeof proto3 = proto3;
|
|
786
|
+
static readonly typeName = "bsync.DeleteOperationsByActorAndNamespaceRequest";
|
|
787
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
788
|
+
{ no: 1, name: "actor_did", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
789
|
+
{ no: 2, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
|
790
|
+
]);
|
|
791
|
+
|
|
792
|
+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteOperationsByActorAndNamespaceRequest {
|
|
793
|
+
return new DeleteOperationsByActorAndNamespaceRequest().fromBinary(bytes, options);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteOperationsByActorAndNamespaceRequest {
|
|
797
|
+
return new DeleteOperationsByActorAndNamespaceRequest().fromJson(jsonValue, options);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteOperationsByActorAndNamespaceRequest {
|
|
801
|
+
return new DeleteOperationsByActorAndNamespaceRequest().fromJsonString(jsonString, options);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
static equals(a: DeleteOperationsByActorAndNamespaceRequest | PlainMessage<DeleteOperationsByActorAndNamespaceRequest> | undefined, b: DeleteOperationsByActorAndNamespaceRequest | PlainMessage<DeleteOperationsByActorAndNamespaceRequest> | undefined): boolean {
|
|
805
|
+
return proto3.util.equals(DeleteOperationsByActorAndNamespaceRequest, a, b);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* @generated from message bsync.DeleteOperationsByActorAndNamespaceResponse
|
|
811
|
+
*/
|
|
812
|
+
export class DeleteOperationsByActorAndNamespaceResponse extends Message<DeleteOperationsByActorAndNamespaceResponse> {
|
|
813
|
+
/**
|
|
814
|
+
* @generated from field: int32 deleted_count = 1;
|
|
815
|
+
*/
|
|
816
|
+
deletedCount = 0;
|
|
817
|
+
|
|
818
|
+
constructor(data?: PartialMessage<DeleteOperationsByActorAndNamespaceResponse>) {
|
|
819
|
+
super();
|
|
820
|
+
proto3.util.initPartial(data, this);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
static readonly runtime: typeof proto3 = proto3;
|
|
824
|
+
static readonly typeName = "bsync.DeleteOperationsByActorAndNamespaceResponse";
|
|
825
|
+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
|
826
|
+
{ no: 1, name: "deleted_count", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
|
|
827
|
+
]);
|
|
828
|
+
|
|
829
|
+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteOperationsByActorAndNamespaceResponse {
|
|
830
|
+
return new DeleteOperationsByActorAndNamespaceResponse().fromBinary(bytes, options);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteOperationsByActorAndNamespaceResponse {
|
|
834
|
+
return new DeleteOperationsByActorAndNamespaceResponse().fromJson(jsonValue, options);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteOperationsByActorAndNamespaceResponse {
|
|
838
|
+
return new DeleteOperationsByActorAndNamespaceResponse().fromJsonString(jsonString, options);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
static equals(a: DeleteOperationsByActorAndNamespaceResponse | PlainMessage<DeleteOperationsByActorAndNamespaceResponse> | undefined, b: DeleteOperationsByActorAndNamespaceResponse | PlainMessage<DeleteOperationsByActorAndNamespaceResponse> | undefined): boolean {
|
|
842
|
+
return proto3.util.equals(DeleteOperationsByActorAndNamespaceResponse, a, b);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
766
846
|
/**
|
|
767
847
|
* Ping
|
|
768
848
|
*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
|
+
import { AppContext } from '../context'
|
|
3
|
+
import { Service } from '../proto/bsync_connect'
|
|
4
|
+
import { DeleteOperationsByActorAndNamespaceResponse } from '../proto/bsync_pb'
|
|
5
|
+
import { authWithApiKey } from './auth'
|
|
6
|
+
import { isValidDid, validateNamespace } from './util'
|
|
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
CHANGED
|
@@ -4,6 +4,7 @@ import { AppContext } from '../context'
|
|
|
4
4
|
import { Service } from '../proto/bsync_connect'
|
|
5
5
|
import addMuteOperation from './add-mute-operation'
|
|
6
6
|
import addNotifOperation from './add-notif-operation'
|
|
7
|
+
import deleteOperations from './delete-operations'
|
|
7
8
|
import putOperation from './put-operation'
|
|
8
9
|
import scanMuteOperations from './scan-mute-operations'
|
|
9
10
|
import scanNotifOperations from './scan-notif-operations'
|
|
@@ -17,6 +18,7 @@ export default (ctx: AppContext) => (router: ConnectRouter) => {
|
|
|
17
18
|
...scanNotifOperations(ctx),
|
|
18
19
|
...putOperation(ctx),
|
|
19
20
|
...scanOperations(ctx),
|
|
21
|
+
...deleteOperations(ctx),
|
|
20
22
|
async ping() {
|
|
21
23
|
const { db } = ctx
|
|
22
24
|
await sql`select 1`.execute(db.db)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
|
|
2
2
|
import { sql } from 'kysely'
|
|
3
|
-
import {
|
|
3
|
+
import { ensureValidRecordKey } from '@atproto/syntax'
|
|
4
4
|
import { AppContext } from '../context'
|
|
5
5
|
import { Database } from '../db'
|
|
6
6
|
import { OperationMethod, createOperationChannel } from '../db/schema/operation'
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
PutOperationResponse,
|
|
12
12
|
} from '../proto/bsync_pb'
|
|
13
13
|
import { authWithApiKey } from './auth'
|
|
14
|
-
import { isValidDid } from './util'
|
|
14
|
+
import { isValidDid, validateNamespace } from './util'
|
|
15
15
|
|
|
16
16
|
export default (ctx: AppContext): Partial<ServiceImpl<typeof Service>> => ({
|
|
17
17
|
async putOperation(req, handlerCtx) {
|
|
@@ -103,21 +103,6 @@ const validateOp = (req: PutOperationRequest): Operation => {
|
|
|
103
103
|
return req as Operation
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
const validateNamespace = (namespace: string): void => {
|
|
107
|
-
const parts = namespace.split('#')
|
|
108
|
-
|
|
109
|
-
if (parts.length !== 1 && parts.length !== 2) {
|
|
110
|
-
throw new Error('namespace must be in the format "nsid[#fragment]"')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const [nsid, fragment] = parts
|
|
114
|
-
|
|
115
|
-
ensureValidNsid(nsid)
|
|
116
|
-
if (fragment && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fragment)) {
|
|
117
|
-
throw new Error('namespace fragment must be a valid identifier')
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
106
|
type Operation = {
|
|
122
107
|
actorDid: string
|
|
123
108
|
namespace: string
|
package/src/routes/util.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
InvalidDidError,
|
|
4
4
|
ensureValidAtUri,
|
|
5
5
|
ensureValidDid,
|
|
6
|
+
ensureValidNsid,
|
|
6
7
|
} from '@atproto/syntax'
|
|
7
8
|
|
|
8
9
|
export const validCursor = (cursor: string): number | null => {
|
|
@@ -49,3 +50,18 @@ export const isValidAtUri = (uri: string) => {
|
|
|
49
50
|
return false
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
export const validateNamespace = (namespace: string): void => {
|
|
55
|
+
const parts = namespace.split('#')
|
|
56
|
+
|
|
57
|
+
if (parts.length !== 1 && parts.length !== 2) {
|
|
58
|
+
throw new Error('namespace must be in the format "nsid[#fragment]"')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const [nsid, fragment] = parts
|
|
62
|
+
|
|
63
|
+
ensureValidNsid(nsid)
|
|
64
|
+
if (fragment && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(fragment)) {
|
|
65
|
+
throw new Error('namespace fragment must be a valid identifier')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import getPort from 'get-port'
|
|
2
|
+
import {
|
|
3
|
+
BsyncClient,
|
|
4
|
+
BsyncService,
|
|
5
|
+
Database,
|
|
6
|
+
authWithApiKey,
|
|
7
|
+
createClient,
|
|
8
|
+
envToCfg,
|
|
9
|
+
} from '../src'
|
|
10
|
+
import { Method } from '../src/proto/bsync_pb'
|
|
11
|
+
|
|
12
|
+
describe('operations', () => {
|
|
13
|
+
let bsync: BsyncService
|
|
14
|
+
let client: BsyncClient
|
|
15
|
+
|
|
16
|
+
const validPayload0 = Buffer.from(JSON.stringify({ value: 0 }))
|
|
17
|
+
const validPayload1 = Buffer.from(JSON.stringify({ value: 1 }))
|
|
18
|
+
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
bsync = await BsyncService.create(
|
|
21
|
+
envToCfg({
|
|
22
|
+
port: await getPort(),
|
|
23
|
+
dbUrl: process.env.DB_POSTGRES_URL,
|
|
24
|
+
dbSchema: 'bsync_delete_operations',
|
|
25
|
+
apiKeys: ['key-1'],
|
|
26
|
+
longPollTimeoutMs: 500,
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
await bsync.ctx.db.migrateToLatestOrThrow()
|
|
30
|
+
await bsync.start()
|
|
31
|
+
client = createClient({
|
|
32
|
+
httpVersion: '1.1',
|
|
33
|
+
baseUrl: `http://localhost:${bsync.ctx.cfg.service.port}`,
|
|
34
|
+
interceptors: [authWithApiKey('key-1')],
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
afterAll(async () => {
|
|
39
|
+
await bsync.destroy()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
await clearOps(bsync.ctx.db)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('deletes', async () => {
|
|
47
|
+
const res1 = await client.putOperation({
|
|
48
|
+
actorDid: 'did:example:a',
|
|
49
|
+
namespace: 'app.bsky.some.col',
|
|
50
|
+
key: 'key1',
|
|
51
|
+
method: Method.CREATE,
|
|
52
|
+
payload: validPayload0,
|
|
53
|
+
})
|
|
54
|
+
const res2 = await client.putOperation({
|
|
55
|
+
actorDid: 'did:example:a',
|
|
56
|
+
namespace: 'app.bsky.other.col#id',
|
|
57
|
+
key: 'key1',
|
|
58
|
+
method: Method.UPDATE,
|
|
59
|
+
payload: validPayload1,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(res1.operation?.id).toBe('1')
|
|
63
|
+
expect(res2.operation?.id).toBe('2')
|
|
64
|
+
expect(await dumpOps(bsync.ctx.db)).toStrictEqual([
|
|
65
|
+
{
|
|
66
|
+
id: 1,
|
|
67
|
+
actorDid: 'did:example:a',
|
|
68
|
+
namespace: 'app.bsky.some.col',
|
|
69
|
+
key: 'key1',
|
|
70
|
+
method: Method.CREATE,
|
|
71
|
+
payload: validPayload0,
|
|
72
|
+
createdAt: expect.any(Date),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 2,
|
|
76
|
+
actorDid: 'did:example:a',
|
|
77
|
+
namespace: 'app.bsky.other.col#id',
|
|
78
|
+
key: 'key1',
|
|
79
|
+
method: Method.UPDATE,
|
|
80
|
+
payload: validPayload1,
|
|
81
|
+
createdAt: expect.any(Date),
|
|
82
|
+
},
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
await client.deleteOperationsByActorAndNamespace({
|
|
86
|
+
actorDid: 'did:example:a',
|
|
87
|
+
namespace: 'app.bsky.some.col',
|
|
88
|
+
})
|
|
89
|
+
await client.deleteOperationsByActorAndNamespace({
|
|
90
|
+
actorDid: 'did:example:a',
|
|
91
|
+
namespace: 'app.bsky.other.col#id',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
expect(await dumpOps(bsync.ctx.db)).toStrictEqual([])
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const dumpOps = async (db: Database) => {
|
|
99
|
+
return db.db
|
|
100
|
+
.selectFrom('operation')
|
|
101
|
+
.selectAll()
|
|
102
|
+
.orderBy('id', 'asc')
|
|
103
|
+
.execute()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const clearOps = async (db: Database) => {
|
|
107
|
+
await db.db.deleteFrom('operation').execute()
|
|
108
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/client.ts","./src/config.ts","./src/context.ts","./src/index.ts","./src/logger.ts","./src/db/index.ts","./src/db/types.ts","./src/db/migrations/20240108T220751294Z-init.ts","./src/db/migrations/20240717T224303472Z-notif-ops.ts","./src/db/migrations/20250527T022203400Z-add-operation.ts","./src/db/migrations/20250603T163446567Z-alter-operation.ts","./src/db/migrations/index.ts","./src/db/migrations/provider.ts","./src/db/schema/index.ts","./src/db/schema/mute_item.ts","./src/db/schema/mute_op.ts","./src/db/schema/notif_item.ts","./src/db/schema/notif_op.ts","./src/db/schema/operation.ts","./src/proto/bsync_connect.ts","./src/proto/bsync_pb.ts","./src/routes/add-mute-operation.ts","./src/routes/add-notif-operation.ts","./src/routes/auth.ts","./src/routes/index.ts","./src/routes/put-operation.ts","./src/routes/scan-mute-operations.ts","./src/routes/scan-notif-operations.ts","./src/routes/scan-operations.ts","./src/routes/util.ts"],"version":"5.8.2"}
|
|
1
|
+
{"root":["./src/client.ts","./src/config.ts","./src/context.ts","./src/index.ts","./src/logger.ts","./src/db/index.ts","./src/db/types.ts","./src/db/migrations/20240108T220751294Z-init.ts","./src/db/migrations/20240717T224303472Z-notif-ops.ts","./src/db/migrations/20250527T022203400Z-add-operation.ts","./src/db/migrations/20250603T163446567Z-alter-operation.ts","./src/db/migrations/index.ts","./src/db/migrations/provider.ts","./src/db/schema/index.ts","./src/db/schema/mute_item.ts","./src/db/schema/mute_op.ts","./src/db/schema/notif_item.ts","./src/db/schema/notif_op.ts","./src/db/schema/operation.ts","./src/proto/bsync_connect.ts","./src/proto/bsync_pb.ts","./src/routes/add-mute-operation.ts","./src/routes/add-notif-operation.ts","./src/routes/auth.ts","./src/routes/delete-operations.ts","./src/routes/index.ts","./src/routes/put-operation.ts","./src/routes/scan-mute-operations.ts","./src/routes/scan-notif-operations.ts","./src/routes/scan-operations.ts","./src/routes/util.ts"],"version":"5.8.2"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./tests/mutes.test.ts","./tests/notifications.test.ts","./tests/operations.test.ts"],"version":"5.8.3"}
|