@atproto/pds 0.3.0-beta.2 → 0.3.0-beta.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -57,6 +57,7 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos';
57
57
  import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate';
58
58
  import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl';
59
59
  import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos';
60
+ import * as ComAtprotoTempUpgradeRepoVersion from './types/com/atproto/temp/upgradeRepoVersion';
60
61
  import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences';
61
62
  import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile';
62
63
  import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles';
@@ -137,6 +138,7 @@ export declare class AtprotoNS {
137
138
  repo: RepoNS;
138
139
  server: ServerNS;
139
140
  sync: SyncNS;
141
+ temp: TempNS;
140
142
  constructor(server: Server);
141
143
  }
142
144
  export declare class AdminNS {
@@ -225,6 +227,11 @@ export declare class SyncNS {
225
227
  requestCrawl<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoSyncRequestCrawl.Handler<ExtractAuth<AV>>, ComAtprotoSyncRequestCrawl.HandlerReqCtx<ExtractAuth<AV>>>): void;
226
228
  subscribeRepos<AV extends StreamAuthVerifier>(cfg: ConfigOf<AV, ComAtprotoSyncSubscribeRepos.Handler<ExtractAuth<AV>>, ComAtprotoSyncSubscribeRepos.HandlerReqCtx<ExtractAuth<AV>>>): void;
227
229
  }
230
+ export declare class TempNS {
231
+ _server: Server;
232
+ constructor(server: Server);
233
+ upgradeRepoVersion<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoTempUpgradeRepoVersion.Handler<ExtractAuth<AV>>, ComAtprotoTempUpgradeRepoVersion.HandlerReqCtx<ExtractAuth<AV>>>): void;
234
+ }
228
235
  export declare class AppNS {
229
236
  _server: Server;
230
237
  bsky: BskyNS;
@@ -3251,6 +3251,32 @@ export declare const schemaDict: {
3251
3251
  };
3252
3252
  };
3253
3253
  };
