@clioplaylists/clio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.env +7 -0
- package/dist/api/com/clioplaylists/alpha/actor/getProfile.js +37 -0
- package/dist/api/com/clioplaylists/alpha/feed/getSongs.js +37 -0
- package/dist/api/health.js +32 -0
- package/dist/api/index.js +43 -0
- package/dist/api/util.js +17 -0
- package/dist/auth-verifier.js +473 -0
- package/dist/client.js +40 -0
- package/dist/config.js +65 -0
- package/dist/context.js +19 -0
- package/dist/dataplane/bsync/index.js +150 -0
- package/dist/dataplane/client.js +165 -0
- package/dist/dataplane/index.js +18 -0
- package/dist/dataplane/server/background.js +51 -0
- package/dist/dataplane/server/db/database-schema.js +2 -0
- package/dist/dataplane/server/db/db.js +228 -0
- package/dist/dataplane/server/db/index.js +17 -0
- package/dist/dataplane/server/db/migrations/20230309T045948368Z-init.js +117 -0
- package/dist/dataplane/server/db/migrations/20230420T211446071Z-did-cache.js +15 -0
- package/dist/dataplane/server/db/migrations/index.js +41 -0
- package/dist/dataplane/server/db/migrations/provider.js +31 -0
- package/dist/dataplane/server/db/pagination.js +144 -0
- package/dist/dataplane/server/db/tables/actor-sync.js +4 -0
- package/dist/dataplane/server/db/tables/actor.js +4 -0
- package/dist/dataplane/server/db/tables/artist-list-item.js +4 -0
- package/dist/dataplane/server/db/tables/artist.js +4 -0
- package/dist/dataplane/server/db/tables/playlist-idea.js +4 -0
- package/dist/dataplane/server/db/tables/playlist-item.js +4 -0
- package/dist/dataplane/server/db/tables/playlist.js +4 -0
- package/dist/dataplane/server/db/tables/profile.js +4 -0
- package/dist/dataplane/server/db/tables/record.js +4 -0
- package/dist/dataplane/server/db/tables/song.js +4 -0
- package/dist/dataplane/server/db/types.js +2 -0
- package/dist/dataplane/server/db/util.js +48 -0
- package/dist/dataplane/server/index.js +52 -0
- package/dist/dataplane/server/indexing/index.js +321 -0
- package/dist/dataplane/server/indexing/plugins/playlist-idea.js +163 -0
- package/dist/dataplane/server/indexing/plugins/profile.js +81 -0
- package/dist/dataplane/server/indexing/processor.js +90 -0
- package/dist/dataplane/server/routes/blocks.js +95 -0
- package/dist/dataplane/server/routes/feed-gens.js +56 -0
- package/dist/dataplane/server/routes/feeds.js +128 -0
- package/dist/dataplane/server/routes/follows.js +122 -0
- package/dist/dataplane/server/routes/identity.js +56 -0
- package/dist/dataplane/server/routes/index.js +19 -0
- package/dist/dataplane/server/routes/interactions.js +111 -0
- package/dist/dataplane/server/routes/labels.js +73 -0
- package/dist/dataplane/server/routes/likes.js +76 -0
- package/dist/dataplane/server/routes/lists.js +77 -0
- package/dist/dataplane/server/routes/moderation.js +92 -0
- package/dist/dataplane/server/routes/mutes.js +166 -0
- package/dist/dataplane/server/routes/notifs.js +137 -0
- package/dist/dataplane/server/routes/posts.js +19 -0
- package/dist/dataplane/server/routes/profile.js +61 -0
- package/dist/dataplane/server/routes/quotes.js +26 -0
- package/dist/dataplane/server/routes/records.js +88 -0
- package/dist/dataplane/server/routes/relationships.js +157 -0
- package/dist/dataplane/server/routes/reposts.js +59 -0
- package/dist/dataplane/server/routes/search.js +70 -0
- package/dist/dataplane/server/routes/starter-packs.js +24 -0
- package/dist/dataplane/server/routes/suggestions.js +134 -0
- package/dist/dataplane/server/routes/sync.js +14 -0
- package/dist/dataplane/server/routes/threads.js +31 -0
- package/dist/dataplane/server/subscription.js +114 -0
- package/dist/dataplane/server/util.js +117 -0
- package/dist/error.js +14 -0
- package/dist/index.js +115 -0
- package/dist/lexicons/index.js +638 -0
- package/dist/lexicons/lexicons.js +4551 -0
- package/dist/lexicons/types/com/atproto/admin/defs.js +54 -0
- package/dist/lexicons/types/com/atproto/admin/deleteAccount.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/disableAccountInvites.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/disableInviteCodes.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/enableAccountInvites.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/getAccountInfo.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/getAccountInfos.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/getInviteCodes.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/getSubjectStatus.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/searchAccounts.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/sendEmail.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/updateAccountEmail.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/updateAccountHandle.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/updateAccountPassword.js +2 -0
- package/dist/lexicons/types/com/atproto/admin/updateSubjectStatus.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/defs.js +14 -0
- package/dist/lexicons/types/com/atproto/identity/getRecommendedDidCredentials.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/refreshIdentity.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/requestPlcOperationSignature.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/resolveDid.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/resolveHandle.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/resolveIdentity.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/signPlcOperation.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/submitPlcOperation.js +2 -0
- package/dist/lexicons/types/com/atproto/identity/updateHandle.js +2 -0
- package/dist/lexicons/types/com/atproto/label/defs.js +54 -0
- package/dist/lexicons/types/com/atproto/label/queryLabels.js +2 -0
- package/dist/lexicons/types/com/atproto/label/subscribeLabels.js +24 -0
- package/dist/lexicons/types/com/atproto/lexicon/schema.js +15 -0
- package/dist/lexicons/types/com/atproto/moderation/createReport.js +2 -0
- package/dist/lexicons/types/com/atproto/moderation/defs.js +20 -0
- package/dist/lexicons/types/com/atproto/repo/applyWrites.js +64 -0
- package/dist/lexicons/types/com/atproto/repo/createRecord.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/defs.js +14 -0
- package/dist/lexicons/types/com/atproto/repo/deleteRecord.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/describeRepo.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/getRecord.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/importRepo.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/listMissingBlobs.js +14 -0
- package/dist/lexicons/types/com/atproto/repo/listRecords.js +14 -0
- package/dist/lexicons/types/com/atproto/repo/putRecord.js +2 -0
- package/dist/lexicons/types/com/atproto/repo/strongRef.js +15 -0
- package/dist/lexicons/types/com/atproto/repo/uploadBlob.js +2 -0
- package/dist/lexicons/types/com/atproto/server/activateAccount.js +2 -0
- package/dist/lexicons/types/com/atproto/server/checkAccountStatus.js +2 -0
- package/dist/lexicons/types/com/atproto/server/confirmEmail.js +2 -0
- package/dist/lexicons/types/com/atproto/server/createAccount.js +2 -0
- package/dist/lexicons/types/com/atproto/server/createAppPassword.js +14 -0
- package/dist/lexicons/types/com/atproto/server/createInviteCode.js +2 -0
- package/dist/lexicons/types/com/atproto/server/createInviteCodes.js +14 -0
- package/dist/lexicons/types/com/atproto/server/createSession.js +2 -0
- package/dist/lexicons/types/com/atproto/server/deactivateAccount.js +2 -0
- package/dist/lexicons/types/com/atproto/server/defs.js +24 -0
- package/dist/lexicons/types/com/atproto/server/deleteAccount.js +2 -0
- package/dist/lexicons/types/com/atproto/server/deleteSession.js +2 -0
- package/dist/lexicons/types/com/atproto/server/describeServer.js +24 -0
- package/dist/lexicons/types/com/atproto/server/getAccountInviteCodes.js +2 -0
- package/dist/lexicons/types/com/atproto/server/getServiceAuth.js +2 -0
- package/dist/lexicons/types/com/atproto/server/getSession.js +2 -0
- package/dist/lexicons/types/com/atproto/server/listAppPasswords.js +14 -0
- package/dist/lexicons/types/com/atproto/server/refreshSession.js +2 -0
- package/dist/lexicons/types/com/atproto/server/requestAccountDelete.js +2 -0
- package/dist/lexicons/types/com/atproto/server/requestEmailConfirmation.js +2 -0
- package/dist/lexicons/types/com/atproto/server/requestEmailUpdate.js +2 -0
- package/dist/lexicons/types/com/atproto/server/requestPasswordReset.js +2 -0
- package/dist/lexicons/types/com/atproto/server/reserveSigningKey.js +2 -0
- package/dist/lexicons/types/com/atproto/server/resetPassword.js +2 -0
- package/dist/lexicons/types/com/atproto/server/revokeAppPassword.js +2 -0
- package/dist/lexicons/types/com/atproto/server/updateEmail.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getBlob.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getBlocks.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getCheckout.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getHead.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getLatestCommit.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getRecord.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getRepo.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/getRepoStatus.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/listBlobs.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/listRepos.js +14 -0
- package/dist/lexicons/types/com/atproto/sync/listReposByCollection.js +14 -0
- package/dist/lexicons/types/com/atproto/sync/notifyOfUpdate.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/requestCrawl.js +2 -0
- package/dist/lexicons/types/com/atproto/sync/subscribeRepos.js +64 -0
- package/dist/lexicons/types/com/atproto/temp/addReservedHandle.js +2 -0
- package/dist/lexicons/types/com/atproto/temp/checkSignupQueue.js +2 -0
- package/dist/lexicons/types/com/atproto/temp/fetchLabels.js +2 -0
- package/dist/lexicons/types/com/atproto/temp/requestPhoneVerification.js +2 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/actor/profile.js +15 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/defs.js +24 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/getSongs.js +2 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/playlistIdea.js +35 -0
- package/dist/lexicons/types/com/clioplaylists/alpha/feed/song.js +25 -0
- package/dist/lexicons/util.js +13 -0
- package/dist/logger.js +26 -0
- package/dist/rpc/clio_connect.js +110 -0
- package/dist/rpc/clio_pb.js +1365 -0
- package/dist/start.js +13 -0
- package/dist/util/retry.js +16 -0
- package/dist/util/uris.js +7 -0
- package/dist/util.js +119 -0
- package/package.json +73 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RecordProcessor = void 0;
|
|
4
|
+
const lexicon_1 = require("@atproto/lexicon");
|
|
5
|
+
const lexicons_1 = require("../../../lexicons/lexicons");
|
|
6
|
+
class RecordProcessor {
|
|
7
|
+
constructor(appDb, params) {
|
|
8
|
+
Object.defineProperty(this, "params", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: params
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "collection", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "db", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
this.db = appDb.db;
|
|
27
|
+
this.collection = this.params.lexId;
|
|
28
|
+
}
|
|
29
|
+
matchesSchema(obj) {
|
|
30
|
+
try {
|
|
31
|
+
this.assertValidRecord(obj);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
assertValidRecord(obj) {
|
|
39
|
+
lexicons_1.lexicons.assertValidRecord(this.params.lexId, obj);
|
|
40
|
+
}
|
|
41
|
+
async insertRecord(uri, cid, obj, timestamp) {
|
|
42
|
+
this.assertValidRecord(obj);
|
|
43
|
+
await this.db
|
|
44
|
+
.insertInto('record')
|
|
45
|
+
.values({
|
|
46
|
+
uri: uri.toString(),
|
|
47
|
+
cid: cid.toString(),
|
|
48
|
+
did: uri.host,
|
|
49
|
+
json: (0, lexicon_1.stringifyLex)(obj),
|
|
50
|
+
indexed_at: timestamp,
|
|
51
|
+
})
|
|
52
|
+
.onConflict((oc) => oc.doNothing())
|
|
53
|
+
.execute();
|
|
54
|
+
await this.params.insertFn(this.db, uri, cid, obj, timestamp);
|
|
55
|
+
}
|
|
56
|
+
// Currently using a very simple strategy for updates: purge the existing index
|
|
57
|
+
// for the uri then replace it. The main upside is that this allows the indexer
|
|
58
|
+
// for each collection to avoid bespoke logic for in-place updates, which isn't
|
|
59
|
+
// straightforward in the general case. We still get nice control over notifications.
|
|
60
|
+
async updateRecord(uri, cid, obj, timestamp) {
|
|
61
|
+
this.assertValidRecord(obj);
|
|
62
|
+
await this.db
|
|
63
|
+
.updateTable('record')
|
|
64
|
+
.where('uri', '=', uri.toString())
|
|
65
|
+
.set({
|
|
66
|
+
cid: cid.toString(),
|
|
67
|
+
json: (0, lexicon_1.stringifyLex)(obj),
|
|
68
|
+
indexed_at: timestamp,
|
|
69
|
+
})
|
|
70
|
+
.execute();
|
|
71
|
+
const deleted = await this.params.deleteFn(this.db, uri);
|
|
72
|
+
if (!deleted) {
|
|
73
|
+
// If a record was updated but hadn't been indexed yet, treat it like a plain insert.
|
|
74
|
+
return this.insertRecord(uri, cid, obj, timestamp);
|
|
75
|
+
}
|
|
76
|
+
const inserted = await this.params.insertFn(this.db, uri, cid, obj, timestamp);
|
|
77
|
+
if (!inserted) {
|
|
78
|
+
throw new Error('Record update failed: removed from index but could not be replaced');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async deleteRecord(uri, cascading = false) {
|
|
82
|
+
await this.db
|
|
83
|
+
.deleteFrom('record')
|
|
84
|
+
.where('uri', '=', uri.toString())
|
|
85
|
+
.execute();
|
|
86
|
+
await this.params.deleteFn(this.db, uri);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.RecordProcessor = RecordProcessor;
|
|
90
|
+
exports.default = RecordProcessor;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const pagination_1 = require("../db/pagination");
|
|
4
|
+
exports.default = (db) => ({
|
|
5
|
+
async getBidirectionalBlock(req) {
|
|
6
|
+
const { actorDid, targetDid } = req;
|
|
7
|
+
const res = await db.db
|
|
8
|
+
.selectFrom('actor_block')
|
|
9
|
+
.where((qb) => qb
|
|
10
|
+
.where('actor_block.creator', '=', actorDid)
|
|
11
|
+
.where('actor_block.subjectDid', '=', targetDid))
|
|
12
|
+
.orWhere((qb) => qb
|
|
13
|
+
.where('actor_block.creator', '=', targetDid)
|
|
14
|
+
.where('actor_block.subjectDid', '=', actorDid))
|
|
15
|
+
.limit(1)
|
|
16
|
+
.selectAll()
|
|
17
|
+
.executeTakeFirst();
|
|
18
|
+
return {
|
|
19
|
+
blockUri: res?.uri,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
async getBlocks(req) {
|
|
23
|
+
const { actorDid, cursor, limit } = req;
|
|
24
|
+
const { ref } = db.db.dynamic;
|
|
25
|
+
let builder = db.db
|
|
26
|
+
.selectFrom('actor_block')
|
|
27
|
+
.where('actor_block.creator', '=', actorDid)
|
|
28
|
+
.selectAll();
|
|
29
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('actor_block.sortAt'), ref('actor_block.cid'));
|
|
30
|
+
builder = (0, pagination_1.paginate)(builder, {
|
|
31
|
+
limit,
|
|
32
|
+
cursor,
|
|
33
|
+
keyset,
|
|
34
|
+
});
|
|
35
|
+
const blocks = await builder.execute();
|
|
36
|
+
return {
|
|
37
|
+
blockUris: blocks.map((b) => b.uri),
|
|
38
|
+
cursor: keyset.packFromResult(blocks),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
async getBidirectionalBlockViaList(req) {
|
|
42
|
+
const { actorDid, targetDid } = req;
|
|
43
|
+
const res = await db.db
|
|
44
|
+
.selectFrom('list_block')
|
|
45
|
+
.innerJoin('list_item', 'list_item.listUri', 'list_block.subjectUri')
|
|
46
|
+
.where((qb) => qb
|
|
47
|
+
.where('list_block.creator', '=', actorDid)
|
|
48
|
+
.where('list_item.subjectDid', '=', targetDid))
|
|
49
|
+
.orWhere((qb) => qb
|
|
50
|
+
.where('list_block.creator', '=', targetDid)
|
|
51
|
+
.where('list_item.subjectDid', '=', actorDid))
|
|
52
|
+
.limit(1)
|
|
53
|
+
.selectAll('list_block')
|
|
54
|
+
.executeTakeFirst();
|
|
55
|
+
return {
|
|
56
|
+
listUri: res?.subjectUri,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
async getBlocklistSubscription(req) {
|
|
60
|
+
const { actorDid, listUri } = req;
|
|
61
|
+
const res = await db.db
|
|
62
|
+
.selectFrom('list_block')
|
|
63
|
+
.where('creator', '=', actorDid)
|
|
64
|
+
.where('subjectUri', '=', listUri)
|
|
65
|
+
.selectAll()
|
|
66
|
+
.limit(1)
|
|
67
|
+
.executeTakeFirst();
|
|
68
|
+
return {
|
|
69
|
+
listblockUri: res?.uri,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
async getBlocklistSubscriptions(req) {
|
|
73
|
+
const { actorDid, limit, cursor } = req;
|
|
74
|
+
const { ref } = db.db.dynamic;
|
|
75
|
+
let builder = db.db
|
|
76
|
+
.selectFrom('list')
|
|
77
|
+
.whereExists(db.db
|
|
78
|
+
.selectFrom('list_block')
|
|
79
|
+
.where('list_block.creator', '=', actorDid)
|
|
80
|
+
.whereRef('list_block.subjectUri', '=', ref('list.uri'))
|
|
81
|
+
.selectAll())
|
|
82
|
+
.selectAll('list');
|
|
83
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('list.createdAt'), ref('list.cid'));
|
|
84
|
+
builder = (0, pagination_1.paginate)(builder, {
|
|
85
|
+
limit,
|
|
86
|
+
cursor,
|
|
87
|
+
keyset,
|
|
88
|
+
});
|
|
89
|
+
const lists = await builder.execute();
|
|
90
|
+
return {
|
|
91
|
+
listUris: lists.map((l) => l.uri),
|
|
92
|
+
cursor: keyset.packFromResult(lists),
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const pagination_1 = require("../db/pagination");
|
|
4
|
+
exports.default = (db) => ({
|
|
5
|
+
async getActorFeeds(req) {
|
|
6
|
+
const { actorDid, limit, cursor } = req;
|
|
7
|
+
const { ref } = db.db.dynamic;
|
|
8
|
+
let builder = db.db
|
|
9
|
+
.selectFrom('feed_generator')
|
|
10
|
+
.selectAll()
|
|
11
|
+
.where('feed_generator.creator', '=', actorDid);
|
|
12
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('feed_generator.createdAt'), ref('feed_generator.cid'));
|
|
13
|
+
builder = (0, pagination_1.paginate)(builder, {
|
|
14
|
+
limit,
|
|
15
|
+
cursor,
|
|
16
|
+
keyset,
|
|
17
|
+
});
|
|
18
|
+
const feeds = await builder.execute();
|
|
19
|
+
return {
|
|
20
|
+
uris: feeds.map((f) => f.uri),
|
|
21
|
+
cursor: keyset.packFromResult(feeds),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
async getSuggestedFeeds(req) {
|
|
25
|
+
const feeds = await db.db
|
|
26
|
+
.selectFrom('suggested_feed')
|
|
27
|
+
.orderBy('suggested_feed.order', 'asc')
|
|
28
|
+
.if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10)))
|
|
29
|
+
.limit(req.limit || 50)
|
|
30
|
+
.selectAll()
|
|
31
|
+
.execute();
|
|
32
|
+
return {
|
|
33
|
+
uris: feeds.map((f) => f.uri),
|
|
34
|
+
cursor: feeds.at(-1)?.order.toString(),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
async searchFeedGenerators(req) {
|
|
38
|
+
const { ref } = db.db.dynamic;
|
|
39
|
+
const limit = req.limit;
|
|
40
|
+
const query = req.query.trim();
|
|
41
|
+
let builder = db.db
|
|
42
|
+
.selectFrom('feed_generator')
|
|
43
|
+
.if(!!query, (q) => q.where('displayName', 'ilike', `%${query}%`))
|
|
44
|
+
.selectAll();
|
|
45
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('feed_generator.createdAt'), ref('feed_generator.cid'));
|
|
46
|
+
builder = (0, pagination_1.paginate)(builder, { limit, keyset });
|
|
47
|
+
const feeds = await builder.execute();
|
|
48
|
+
return {
|
|
49
|
+
uris: feeds.map((f) => f.uri),
|
|
50
|
+
cursor: keyset.packFromResult(feeds),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
async getFeedGeneratorStatus() {
|
|
54
|
+
throw new Error('unimplemented');
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bsky_pb_1 = require("../../../proto/bsky_pb");
|
|
4
|
+
const pagination_1 = require("../db/pagination");
|
|
5
|
+
exports.default = (db) => ({
|
|
6
|
+
async getAuthorFeed(req) {
|
|
7
|
+
const { actorDid, limit, cursor, feedType } = req;
|
|
8
|
+
const { ref } = db.db.dynamic;
|
|
9
|
+
// defaults to posts, reposts, and replies
|
|
10
|
+
let builder = db.db
|
|
11
|
+
.selectFrom('feed_item')
|
|
12
|
+
.innerJoin('post', 'post.uri', 'feed_item.postUri')
|
|
13
|
+
.selectAll('feed_item')
|
|
14
|
+
.where('originatorDid', '=', actorDid);
|
|
15
|
+
if (feedType === bsky_pb_1.FeedType.POSTS_WITH_MEDIA) {
|
|
16
|
+
builder = builder
|
|
17
|
+
// only your own posts
|
|
18
|
+
.where('type', '=', 'post')
|
|
19
|
+
// only posts with media
|
|
20
|
+
.whereExists((qb) => qb
|
|
21
|
+
.selectFrom('post_embed_image')
|
|
22
|
+
.select('post_embed_image.postUri')
|
|
23
|
+
.whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'));
|
|
24
|
+
}
|
|
25
|
+
else if (feedType === bsky_pb_1.FeedType.POSTS_WITH_VIDEO) {
|
|
26
|
+
builder = builder
|
|
27
|
+
// only your own posts
|
|
28
|
+
.where('type', '=', 'post')
|
|
29
|
+
// only posts with video
|
|
30
|
+
.whereExists((qb) => qb
|
|
31
|
+
.selectFrom('post_embed_video')
|
|
32
|
+
.select('post_embed_video.postUri')
|
|
33
|
+
.whereRef('post_embed_video.postUri', '=', 'feed_item.postUri'));
|
|
34
|
+
}
|
|
35
|
+
else if (feedType === bsky_pb_1.FeedType.POSTS_NO_REPLIES) {
|
|
36
|
+
builder = builder.where((qb) => qb.where('post.replyParent', 'is', null).orWhere('type', '=', 'repost'));
|
|
37
|
+
}
|
|
38
|
+
else if (feedType === bsky_pb_1.FeedType.POSTS_AND_AUTHOR_THREADS) {
|
|
39
|
+
builder = builder.where((qb) => qb
|
|
40
|
+
.where('type', '=', 'repost')
|
|
41
|
+
.orWhere('post.replyParent', 'is', null)
|
|
42
|
+
.orWhere('post.replyRoot', 'like', `at://${actorDid}/%`));
|
|
43
|
+
}
|
|
44
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('feed_item.sortAt'), ref('feed_item.cid'));
|
|
45
|
+
builder = (0, pagination_1.paginate)(builder, {
|
|
46
|
+
limit,
|
|
47
|
+
cursor,
|
|
48
|
+
keyset,
|
|
49
|
+
});
|
|
50
|
+
const feedItems = await builder.execute();
|
|
51
|
+
return {
|
|
52
|
+
items: feedItems.map(feedItemFromRow),
|
|
53
|
+
cursor: keyset.packFromResult(feedItems),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async getTimeline(req) {
|
|
57
|
+
const { actorDid, limit, cursor } = req;
|
|
58
|
+
const { ref } = db.db.dynamic;
|
|
59
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('feed_item.sortAt'), ref('feed_item.cid'));
|
|
60
|
+
let followQb = db.db
|
|
61
|
+
.selectFrom('feed_item')
|
|
62
|
+
.innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid')
|
|
63
|
+
.where('follow.creator', '=', actorDid)
|
|
64
|
+
.selectAll('feed_item');
|
|
65
|
+
followQb = (0, pagination_1.paginate)(followQb, {
|
|
66
|
+
limit,
|
|
67
|
+
cursor,
|
|
68
|
+
keyset,
|
|
69
|
+
tryIndex: true,
|
|
70
|
+
});
|
|
71
|
+
let selfQb = db.db
|
|
72
|
+
.selectFrom('feed_item')
|
|
73
|
+
.where('feed_item.originatorDid', '=', actorDid)
|
|
74
|
+
.selectAll('feed_item');
|
|
75
|
+
selfQb = (0, pagination_1.paginate)(selfQb, {
|
|
76
|
+
limit: Math.min(limit, 10),
|
|
77
|
+
cursor,
|
|
78
|
+
keyset,
|
|
79
|
+
tryIndex: true,
|
|
80
|
+
});
|
|
81
|
+
const [followRes, selfRes] = await Promise.all([
|
|
82
|
+
followQb.execute(),
|
|
83
|
+
selfQb.execute(),
|
|
84
|
+
]);
|
|
85
|
+
const feedItems = [...followRes, ...selfRes]
|
|
86
|
+
.sort((a, b) => {
|
|
87
|
+
if (a.sortAt > b.sortAt)
|
|
88
|
+
return -1;
|
|
89
|
+
if (a.sortAt < b.sortAt)
|
|
90
|
+
return 1;
|
|
91
|
+
return a.cid > b.cid ? -1 : 1;
|
|
92
|
+
})
|
|
93
|
+
.slice(0, limit);
|
|
94
|
+
return {
|
|
95
|
+
items: feedItems.map(feedItemFromRow),
|
|
96
|
+
cursor: keyset.packFromResult(feedItems),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
async getListFeed(req) {
|
|
100
|
+
const { listUri, cursor, limit } = req;
|
|
101
|
+
const { ref } = db.db.dynamic;
|
|
102
|
+
let builder = db.db
|
|
103
|
+
.selectFrom('post')
|
|
104
|
+
.selectAll('post')
|
|
105
|
+
.innerJoin('list_item', 'list_item.subjectDid', 'post.creator')
|
|
106
|
+
.where('list_item.listUri', '=', listUri);
|
|
107
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('post.sortAt'), ref('post.cid'));
|
|
108
|
+
builder = (0, pagination_1.paginate)(builder, {
|
|
109
|
+
limit,
|
|
110
|
+
cursor,
|
|
111
|
+
keyset,
|
|
112
|
+
tryIndex: true,
|
|
113
|
+
});
|
|
114
|
+
const feedItems = await builder.execute();
|
|
115
|
+
return {
|
|
116
|
+
items: feedItems.map((item) => ({ uri: item.uri, cid: item.cid })),
|
|
117
|
+
cursor: keyset.packFromResult(feedItems),
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
// @NOTE does not support additional fields in the protos specific to author feeds
|
|
122
|
+
// and timelines. at the time of writing, hydration/view implementations do not rely on them.
|
|
123
|
+
const feedItemFromRow = (row) => {
|
|
124
|
+
return {
|
|
125
|
+
uri: row.postUri,
|
|
126
|
+
repost: row.uri === row.postUri ? undefined : row.uri,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const common_1 = require("@atproto/common");
|
|
4
|
+
const bsky_pb_1 = require("../../../proto/bsky_pb");
|
|
5
|
+
const pagination_1 = require("../db/pagination");
|
|
6
|
+
exports.default = (db) => ({
|
|
7
|
+
async getActorFollowsActors(req) {
|
|
8
|
+
const { actorDid, targetDids } = req;
|
|
9
|
+
if (targetDids.length < 1) {
|
|
10
|
+
return { uris: [] };
|
|
11
|
+
}
|
|
12
|
+
const res = await db.db
|
|
13
|
+
.selectFrom('follow')
|
|
14
|
+
.where('follow.creator', '=', actorDid)
|
|
15
|
+
.where('follow.subjectDid', 'in', targetDids)
|
|
16
|
+
.selectAll()
|
|
17
|
+
.execute();
|
|
18
|
+
const bySubject = (0, common_1.keyBy)(res, 'subjectDid');
|
|
19
|
+
const uris = targetDids.map((did) => bySubject.get(did)?.uri ?? '');
|
|
20
|
+
return {
|
|
21
|
+
uris,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
async getFollowers(req) {
|
|
25
|
+
const { actorDid, limit, cursor } = req;
|
|
26
|
+
const { ref } = db.db.dynamic;
|
|
27
|
+
let followersReq = db.db
|
|
28
|
+
.selectFrom('follow')
|
|
29
|
+
.where('follow.subjectDid', '=', actorDid)
|
|
30
|
+
.innerJoin('actor as creator', 'creator.did', 'follow.creator')
|
|
31
|
+
.selectAll('creator')
|
|
32
|
+
.select([
|
|
33
|
+
'follow.uri as uri',
|
|
34
|
+
'follow.cid as cid',
|
|
35
|
+
'follow.creator as creatorDid',
|
|
36
|
+
'follow.subjectDid as subjectDid',
|
|
37
|
+
'follow.sortAt as sortAt',
|
|
38
|
+
]);
|
|
39
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid'));
|
|
40
|
+
followersReq = (0, pagination_1.paginate)(followersReq, {
|
|
41
|
+
limit,
|
|
42
|
+
cursor,
|
|
43
|
+
keyset,
|
|
44
|
+
tryIndex: true,
|
|
45
|
+
});
|
|
46
|
+
const followers = await followersReq.execute();
|
|
47
|
+
return {
|
|
48
|
+
followers: followers.map((f) => ({
|
|
49
|
+
uri: f.uri,
|
|
50
|
+
actorDid: f.creatorDid,
|
|
51
|
+
subjectDid: f.subjectDid,
|
|
52
|
+
})),
|
|
53
|
+
cursor: keyset.packFromResult(followers),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async getFollows(req) {
|
|
57
|
+
const { actorDid, limit, cursor } = req;
|
|
58
|
+
const { ref } = db.db.dynamic;
|
|
59
|
+
let followsReq = db.db
|
|
60
|
+
.selectFrom('follow')
|
|
61
|
+
.where('follow.creator', '=', actorDid)
|
|
62
|
+
.innerJoin('actor as subject', 'subject.did', 'follow.subjectDid')
|
|
63
|
+
.selectAll('subject')
|
|
64
|
+
.select([
|
|
65
|
+
'follow.uri as uri',
|
|
66
|
+
'follow.cid as cid',
|
|
67
|
+
'follow.creator as creatorDid',
|
|
68
|
+
'follow.subjectDid as subjectDid',
|
|
69
|
+
'follow.sortAt as sortAt',
|
|
70
|
+
]);
|
|
71
|
+
const keyset = new pagination_1.TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid'));
|
|
72
|
+
followsReq = (0, pagination_1.paginate)(followsReq, {
|
|
73
|
+
limit,
|
|
74
|
+
cursor,
|
|
75
|
+
keyset,
|
|
76
|
+
tryIndex: true,
|
|
77
|
+
});
|
|
78
|
+
const follows = await followsReq.execute();
|
|
79
|
+
return {
|
|
80
|
+
follows: follows.map((f) => ({
|
|
81
|
+
uri: f.uri,
|
|
82
|
+
actorDid: f.creatorDid,
|
|
83
|
+
subjectDid: f.subjectDid,
|
|
84
|
+
})),
|
|
85
|
+
cursor: keyset.packFromResult(follows),
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Return known followers of a given actor.
|
|
90
|
+
*
|
|
91
|
+
* Example:
|
|
92
|
+
* - Alice follows Bob
|
|
93
|
+
* - Bob follows Dan
|
|
94
|
+
*
|
|
95
|
+
* If Alice (the viewer) looks at Dan's profile (the subject), she should see that Bob follows Dan
|
|
96
|
+
*/
|
|
97
|
+
async getFollowsFollowing(req) {
|
|
98
|
+
const { actorDid: viewerDid, targetDids: subjectDids } = req;
|
|
99
|
+
/*
|
|
100
|
+
* 1. Get all the people the Alice is following
|
|
101
|
+
* 2. Get all the people the Dan is followed by
|
|
102
|
+
* 3. Find the intersection
|
|
103
|
+
*/
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const subjectDid of subjectDids) {
|
|
106
|
+
const followsReq = db.db
|
|
107
|
+
.selectFrom('follow')
|
|
108
|
+
.where('follow.creator', '=', viewerDid)
|
|
109
|
+
.where('follow.subjectDid', 'in', db.db
|
|
110
|
+
.selectFrom('follow')
|
|
111
|
+
.where('follow.subjectDid', '=', subjectDid)
|
|
112
|
+
.select(['creator']))
|
|
113
|
+
.select(['subjectDid']);
|
|
114
|
+
const rows = await followsReq.execute();
|
|
115
|
+
results.push(new bsky_pb_1.FollowsFollowing({
|
|
116
|
+
targetDid: subjectDid,
|
|
117
|
+
dids: rows.map((r) => r.subjectDid),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
return { results };
|
|
121
|
+
},
|
|
122
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const identity_1 = require("@atproto/identity");
|
|
4
|
+
const protobuf_1 = require("@bufbuild/protobuf");
|
|
5
|
+
const connect_1 = require("@connectrpc/connect");
|
|
6
|
+
exports.default = (_db, idResolver) => ({
|
|
7
|
+
async getIdentityByDid(req) {
|
|
8
|
+
const doc = await idResolver.did.resolve(req.did);
|
|
9
|
+
if (!doc) {
|
|
10
|
+
throw new connect_1.ConnectError('identity not found', connect_1.Code.NotFound);
|
|
11
|
+
}
|
|
12
|
+
return getResultFromDoc(doc);
|
|
13
|
+
},
|
|
14
|
+
async getIdentityByHandle(req) {
|
|
15
|
+
const did = await idResolver.handle.resolve(req.handle);
|
|
16
|
+
if (!did) {
|
|
17
|
+
throw new connect_1.ConnectError('identity not found', connect_1.Code.NotFound);
|
|
18
|
+
}
|
|
19
|
+
const doc = await idResolver.did.resolve(did);
|
|
20
|
+
if (!doc || did !== (0, identity_1.getDid)(doc)) {
|
|
21
|
+
throw new connect_1.ConnectError('identity not found', connect_1.Code.NotFound);
|
|
22
|
+
}
|
|
23
|
+
return getResultFromDoc(doc);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const getResultFromDoc = (doc) => {
|
|
27
|
+
const keys = {};
|
|
28
|
+
doc.verificationMethod?.forEach((method) => {
|
|
29
|
+
const id = method.id.split('#').at(1);
|
|
30
|
+
if (!id)
|
|
31
|
+
return;
|
|
32
|
+
keys[id] = {
|
|
33
|
+
Type: method.type,
|
|
34
|
+
PublicKeyMultibase: method.publicKeyMultibase || '',
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
const services = {};
|
|
38
|
+
doc.service?.forEach((service) => {
|
|
39
|
+
const id = service.id.split('#').at(1);
|
|
40
|
+
if (!id)
|
|
41
|
+
return;
|
|
42
|
+
if (typeof service.serviceEndpoint !== 'string')
|
|
43
|
+
return;
|
|
44
|
+
services[id] = {
|
|
45
|
+
Type: service.type,
|
|
46
|
+
URL: service.serviceEndpoint,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
did: (0, identity_1.getDid)(doc),
|
|
51
|
+
handle: (0, identity_1.getHandle)(doc),
|
|
52
|
+
keys: Buffer.from(JSON.stringify(keys)),
|
|
53
|
+
services: Buffer.from(JSON.stringify(services)),
|
|
54
|
+
updated: protobuf_1.Timestamp.fromDate(new Date()),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const clio_connect_1 = require("../../../rpc/clio_connect");
|
|
7
|
+
const identity_1 = __importDefault(require("./identity"));
|
|
8
|
+
const profile_1 = __importDefault(require("./profile"));
|
|
9
|
+
const records_1 = __importDefault(require("./records"));
|
|
10
|
+
const sync_1 = __importDefault(require("./sync"));
|
|
11
|
+
exports.default = (db, idResolver) => (router) => router.service(clio_connect_1.ClioService, {
|
|
12
|
+
...(0, identity_1.default)(db, idResolver),
|
|
13
|
+
...(0, profile_1.default)(db),
|
|
14
|
+
...(0, records_1.default)(db),
|
|
15
|
+
...(0, sync_1.default)(db),
|
|
16
|
+
async healthCheck() {
|
|
17
|
+
return {};
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const common_1 = require("@atproto/common");
|
|
4
|
+
const util_1 = require("../db/util");
|
|
5
|
+
exports.default = (db) => ({
|
|
6
|
+
async getInteractionCounts(req) {
|
|
7
|
+
const uris = req.refs.map((ref) => ref.uri);
|
|
8
|
+
if (uris.length === 0) {
|
|
9
|
+
return { likes: [], replies: [], reposts: [], quotes: [] };
|
|
10
|
+
}
|
|
11
|
+
const res = await db.db
|
|
12
|
+
.selectFrom('post_agg')
|
|
13
|
+
.where('uri', 'in', uris)
|
|
14
|
+
.selectAll()
|
|
15
|
+
.execute();
|
|
16
|
+
const byUri = (0, common_1.keyBy)(res, 'uri');
|
|
17
|
+
return {
|
|
18
|
+
likes: uris.map((uri) => byUri.get(uri)?.likeCount ?? 0),
|
|
19
|
+
replies: uris.map((uri) => byUri.get(uri)?.replyCount ?? 0),
|
|
20
|
+
reposts: uris.map((uri) => byUri.get(uri)?.repostCount ?? 0),
|
|
21
|
+
quotes: uris.map((uri) => byUri.get(uri)?.quoteCount ?? 0),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
async getCountsForUsers(req) {
|
|
25
|
+
if (req.dids.length === 0) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const { ref } = db.db.dynamic;
|
|
29
|
+
const res = await db.db
|
|
30
|
+
.selectFrom('profile_agg')
|
|
31
|
+
.where('did', 'in', req.dids)
|
|
32
|
+
.selectAll('profile_agg')
|
|
33
|
+
.select([
|
|
34
|
+
db.db
|
|
35
|
+
.selectFrom('feed_generator')
|
|
36
|
+
.whereRef('creator', '=', ref('profile_agg.did'))
|
|
37
|
+
.select(util_1.countAll.as('val'))
|
|
38
|
+
.as('feedGensCount'),
|
|
39
|
+
db.db
|
|
40
|
+
.selectFrom('list')
|
|
41
|
+
.whereRef('creator', '=', ref('profile_agg.did'))
|
|
42
|
+
.select(util_1.countAll.as('val'))
|
|
43
|
+
.as('listsCount'),
|
|
44
|
+
db.db
|
|
45
|
+
.selectFrom('starter_pack')
|
|
46
|
+
.whereRef('creator', '=', ref('profile_agg.did'))
|
|
47
|
+
.select(util_1.countAll.as('val'))
|
|
48
|
+
.as('starterPacksCount'),
|
|
49
|
+
])
|
|
50
|
+
.execute();
|
|
51
|
+
const byDid = (0, common_1.keyBy)(res, 'did');
|
|
52
|
+
return {
|
|
53
|
+
followers: req.dids.map((uri) => byDid.get(uri)?.followersCount ?? 0),
|
|
54
|
+
following: req.dids.map((uri) => byDid.get(uri)?.followsCount ?? 0),
|
|
55
|
+
posts: req.dids.map((uri) => byDid.get(uri)?.postsCount ?? 0),
|
|
56
|
+
lists: req.dids.map((uri) => byDid.get(uri)?.listsCount ?? 0),
|
|
57
|
+
feeds: req.dids.map((uri) => byDid.get(uri)?.feedGensCount ?? 0),
|
|
58
|
+
starterPacks: req.dids.map((uri) => byDid.get(uri)?.starterPacksCount ?? 0),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
async getStarterPackCounts(req) {
|
|
62
|
+
const weekAgo = new Date(Date.now() - 7 * common_1.DAY);
|
|
63
|
+
const uris = req.refs.map((ref) => ref.uri);
|
|
64
|
+
if (uris.length === 0) {
|
|
65
|
+
return { joinedAllTime: [], joinedWeek: [] };
|
|
66
|
+
}
|
|
67
|
+
const countsAllTime = await db.db
|
|
68
|
+
.selectFrom('profile')
|
|
69
|
+
.where('joinedViaStarterPackUri', 'in', uris)
|
|
70
|
+
.select(['joinedViaStarterPackUri as uri', util_1.countAll.as('count')])
|
|
71
|
+
.groupBy('joinedViaStarterPackUri')
|
|
72
|
+
.execute();
|
|
73
|
+
const countsWeek = await db.db
|
|
74
|
+
.selectFrom('profile')
|
|
75
|
+
.where('joinedViaStarterPackUri', 'in', uris)
|
|
76
|
+
.where('createdAt', '>', weekAgo.toISOString())
|
|
77
|
+
.select(['joinedViaStarterPackUri as uri', util_1.countAll.as('count')])
|
|
78
|
+
.groupBy('joinedViaStarterPackUri')
|
|
79
|
+
.execute();
|
|
80
|
+
const countsWeekByUri = countsWeek.reduce((cur, item) => {
|
|
81
|
+
if (!item.uri)
|
|
82
|
+
return cur;
|
|
83
|
+
return cur.set(item.uri, item.count);
|
|
84
|
+
}, new Map());
|
|
85
|
+
const countsAllTimeByUri = countsAllTime.reduce((cur, item) => {
|
|
86
|
+
if (!item.uri)
|
|
87
|
+
return cur;
|
|
88
|
+
return cur.set(item.uri, item.count);
|
|
89
|
+
}, new Map());
|
|
90
|
+
return {
|
|
91
|
+
joinedWeek: uris.map((uri) => countsWeekByUri.get(uri) ?? 0),
|
|
92
|
+
joinedAllTime: uris.map((uri) => countsAllTimeByUri.get(uri) ?? 0),
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
async getListCounts(req) {
|
|
96
|
+
const uris = req.refs.map((ref) => ref.uri);
|
|
97
|
+
if (uris.length === 0) {
|
|
98
|
+
return { listItems: [] };
|
|
99
|
+
}
|
|
100
|
+
const countsListItems = await db.db
|
|
101
|
+
.selectFrom('list_item')
|
|
102
|
+
.where('listUri', 'in', uris)
|
|
103
|
+
.select(['listUri as uri', util_1.countAll.as('count')])
|
|
104
|
+
.groupBy('listUri')
|
|
105
|
+
.execute();
|
|
106
|
+
const countsByUri = (0, common_1.keyBy)(countsListItems, 'uri');
|
|
107
|
+
return {
|
|
108
|
+
listItems: uris.map((uri) => countsByUri.get(uri)?.count ?? 0),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
});
|