@hatk/hatk 0.0.1-alpha.97 → 0.0.1-alpha.99

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/indexer.js CHANGED
@@ -4,7 +4,7 @@ import { rkeyOf } from "./spaces/uri.js";
4
4
  import { cborDecode } from "./cbor.js";
5
5
  import { parseCarFrame } from "./car.js";
6
6
  import { isPrivateCollection } from "./private-collections.js";
7
- import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoRetryInfo, listAllRepoStatuses, getDatabasePort, updateRepoHandle, } from "./database/db.js";
7
+ import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoStatus, purgeRepoRecords, getRepoRetryInfo, listAllRepoStatuses, getDatabasePort, updateRepoHandle, } from "./database/db.js";
8
8
  import { backfillRepo } from "./backfill.js";
9
9
  import { rebuildAllIndexes } from "./database/fts.js";
10
10
  import { log, emit, timer } from "./logger.js";
@@ -649,6 +649,61 @@ export async function handleIdentityEvent(did, payloadHandle) {
649
649
  });
650
650
  }
651
651
  }
652
+ /**
653
+ * Handle an `#account` event: the network saying an account is active again,
654
+ * or is not — deactivated, suspended, taken down by its host, or deleted.
655
+ *
656
+ * - deleted: every record it holds here is removed, and it is marked
657
+ * `deleted`.
658
+ * - deactivated, suspended, taken down by its host: marked `deactivated`. Its
659
+ * records stay, and no read serves them (see HIDDEN_REPO_STATUSES), so an
660
+ * account that comes back is whole again at once.
661
+ * - active again, after either: marked `pending` and backfilled, since it may
662
+ * have changed while it was gone.
663
+ *
664
+ * An admin's own takedown here wins over all of it: only reverse-takedown
665
+ * lifts that. Statuses that say nothing about the account itself (a relay's
666
+ * `desynchronized` or `throttled`) change nothing. Like identity events, only
667
+ * DIDs already tracked are touched.
668
+ */
669
+ export async function handleAccountEvent(did, active, status) {
670
+ if (!repoStatusCache.has(did))
671
+ return;
672
+ try {
673
+ const current = await getRepoStatus(did);
674
+ if (current === null || current === 'takendown')
675
+ return;
676
+ if (!active && status === 'deleted') {
677
+ const removed = await purgeRepoRecords(did);
678
+ await setRepoStatus(did, 'deleted');
679
+ repoStatusCache.set(did, 'deleted');
680
+ emit('indexer', 'account_deleted', { did, removed });
681
+ return;
682
+ }
683
+ if (!active && status && INACTIVE_ACCOUNT_STATUSES.has(status)) {
684
+ if (current === 'deactivated')
685
+ return;
686
+ await setRepoStatus(did, 'deactivated');
687
+ repoStatusCache.set(did, 'deactivated');
688
+ emit('indexer', 'account_deactivated', { did, status });
689
+ return;
690
+ }
691
+ if (active && (current === 'deactivated' || current === 'deleted')) {
692
+ await setRepoStatus(did, 'pending');
693
+ repoStatusCache.set(did, 'pending');
694
+ emit('indexer', 'account_reactivated', { did, was: current });
695
+ void triggerAutoBackfill(did);
696
+ }
697
+ }
698
+ catch (err) {
699
+ emit('indexer', 'account_event_error', {
700
+ did,
701
+ error: err instanceof Error ? err.message : String(err),
702
+ });
703
+ }
704
+ }
705
+ /** `#account` statuses that mean the account itself is not there. */
706
+ const INACTIVE_ACCOUNT_STATUSES = new Set(['deactivated', 'suspended', 'takendown']);
652
707
  /**
653
708
  * Whether a collection's records may be written from network data.
654
709
  *
@@ -751,6 +806,15 @@ export function processMessage(bytes, collections, onSeq = noteSeq) {
751
806
  handleIdentityEvent(did, handle);
752
807
  return;
753
808
  }
809
+ // Account status changes (deactivated, deleted, back again). Fire-and-forget,
810
+ // like identity.
811
+ if (header.value.t === '#account') {
812
+ const did = typeof body.value.did === 'string' ? body.value.did : undefined;
813
+ const status = typeof body.value.status === 'string' ? body.value.status : undefined;
814
+ if (did)
815
+ void handleAccountEvent(did, body.value.active === true, status);
816
+ return;
817
+ }
754
818
  if (header.value.op !== 1 || header.value.t !== '#commit')
755
819
  return;
756
820
  if (!body.value.blocks || !body.value.ops)
@@ -1 +1 @@
1
- {"version":3,"file":"jetstream.d.ts","sourceRoot":"","sources":["../src/jetstream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAUL,KAAK,QAAQ,EACb,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAGrB,sEAAsE;AACtE,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,QAAQ,QAAS,CAAA;AAO9B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,wDAAwD;AACxD,UAAU,eAAe;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAalG;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,QAAQ,GAAG,IAAI,CAY7F;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAuBzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC,MAAM,GAAG,IAAI,GAAG,SAAS,CAK3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CA0DjG"}
1
+ {"version":3,"file":"jetstream.d.ts","sourceRoot":"","sources":["../src/jetstream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAWL,KAAK,QAAQ,EACb,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAGrB,sEAAsE;AACtE,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,QAAQ,QAAS,CAAA;AAU9B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,wDAAwD;AACxD,UAAU,eAAe;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAC/B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAalG;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,QAAQ,GAAG,IAAI,CAY7F;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAiCzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC,MAAM,GAAG,IAAI,GAAG,SAAS,CAK3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CA0DjG"}
package/dist/jetstream.js CHANGED
@@ -14,13 +14,16 @@
14
14
  * @see https://bsky.network/docs/jetstream
15
15
  * @module
16
16
  */