3254
+ ComAtprotoTempUpgradeRepoVersion: {
3255
+ lexicon: number;
3256
+ id: string;
3257
+ defs: {
3258
+ main: {
3259
+ type: string;
3260
+ description: string;
3261
+ input: {
3262
+ encoding: string;
3263
+ schema: {
3264
+ type: string;
3265
+ required: string[];
3266
+ properties: {
3267
+ did: {
3268
+ type: string;
3269
+ format: string;
3270
+ };
3271
+ force: {
3272
+ type: string;
3273
+ };
3274
+ };
3275
+ };
3276
+ };
3277
+ };
3278
+ };
3279
+ };
3254
3280
  AppBskyActorDefs: {
3255
3281
  lexicon: number;
3256
3282
  id: string;
@@ -6465,6 +6491,7 @@ export declare const ids: {
6465
6491
  ComAtprotoSyncNotifyOfUpdate: string;
6466
6492
  ComAtprotoSyncRequestCrawl: string;
6467
6493
  ComAtprotoSyncSubscribeRepos: string;
6494
+ ComAtprotoTempUpgradeRepoVersion: string;
6468
6495
  AppBskyActorDefs: string;
6469
6496
  AppBskyActorGetPreferences: string;
6470
6497
  AppBskyActorGetProfile: string;
@@ -13,6 +13,7 @@ import { LocalService } from './local';
13
13
  export declare function createServices(resources: {
14
14
  repoSigningKey: crypto.Keypair;
15
15
  blobstore: BlobStore;
16
+ pdsHostname: string;
16
17
  appViewAgent?: AtpAgent;
17
18
  appViewDid?: string;
18
19
  appViewCdnUrlPattern?: string;
@@ -15,11 +15,12 @@ declare type CommonSignedUris = 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_f
15
15
  export declare class LocalService {
16
16
  db: Database;
17
17
  signingKey: Keypair;
18
+ pdsHostname: string;
18
19
  appviewAgent?: AtpAgent | undefined;
19
20
  appviewDid?: string | undefined;
20
21
  appviewCdnUrlPattern?: string | undefined;
21
- constructor(db: Database, signingKey: Keypair, appviewAgent?: AtpAgent | undefined, appviewDid?: string | undefined, appviewCdnUrlPattern?: string | undefined);
22
- static creator(signingKey: Keypair, appviewAgent?: AtpAgent, appviewDid?: string, appviewCdnUrlPattern?: string): (db: Database) => LocalService;
22
+ constructor(db: Database, signingKey: Keypair, pdsHostname: string, appviewAgent?: AtpAgent | undefined, appviewDid?: string | undefined, appviewCdnUrlPattern?: string | undefined);
23
+ static creator(signingKey: Keypair, pdsHostname: string, appviewAgent?: AtpAgent, appviewDid?: string, appviewCdnUrlPattern?: string): (db: Database) => LocalService;
23
24
  getImageUrl(pattern: CommonSignedUris, did: string, cid: string): string;
24
25
  serviceAuthHeaders(did: string): Promise<{
25
26
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/pds",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,15 +39,15 @@
39
39
  "typed-emitter": "^2.1.0",
40
40
  "uint8arrays": "3.0.0",
41
41
  "zod": "^3.21.4",
42
- "@atproto/api": "^0.6.12",
43
- "@atproto/common": "^0.3.0",
44
42
  "@atproto/aws": "^0.1.0",
45
- "@atproto/crypto": "^0.2.2",
43
+ "@atproto/api": "^0.6.12",
46
44
  "@atproto/syntax": "^0.1.0",
45
+ "@atproto/common": "^0.3.0",
47
46
  "@atproto/identity": "^0.2.0",
47
+ "@atproto/crypto": "^0.2.2",
48
48
  "@atproto/repo": "^0.3.0",
49
- "@atproto/lexicon": "^0.2.0",
50
49
  "@atproto/xrpc-server": "^0.3.0",
50
+ "@atproto/lexicon": "^0.2.0",
51
51
  "@atproto/xrpc": "^0.3.0"
52
52
  },
53
53
  "devDependencies": {
@@ -61,10 +61,10 @@
61
61
  "@types/qs": "^6.9.7",
62
62
  "@types/sharp": "^0.31.0",
63
63
  "axios": "^0.27.2",
64
- "@atproto/bsky": "^0.0.3",
65
- "@atproto/api": "^0.6.12",
66
64
  "@atproto/dev-env": "^0.2.3",
67
- "@atproto/lex-cli": "^0.2.0"
65
+ "@atproto/api": "^0.6.12",
66
+ "@atproto/lex-cli": "^0.2.0",
67
+ "@atproto/bsky": "^0.0.3"
68
68
  },
69
69
  "scripts": {
70
70
  "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*",
@@ -6,6 +6,7 @@ import moderation from './moderation'
6
6
  import repo from './repo'
7
7
  import serverMethods from './server'
8
8
  import sync from './sync'
9
+ import upgradeRepoVersion from './upgradeRepoVersion'
9
10
 
10
11
  export default function (server: Server, ctx: AppContext) {
11
12
  admin(server, ctx)
@@ -14,4 +15,5 @@ export default function (server: Server, ctx: AppContext) {
14
15
  repo(server, ctx)
15
16
  serverMethods(server, ctx)
16
17
  sync(server, ctx)
18
+ upgradeRepoVersion(server, ctx)
17
19
  }
@@ -0,0 +1,140 @@
1
+ import { InvalidRequestError } from '@atproto/xrpc-server'
2
+ import { TID, chunkArray, wait } from '@atproto/common'
3
+ import { Server } from '../../../lexicon'
4
+ import SqlRepoStorage from '../../../sql-repo-storage'
5
+ import AppContext from '../../../context'
6
+ import {
7
+ BlockMap,
8
+ CidSet,
9
+ DataDiff,
10
+ MST,
11
+ MemoryBlockstore,
12
+ def,
13
+ signCommit,
14
+ } from '@atproto/repo'
15
+ import { CID } from 'multiformats/cid'
16
+ import { formatSeqCommit, sequenceEvt } from '../../../sequencer'
17
+ import { httpLogger as log } from '../../../logger'
18
+
19
+ export default function (server: Server, ctx: AppContext) {
20
+ server.com.atproto.temp.upgradeRepoVersion({
21
+ auth: ctx.roleVerifier,
22
+ handler: async ({ input, auth }) => {
23
+ if (!auth.credentials.admin) {
24
+ throw new InvalidRequestError('must be admin')
25
+ }
26
+ const { did, force } = input.body
27
+
28
+ await ctx.db.transaction(async (dbTxn) => {
29
+ const storage = new SqlRepoStorage(dbTxn, did)
30
+ await obtainLock(storage)
31
+ const prevCid = await storage.getRoot()
32
+ if (!prevCid) {
33
+ throw new InvalidRequestError('Could not find repo')
34
+ }
35
+ const prev = await storage.readObj(prevCid, def.versionedCommit)
36
+ const records = await dbTxn.db
37
+ .selectFrom('record')
38
+ .select(['collection', 'rkey', 'cid'])
39
+ .where('did', '=', did)
40
+ .execute()
41
+ const memoryStore = new MemoryBlockstore()
42
+ let data = await MST.create(memoryStore)
43
+ for (const record of records) {
44
+ const dataKey = record.collection + '/' + record.rkey
45
+ const cid = CID.parse(record.cid)
46
+ data = await data.add(dataKey, cid)
47
+ }
48
+ const dataCid = await data.getPointer()
49
+ if (!force && !dataCid.equals(prev.data)) {
50
+ throw new InvalidRequestError('Data cid did not match')
51
+ }
52
+ const recordCids = records.map((r) => r.cid)
53
+ const diff = await DataDiff.of(data, null)
54
+ const cidsToKeep = [...recordCids, ...diff.newMstBlocks.cids()]
55
+ const rev = TID.nextStr(prev.rev)
56
+ if (force) {
57
+ const got = await storage.getBlocks(diff.newMstBlocks.cids())
58
+ const toAdd = diff.newMstBlocks.getMany(got.missing)
59
+ log.info(
60
+ { missing: got.missing.length },
61
+ 'force added missing blocks',
62
+ )
63
+ // puts any new blocks & no-ops for already existing
64
+ await storage.putMany(toAdd.blocks, rev)
65
+ }
66
+ for (const chunk of chunkArray(cidsToKeep, 500)) {
67
+ const cidStrs = chunk.map((c) => c.toString())
68
+ await dbTxn.db
69
+ .updateTable('ipld_block')
70
+ .set({ repoRev: rev })
71
+ .where('creator', '=', did)
72
+ .where('cid', 'in', cidStrs)
73
+ .execute()
74
+ }
75
+ await dbTxn.db
76
+ .deleteFrom('ipld_block')
77
+ .where('creator', '=', did)
78
+ .where((qb) =>
79
+ qb.where('repoRev', 'is', null).orWhere('repoRev', '!=', rev),
80
+ )
81
+ .execute()
82
+ await dbTxn.db
83
+ .updateTable('repo_blob')
84
+ .set({ repoRev: rev })
85
+ .where('did', '=', did)
86
+ .execute()
87
+ await dbTxn.db
88
+ .updateTable('record')
89
+ .set({ repoRev: rev })
90
+ .where('did', '=', did)
91
+ .execute()
92
+ const commit = await signCommit(
93
+ {
94
+ did,
95
+ version: 3,
96
+ rev: TID.nextStr(),
97
+ prev: prevCid,
98
+ data: dataCid,
99
+ },
100
+ ctx.repoSigningKey,
101
+ )
102
+ const newBlocks = new BlockMap()
103
+ const commitCid = await newBlocks.add(commit)
104
+ await storage.putMany(newBlocks, rev)
105
+ await dbTxn.db
106
+ .updateTable('repo_root')
107
+ .set({
108
+ root: commitCid.toString(),
109
+ rev,
110
+ indexedAt: storage.getTimestamp(),
111
+ })
112
+ .where('did', '=', did)
113
+ .execute()
114
+
115
+ const commitData = {
116
+ cid: commitCid,
117
+ rev,
118
+ prev: prevCid,
119
+ since: null,
120
+ newBlocks,
121
+ removedCids: new CidSet(),
122
+ }
123
+ const seqEvt = await formatSeqCommit(did, commitData, [])
124
+ await sequenceEvt(dbTxn, seqEvt)
125
+ })
126
+ },
127
+ })
128
+ }
129
+
130
+ const obtainLock = async (storage: SqlRepoStorage, tries = 20) => {
131
+ const obtained = await storage.lockRepo()
132
+ if (obtained) {
133
+ return
134
+ }
135
+ if (tries < 1) {
136
+ throw new InvalidRequestError('could not obtain lock')
137
+ }
138
+ await wait(50)
139
+ return obtainLock(storage, tries - 1)
140
+ }
package/src/context.ts CHANGED
@@ -180,6 +180,7 @@ export class AppContext {
180
180
  repoSigningKey,
181
181
  blobstore,
182
182
  appViewAgent,
183
+ pdsHostname: cfg.service.hostname,
183
184
  appViewDid: cfg.bskyAppView.did,
184
185
  appViewCdnUrlPattern: cfg.bskyAppView.cdnUrlPattern,
185
186
  backgroundQueue,
@@ -67,6 +67,7 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos'
67
67
  import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate'
68
68
  import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
69
69
  import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
70
+ import * as ComAtprotoTempUpgradeRepoVersion from './types/com/atproto/temp/upgradeRepoVersion'
70
71
  import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
71
72
  import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
72
73
  import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
@@ -163,6 +164,7 @@ export class AtprotoNS {
163
164
  repo: RepoNS
164
165
  server: ServerNS
165
166
  sync: SyncNS
167
+ temp: TempNS
166
168
 
167
169
  constructor(server: Server) {
168
170
  this._server = server
@@ -173,6 +175,7 @@ export class AtprotoNS {
173
175
  this.repo = new RepoNS(server)
174
176
  this.server = new ServerNS(server)
175
177
  this.sync = new SyncNS(server)
178
+ this.temp = new TempNS(server)
176
179
  }
177
180
  }
178
181
 
@@ -870,6 +873,25 @@ export class SyncNS {
870
873
  }
871
874
  }
872
875
 
876
+ export class TempNS {
877
+ _server: Server
878
+
879
+ constructor(server: Server) {
880
+ this._server = server
881
+ }
882
+
883
+ upgradeRepoVersion<AV extends AuthVerifier>(
884
+ cfg: ConfigOf<
885
+ AV,
886
+ ComAtprotoTempUpgradeRepoVersion.Handler<ExtractAuth<AV>>,
887
+ ComAtprotoTempUpgradeRepoVersion.HandlerReqCtx<ExtractAuth<AV>>
888
+ >,
889
+ ) {
890
+ const nsid = 'com.atproto.temp.upgradeRepoVersion' // @ts-ignore
891
+ return this._server.xrpc.method(nsid, cfg)
892
+ }
893
+ }
894
+
873
895
  export class AppNS {
874
896
  _server: Server
875
897
  bsky: BskyNS
@@ -3517,6 +3517,32 @@ export const schemaDict = {
3517
3517
  },
3518
3518
  },
3519
3519
  },
3520
+ ComAtprotoTempUpgradeRepoVersion: {
3521
+ lexicon: 1,
3522
+ id: 'com.atproto.temp.upgradeRepoVersion',
3523
+ defs: {
3524
+ main: {
3525
+ type: 'procedure',
3526
+ description: 'Upgrade a repo to v3',
3527
+ input: {
3528
+ encoding: 'application/json',
3529
+ schema: {
3530
+ type: 'object',
3531
+ required: ['did'],
3532
+ properties: {
3533
+ did: {
3534
+ type: 'string',
3535
+ format: 'did',
3536
+ },
3537
+ force: {
3538
+ type: 'boolean',
3539
+ },
3540
+ },
3541
+ },
3542
+ },
3543
+ },
3544
+ },
3545
+ },
3520
3546
  AppBskyActorDefs: {
3521
3547
  lexicon: 1,
3522
3548
  id: 'app.bsky.actor.defs',
@@ -6838,6 +6864,7 @@ export const ids = {
6838
6864
  ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate',
6839
6865
  ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
6840
6866
  ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
6867
+ ComAtprotoTempUpgradeRepoVersion: 'com.atproto.temp.upgradeRepoVersion',
6841
6868
  AppBskyActorDefs: 'app.bsky.actor.defs',
6842
6869
  AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences',
6843
6870
  AppBskyActorGetProfile: 'app.bsky.actor.getProfile',
@@ -0,0 +1,39 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth } from '@atproto/xrpc-server'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ did: string
15
+ force?: boolean
16
+ [k: string]: unknown
17
+ }
18
+
19
+ export interface HandlerInput {
20
+ encoding: 'application/json'
21
+ body: InputSchema
22
+ }
23
+
24
+ export interface HandlerError {
25
+ status: number
26
+ message?: string
27
+ }
28
+
29
+ export type HandlerOutput = HandlerError | void
30
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
31
+ auth: HA
32
+ params: QueryParams
33
+ input: HandlerInput
34
+ req: express.Request
35
+ res: express.Response
36
+ }
37
+ export type Handler<HA extends HandlerAuth = never> = (
38
+ ctx: HandlerReqCtx<HA>,
39
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -14,6 +14,7 @@ import { LocalService } from './local'
14
14
  export function createServices(resources: {
15
15
  repoSigningKey: crypto.Keypair
16
16
  blobstore: BlobStore
17
+ pdsHostname: string
17
18
  appViewAgent?: AtpAgent
18
19
  appViewDid?: string
19
20
  appViewCdnUrlPattern?: string
@@ -23,6 +24,7 @@ export function createServices(resources: {
23
24
  const {
24
25
  repoSigningKey,
25
26
  blobstore,
27
+ pdsHostname,
26
28
  appViewAgent,
27
29
  appViewDid,
28
30
  appViewCdnUrlPattern,
@@ -41,6 +43,7 @@ export function createServices(resources: {
41
43
  ),
42
44
  local: LocalService.creator(
43
45
  repoSigningKey,
46
+ pdsHostname,
44
47
  appViewAgent,
45
48
  appViewDid,
46
49
  appViewCdnUrlPattern,
@@ -39,6 +39,7 @@ export class LocalService {
39
39
  constructor(
40
40
  public db: Database,
41
41
  public signingKey: Keypair,
42
+ public pdsHostname: string,
42
43
  public appviewAgent?: AtpAgent,
43
44
  public appviewDid?: string,
44
45
  public appviewCdnUrlPattern?: string,
@@ -46,6 +47,7 @@ export class LocalService {
46
47
 
47
48
  static creator(
48
49
  signingKey: Keypair,
50
+ pdsHostname: string,
49
51
  appviewAgent?: AtpAgent,
50
52
  appviewDid?: string,
51
53
  appviewCdnUrlPattern?: string,
@@ -54,6 +56,7 @@ export class LocalService {
54
56
  new LocalService(
55
57
  db,
56
58
  signingKey,
59
+ pdsHostname,
57
60
  appviewAgent,
58
61
  appviewDid,
59
62
  appviewCdnUrlPattern,
@@ -62,7 +65,7 @@ export class LocalService {
62
65
 
63
66
  getImageUrl(pattern: CommonSignedUris, did: string, cid: string) {
64
67
  if (!this.appviewCdnUrlPattern) {
65
- return ''
68
+ return `https://${this.pdsHostname}/xrpc/${ids.ComAtprotoSyncGetBlob}?did=${did}&cid=${cid}`
66
69
  }
67
70
  return util.format(this.appviewCdnUrlPattern, pattern, did, cid)
68
71
  }
package/test.log CHANGED
Binary file