17
- import { applyCommit, configureIndexer, getLastSeq, handleIdentityEvent, isIndexableCollection, jetstreamCursorKey, noteSeq, resumeCursor, setCursorKey, } from "./indexer.js";
17
+ import { applyCommit, configureIndexer, getLastSeq, handleIdentityEvent, handleAccountEvent, isIndexableCollection, jetstreamCursorKey, noteSeq, resumeCursor, setCursorKey, } from "./indexer.js";
18
18
  import { log, emit } from "./logger.js";
19
19
  /** Server-side filter caps, rejected before the WebSocket upgrade. */
20
20
  export const MAX_COLLECTIONS = 100;
21
21
  export const MAX_DIDS = 10_000;
22
- /** Event kinds hatk consumes. `commit` carries records; `identity` drives handle renames. */
23
- const KINDS = ['commit', 'identity'];
22
+ /**
23
+ * Event kinds hatk consumes. `commit` carries records; `identity` drives handle
24
+ * renames; `account` says an account was deactivated, deleted, or came back.
25
+ */
26
+ const KINDS = ['commit', 'identity', 'account'];
24
27
  const RECONNECT_DELAY_MS = 3000;
25
28
  /**
26
29
  * Refused handshakes before the next attempt drops its cursor and probes live.
@@ -118,6 +121,16 @@ export function processEvent(payload, collections) {
118
121
  handleIdentityEvent(did, handle);
119
122
  return;
120
123
  }
124
+ if (kind === 'account') {
125
+ // Jetstream nests the status: `{ did, seq, account: { active, status } }`,
126
+ // unlike the relay's flat #account frame.
127
+ const did = typeof payload.did === 'string' ? payload.did : undefined;
128
+ const account = payload.account ?? {};
129
+ const status = typeof account.status === 'string' ? account.status : undefined;
130
+ if (did)
131
+ void handleAccountEvent(did, account.active === true, status);
132
+ return;
133
+ }
121
134
  if (kind !== 'commit')
122
135
  return;
123
136
  // Track sequence for the cursor before filtering: a stream of events for
package/dist/main.js CHANGED
@@ -22,7 +22,6 @@ import { initFeeds, listFeeds } from "./feeds.js";
22
22
  import { initXrpc, listXrpc, configureRelay, configureCdn, configureOAuth, callXrpc } from "./xrpc.js";
23
23
  import { initOpengraph } from "./opengraph.js";
24
24
  import { initLabels, getLabelDefinitions } from "./labels.js";
25
- import { initClassifiers, listClassifiers, configureSystemOne } from "./classifiers.js";
26
25
  import { auxCursorKey, jetstreamCursorKey, startAuxIndexer, startIndexer, sweepReferences } from "./indexer.js";
27
26
  import { startJetstreamIndexer } from "./jetstream.js";
28
27
  import { rebuildAllIndexes } from "./database/fts.js";
@@ -109,11 +108,8 @@ else {
109
108
  log(`[main] OpenGraph initialized`);
110
109
  await initLabels(resolve(configDir, 'labels'));
111
110
  log(`[main] Labels initialized: ${getLabelDefinitions().length} definitions`);
112
- await initClassifiers(resolve(configDir, 'classifiers'));
113
- log(`[main] Classifiers initialized: ${listClassifiers().join(', ') || 'none'}`);
114
111
  }
115
112
  // Register built-in dev.hatk.* handlers so callXrpc() can find them
116
- configureSystemOne(config.typesafe);
117
113
  setPrivateCollections(config.privateCollections);
118
114
  registerCoreHandlers(collections, config.oauth);
119
115
  configureOAuth(config.oauth);
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA4EA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAuE9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CA2M3F;AAgDD,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,mFAAmF;IACnF,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmwC5F;AAGD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,WAAW,EAAE,MAAM,CAG5B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAsEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAuE9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CA2M3F;AAgDD,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,mFAAmF;IACnF,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0qC5F;AAGD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,WAAW,EAAE,MAAM,CAG5B"}
package/dist/server.js CHANGED
@@ -3,8 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { join, extname } from 'node:path';
5
5
  import { gzipSync } from 'node:zlib';
6
- import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getRepoStatusCounts, getDatabaseSize, deleteLabels, getRecentRecords, listActiveRepoDids, removeRepo, getRepoHandle, getPreferences, putPreference, insertReport, queryReports, resolveReport, getOpenReportCount, queryClassifications, getClassificationStats, getRecordsByDid, getRecordCountsByDid, findOpenReportsForSubject, } from "./database/db.js";
7
- import { runScan, getScanProgress, listClassifiers, isSystemOneConfigured } from "./classifiers.js";
6
+ import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getRepoStatusCounts, getDatabaseSize, deleteLabels, getRecentRecords, listActiveRepoDids, removeRepo, getRepoHandle, getPreferences, putPreference, insertReport, queryReports, resolveReport, getOpenReportCount, } from "./database/db.js";
8
7
  import { executeFeed, listFeeds } from "./feeds.js";
9
8
  import { executeXrpc, InvalidRequestError, NotFoundError, registerCoreXrpcHandler, isLocalRelay, paramsFromSearch } from "./xrpc.js";
10
9
  import { pdsFor } from "./backfill.js";
@@ -826,93 +825,6 @@ export function createHandler(config) {
826
825
  invalidateAdminStats();
827
826
  return withCors(json({ ok: true }, 200, acceptEncoding));
828
827
  }
829
- // GET /admin/review — the classifier review queue
830
- if (url.pathname === '/admin/review' && request.method === 'GET') {
831
- const denied = requireAdmin(viewer, acceptEncoding);
832
- if (denied)
833
- return denied;
834
- const result = await queryClassifications({
835
- classifier: url.searchParams.get('classifier') || undefined,
836
- signal: url.searchParams.get('signal') || undefined,
837
- minScore: url.searchParams.has('minScore') ? Number(url.searchParams.get('minScore')) : undefined,
838
- state: url.searchParams.get('state') || 'pending',
839
- limit: parseInt(url.searchParams.get('limit') || '25'),
840
- offset: parseInt(url.searchParams.get('offset') || '0'),
841
- });
842
- return withCors(json(result, 200, acceptEncoding));
843
- }
844
- // GET /admin/review/stats — rollups, classifier names, scan progress
845
- if (url.pathname === '/admin/review/stats' && request.method === 'GET') {
846
- const denied = requireAdmin(viewer, acceptEncoding);
847
- if (denied)
848
- return denied;
849
- const stats = await getClassificationStats();
850
- return withCors(json({ ...stats, classifiers: listClassifiers(), configured: isSystemOneConfigured(), scan: getScanProgress() }, 200, acceptEncoding));
851
- }
852
- // POST /admin/review/scan — start a scan. Returns as soon as it is under
853
- // way; a scan of a whole network outlives any sensible request timeout, so
854
- // progress is polled from /admin/review/stats rather than awaited here.
855
- if (url.pathname === '/admin/review/scan' && request.method === 'POST') {
856
- const denied = requireAdmin(viewer, acceptEncoding);
857
- if (denied)
858
- return denied;
859
- if (!isSystemOneConfigured())
860
- return withCors(jsonError(400, 'System One is not configured — set `typesafe` in hatk.config.ts', acceptEncoding));
861
- const body = JSON.parse((await request.text()) || '{}');
862
- const running = getScanProgress();
863
- if (running?.running)
864
- return withCors(jsonError(409, 'A scan is already running', acceptEncoding));
865
- runScan({ classifier: body.classifier, force: body.force === true, limit: body.limit })
866
- .then(() => invalidateAdminStats())
867
- .catch(() => { });
868
- return withCors(json({ ok: true, started: true }, 202, acceptEncoding));
869
- }
870
- // GET /admin/review/subject/:did — everything needed to judge one subject
871
- if (url.pathname.startsWith('/admin/review/subject/')) {
872
- const denied = requireAdmin(viewer, acceptEncoding);
873
- if (denied)
874
- return denied;
875
- const did = decodeURIComponent(url.pathname.slice('/admin/review/subject/'.length));
876
- if (!did.startsWith('did:'))
877
- return withCors(jsonError(400, 'Expected a DID', acceptEncoding));
878
- const [records, counts, handle, status, labels] = await Promise.all([
879
- getRecordsByDid(did, collections, 6),
880
- getRecordCountsByDid(did, collections),
881
- getRepoHandle(did),
882
- getRepoStatus(did),
883
- queryLabelsForUris([did]),
884
- ]);
885
- return withCors(json({ did, handle, status, records, counts, labels }, 200, acceptEncoding));
886
- }
887
- // POST /admin/review/act — resolve the report and act, in one step
888
- if (url.pathname === '/admin/review/act' && request.method === 'POST') {
889
- const denied = requireAdmin(viewer, acceptEncoding);
890
- if (denied)
891
- return denied;
892
- const { did, uri, action, label } = JSON.parse(await request.text());
893
- const subjectUri = uri || did;
894
- if (!subjectUri || !action)
895
- return withCors(jsonError(400, 'Missing subject or action', acceptEncoding));
896
- if (!['takedown', 'label', 'dismiss'].includes(action))
897
- return withCors(jsonError(400, 'Action must be takedown, label or dismiss', acceptEncoding));
898
- if (action === 'takedown') {
899
- if (!did)
900
- return withCors(jsonError(400, 'Takedown needs a did', acceptEncoding));
901
- await setRepoStatus(did, 'takendown');
902
- }
903
- else if (action === 'label') {
904
- if (!label)
905
- return withCors(jsonError(400, 'Label action needs a label', acceptEncoding));
906
- await insertLabels([{ src: 'admin', uri: subjectUri, val: label }]);
907
- }
908
- // Close whatever the classifier filed, so the subject leaves the queue
909
- // whichever way it was decided.
910
- for (const id of await findOpenReportsForSubject(subjectUri)) {
911
- await resolveReport(id, action === 'dismiss' ? 'dismissed' : 'resolved', viewer.did);
912
- }
913
- invalidateAdminStats();
914
- return withCors(json({ ok: true }, 200, acceptEncoding));
915
- }
916
828
  // GET /admin/info/:did — repo status info
917
829
  if (url.pathname.startsWith('/admin/info/did:')) {
918
830
  const denied = requireAdmin(viewer, acceptEncoding);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatk/hatk",
3
- "version": "0.0.1-alpha.97",
3
+ "version": "0.0.1-alpha.99",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "hatk": "dist/cli.js"
@@ -26,8 +26,7 @@
26
26
  "./config": "./dist/config.js",
27
27
  "./vite-plugin": "./dist/vite-plugin.js",
28
28
  "./renderer": "./dist/renderer.js",
29
- "./spaces": "./dist/spaces/index.js",
30
- "./classifiers": "./dist/classifiers.js"
29
+ "./spaces": "./dist/spaces/index.js"
31
30
  },
32
31
  "scripts": {
33
32
  "clean": "rm -rf dist",