.agent
src
modules
atproto
application
infrastructure
cards
domain
infrastructure
tests
···
55
55
```
56
56
57
57
**Design Notes:**
58
58
+
58
59
- No `removedBy` field - user identity is derived from the AT-URI of the removal record
59
60
- No `reason` field - kept the schema minimal and focused
60
61
- Uses StrongRef to ensure content-addressable references (URI + CID)
···
64
65
### Removal Permissions
65
66
66
67
**For OPEN Collections:**
68
68
+
67
69
- ✅ Collection author can remove any card (including cards added by others)
68
70
- ✅ Users can only remove cards they themselves added
69
71
- ❌ Non-authors cannot remove cards added by others
···
235
237
## Files Modified
236
238
237
239
### Created
240
240
+
238
241
1. `src/modules/atproto/infrastructure/lexicons/collectionLinkRemoval.json` - Lexicon schema
239
242
2. `src/modules/atproto/infrastructure/mappers/CollectionLinkRemovalMapper.ts` - Mapper for removal records
240
243
241
244
### Modified
245
245
+
242
246
1. `src/modules/cards/domain/Collection.ts` - Updated `removeCard()` method with proper permissions
243
247
2. `src/modules/cards/domain/services/CardCollectionService.ts` - Updated removal logic with publishing decisions
244
248
3. `src/modules/cards/application/ports/ICollectionPublisher.ts` - Added `publishCollectionLinkRemoval` method signature
···
249
253
8. `src/modules/cards/tests/application/RemoveCardFromCollectionUseCase.test.ts` - Updated/added tests
250
254
251
255
### Generated
256
256
+
252
257
- TypeScript types regenerated via `npm run lexgen`
253
258
254
259
## Test Coverage
255
260
256
261
### Test Results
262
262
+
257
263
- ✅ 20 tests passed in RemoveCardFromCollectionUseCase
258
264
- ✅ 2 tests skipped (CLOSED collections with collaborators - out of scope)
259
265
- ✅ 148 total collection-related tests passed
···
268
274
### Skipped Tests (Out of Scope)
269
275
270
276
The following tests were skipped as CLOSED collection scenarios with collaborators are deferred:
277
277
+
271
278
- `should allow collaborator to remove cards from closed collection`
272
279
- `should handle mixed collection permissions when removing cards`
273
280
···
283
290
## Future Work
284
291
285
292
### CLOSED Collections with Collaborators
293
293
+
286
294
Deferred for future implementation:
295
295
+
287
296
- How collaborators can remove cards from CLOSED collections
288
297
- Whether collaborators can remove any card or only their own
289
298
- Permission rules for multi-user CLOSED collection scenarios
290
299
291
300
### Potential Enhancements
301
301
+
292
302
- Add a `reason` field to collectionLinkRemoval for moderation use cases
293
303
- Implement bulk removal operations
294
304
- Add notifications when cards are removed from collections
···
85
85
const result = await this.createCollectionUseCase.execute({
86
86
name: request.record.name,
87
87
description: request.record.description,
88
88
-
accessType: request.record.accessType as CollectionAccessType | undefined,
88
88
+
accessType: request.record.accessType as
89
89
+
| CollectionAccessType
90
90
+
| undefined,
89
91
curatorId: authorDid,
90
92
publishedRecordId: publishedRecordId,
91
93
});
···
170
172
collectionId: collectionIdResult.value.getStringValue(),
171
173
name: request.record.name,
172
174
description: request.record.description,
173
173
-
accessType: request.record.accessType as CollectionAccessType | undefined,
175
175
+
accessType: request.record.accessType as
176
176
+
| CollectionAccessType
177
177
+
| undefined,
174
178
curatorId: authorDid,
175
179
publishedRecordId: publishedRecordId,
176
180
});
···
19
19
const ENABLE_FIREHOSE_LOGGING = true;
20
20
21
21
export class ProcessCollectionLinkRemovalFirehoseEventUseCase
22
22
-
implements
23
23
-
UseCase<ProcessCollectionLinkRemovalFirehoseEventDTO, Result<void>>
22
22
+
implements UseCase<ProcessCollectionLinkRemovalFirehoseEventDTO, Result<void>>
24
23
{
25
24
constructor(
26
25
private atUriResolutionService: IAtUriResolutionService,
···
137
137
!removalRecord.removedAt
138
138
) {
139
139
return err(
140
140
-
new ValidationError('Invalid collection link removal record structure'),
140
140
+
new ValidationError(
141
141
+
'Invalid collection link removal record structure',
142
142
+
),
141
143
);
142
144
}
143
145
}
···
7
7
type Options as XrpcOptions,
8
8
type AuthVerifier,
9
9
type StreamAuthVerifier,
10
10
-
} from '@atproto/xrpc-server'
11
11
-
import { schemas } from './lexicons.js'
10
10
+
} from '@atproto/xrpc-server';
11
11
+
import { schemas } from './lexicons.js';
12
12
13
13
export function createServer(options?: XrpcOptions): Server {
14
14
-
return new Server(options)
14
14
+
return new Server(options);
15
15
}
16
16
17
17
export class Server {
18
18
-
xrpc: XrpcServer
19
19
-
network: NetworkNS
20
20
-
com: ComNS
18
18
+
xrpc: XrpcServer;
19
19
+
network: NetworkNS;
20
20
+
com: ComNS;
21
21
22
22
constructor(options?: XrpcOptions) {
23
23
-
this.xrpc = createXrpcServer(schemas, options)
24
24
-
this.network = new NetworkNS(this)
25
25
-
this.com = new ComNS(this)
23
23
+
this.xrpc = createXrpcServer(schemas, options);
24
24
+
this.network = new NetworkNS(this);
25
25
+
this.com = new ComNS(this);
26
26
}
27
27
}
28
28
29
29
export class NetworkNS {
30
30
-
_server: Server
31
31
-
cosmik: NetworkCosmikNS
30
30
+
_server: Server;
31
31
+
cosmik: NetworkCosmikNS;
32
32
33
33
constructor(server: Server) {
34
34
-
this._server = server
35
35
-
this.cosmik = new NetworkCosmikNS(server)
34
34
+
this._server = server;
35
35
+
this.cosmik = new NetworkCosmikNS(server);
36
36
}
37
37
}
38
38
39
39
export class NetworkCosmikNS {
40
40
-
_server: Server
40
40
+
_server: Server;
41
41
42
42
constructor(server: Server) {
43
43
-
this._server = server
43
43
+
this._server = server;
44
44
}
45
45
}
46
46
47
47
export class ComNS {
48
48
-
_server: Server
49
49
-
atproto: ComAtprotoNS
48
48
+
_server: Server;
49
49
+
atproto: ComAtprotoNS;
50
50
51
51
constructor(server: Server) {
52
52
-
this._server = server
53
53
-
this.atproto = new ComAtprotoNS(server)
52
52
+
this._server = server;
53
53
+
this.atproto = new ComAtprotoNS(server);
54
54
}
55
55
}
56
56
57
57
export class ComAtprotoNS {
58
58
-
_server: Server
59
59
-
repo: ComAtprotoRepoNS
58
58
+
_server: Server;
59
59
+
repo: ComAtprotoRepoNS;
60
60
61
61
constructor(server: Server) {
62
62
-
this._server = server
63
63
-
this.repo = new ComAtprotoRepoNS(server)
62
62
+
this._server = server;
63
63
+
this.repo = new ComAtprotoRepoNS(server);
64
64
}
65
65
}
66
66
67
67
export class ComAtprotoRepoNS {
68
68
-
_server: Server
68
68
+
_server: Server;
69
69
70
70
constructor(server: Server) {
71
71
-
this._server = server
71
71
+
this._server = server;
72
72
}
73
73
}
74
74
75
75
type SharedRateLimitOpts<T> = {
76
76
-
name: string
77
77
-
calcKey?: (ctx: T) => string | null
78
78
-
calcPoints?: (ctx: T) => number
79
79
-
}
76
76
+
name: string;
77
77
+
calcKey?: (ctx: T) => string | null;
78
78
+
calcPoints?: (ctx: T) => number;
79
79
+
};
80
80
type RouteRateLimitOpts<T> = {
81
81
-
durationMs: number
82
82
-
points: number
83
83
-
calcKey?: (ctx: T) => string | null
84
84
-
calcPoints?: (ctx: T) => number
85
85
-
}
86
86
-
type HandlerOpts = { blobLimit?: number }
87
87
-
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
81
81
+
durationMs: number;
82
82
+
points: number;
83
83
+
calcKey?: (ctx: T) => string | null;
84
84
+
calcPoints?: (ctx: T) => number;
85
85
+
};
86
86
+
type HandlerOpts = { blobLimit?: number };
87
87
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>;
88
88
type ConfigOf<Auth, Handler, ReqCtx> =
89
89
| Handler
90
90
| {
91
91
-
auth?: Auth
92
92
-
opts?: HandlerOpts
93
93
-
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
94
94
-
handler: Handler
95
95
-
}
91
91
+
auth?: Auth;
92
92
+
opts?: HandlerOpts;
93
93
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[];
94
94
+
handler: Handler;
95
95
+
};
96
96
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
97
97
Awaited<ReturnType<AV>>,
98
98
{ credentials: unknown }
99
99
-
>
99
99
+
>;
···
6
6
Lexicons,
7
7
ValidationError,
8
8
type ValidationResult,
9
9
-
} from '@atproto/lexicon'
10
10
-
import { type $Typed, is$typed, maybe$typed } from './util.js'
9
9
+
} from '@atproto/lexicon';
10
10
+
import { type $Typed, is$typed, maybe$typed } from './util.js';
11
11
12
12
export const schemaDict = {
13
13
NetworkCosmikCard: {
···
337
337
},
338
338
},
339
339
},
340
340
-
} as const satisfies Record<string, LexiconDoc>
341
341
-
export const schemas = Object.values(schemaDict) satisfies LexiconDoc[]
342
342
-
export const lexicons: Lexicons = new Lexicons(schemas)
340
340
+
} as const satisfies Record<string, LexiconDoc>;
341
341
+
export const schemas = Object.values(schemaDict) satisfies LexiconDoc[];
342
342
+
export const lexicons: Lexicons = new Lexicons(schemas);
343
343
344
344
export function validate<T extends { $type: string }>(
345
345
v: unknown,
346
346
id: string,
347
347
hash: string,
348
348
requiredType: true,
349
349
-
): ValidationResult<T>
349
349
+
): ValidationResult<T>;
350
350
export function validate<T extends { $type?: string }>(
351
351
v: unknown,
352
352
id: string,
353
353
hash: string,
354
354
requiredType?: false,
355
355
-
): ValidationResult<T>
355
355
+
): ValidationResult<T>;
356
356
export function validate(
357
357
v: unknown,
358
358
id: string,
···
366
366
error: new ValidationError(
367
367
`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
368
368
),
369
369
-
}
369
369
+
};
370
370
}
371
371
372
372
export const ids = {
···
376
376
NetworkCosmikCollectionLinkRemoval: 'network.cosmik.collectionLinkRemoval',
377
377
NetworkCosmikDefs: 'network.cosmik.defs',
378
378
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
379
379
-
} as const
379
379
+
} as const;
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../../lexicons'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../../lexicons';
7
7
import {
8
8
type $Typed,
9
9
is$typed as _is$typed,
10
10
type OmitKey,
11
11
-
} from '../../../../util'
11
11
+
} from '../../../../util';
12
12
13
13
const is$typed = _is$typed,
14
14
-
validate = _validate
15
15
-
const id = 'com.atproto.repo.strongRef'
14
14
+
validate = _validate;
15
15
+
const id = 'com.atproto.repo.strongRef';
16
16
17
17
export interface Main {
18
18
-
$type?: 'com.atproto.repo.strongRef'
19
19
-
cid: string
20
20
-
uri: string
18
18
+
$type?: 'com.atproto.repo.strongRef';
19
19
+
cid: string;
20
20
+
uri: string;
21
21
}
22
22
23
23
-
const hashMain = 'main'
23
23
+
const hashMain = 'main';
24
24
25
25
export function isMain<V>(v: V) {
26
26
-
return is$typed(v, id, hashMain)
26
26
+
return is$typed(v, id, hashMain);
27
27
}
28
28
29
29
export function validateMain<V>(v: V) {
30
30
-
return validate<Main & V>(v, id, hashMain)
30
30
+
return validate<Main & V>(v, id, hashMain);
31
31
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
9
9
-
import type * as NetworkCosmikDefs from './defs.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
13
13
+
import type * as NetworkCosmikDefs from './defs.js';
10
14
11
15
const is$typed = _is$typed,
12
12
-
validate = _validate
13
13
-
const id = 'network.cosmik.card'
16
16
+
validate = _validate;
17
17
+
const id = 'network.cosmik.card';
14
18
15
19
export interface Record {
16
16
-
$type: 'network.cosmik.card'
20
20
+
$type: 'network.cosmik.card';
17
21
/** The type of card */
18
18
-
type: 'URL' | 'NOTE' | (string & {})
19
19
-
content: $Typed<UrlContent> | $Typed<NoteContent> | { $type: string }
22
22
+
type: 'URL' | 'NOTE' | (string & {});
23
23
+
content: $Typed<UrlContent> | $Typed<NoteContent> | { $type: string };
20
24
/** Optional URL associated with the card. Required for URL cards, optional for NOTE cards. */
21
21
-
url?: string
22
22
-
parentCard?: ComAtprotoRepoStrongRef.Main
25
25
+
url?: string;
26
26
+
parentCard?: ComAtprotoRepoStrongRef.Main;
23
27
/** Timestamp when this card was created (usually set by PDS). */
24
24
-
createdAt?: string
25
25
-
originalCard?: ComAtprotoRepoStrongRef.Main
26
26
-
provenance?: NetworkCosmikDefs.Provenance
27
27
-
[k: string]: unknown
28
28
+
createdAt?: string;
29
29
+
originalCard?: ComAtprotoRepoStrongRef.Main;
30
30
+
provenance?: NetworkCosmikDefs.Provenance;
31
31
+
[k: string]: unknown;
28
32
}
29
33
30
30
-
const hashRecord = 'main'
34
34
+
const hashRecord = 'main';
31
35
32
36
export function isRecord<V>(v: V) {
33
33
-
return is$typed(v, id, hashRecord)
37
37
+
return is$typed(v, id, hashRecord);
34
38
}
35
39
36
40
export function validateRecord<V>(v: V) {
37
37
-
return validate<Record & V>(v, id, hashRecord, true)
41
41
+
return validate<Record & V>(v, id, hashRecord, true);
38
42
}
39
43
40
44
/** Content structure for a URL card. */
41
45
export interface UrlContent {
42
42
-
$type?: 'network.cosmik.card#urlContent'
46
46
+
$type?: 'network.cosmik.card#urlContent';
43
47
/** The URL being saved */
44
44
-
url: string
45
45
-
metadata?: UrlMetadata
48
48
+
url: string;
49
49
+
metadata?: UrlMetadata;
46
50
}
47
51
48
48
-
const hashUrlContent = 'urlContent'
52
52
+
const hashUrlContent = 'urlContent';
49
53
50
54
export function isUrlContent<V>(v: V) {
51
51
-
return is$typed(v, id, hashUrlContent)
55
55
+
return is$typed(v, id, hashUrlContent);
52
56
}
53
57
54
58
export function validateUrlContent<V>(v: V) {
55
55
-
return validate<UrlContent & V>(v, id, hashUrlContent)
59
59
+
return validate<UrlContent & V>(v, id, hashUrlContent);
56
60
}
57
61
58
62
/** Content structure for a note card. */
59
63
export interface NoteContent {
60
60
-
$type?: 'network.cosmik.card#noteContent'
64
64
+
$type?: 'network.cosmik.card#noteContent';
61
65
/** The note text content */
62
62
-
text: string
66
66
+
text: string;
63
67
}
64
68
65
65
-
const hashNoteContent = 'noteContent'
69
69
+
const hashNoteContent = 'noteContent';
66
70
67
71
export function isNoteContent<V>(v: V) {
68
68
-
return is$typed(v, id, hashNoteContent)
72
72
+
return is$typed(v, id, hashNoteContent);
69
73
}
70
74
71
75
export function validateNoteContent<V>(v: V) {
72
72
-
return validate<NoteContent & V>(v, id, hashNoteContent)
76
76
+
return validate<NoteContent & V>(v, id, hashNoteContent);
73
77
}
74
78
75
79
/** Metadata about a URL. */
76
80
export interface UrlMetadata {
77
77
-
$type?: 'network.cosmik.card#urlMetadata'
81
81
+
$type?: 'network.cosmik.card#urlMetadata';
78
82
/** Title of the page */
79
79
-
title?: string
83
83
+
title?: string;
80
84
/** Description of the page */
81
81
-
description?: string
85
85
+
description?: string;
82
86
/** Author of the content */
83
83
-
author?: string
87
87
+
author?: string;
84
88
/** When the content was published */
85
85
-
publishedDate?: string
89
89
+
publishedDate?: string;
86
90
/** Name of the site */
87
87
-
siteName?: string
91
91
+
siteName?: string;
88
92
/** URL of a representative image */
89
89
-
imageUrl?: string
93
93
+
imageUrl?: string;
90
94
/** Type of content (e.g., 'video', 'article') */
91
91
-
type?: string
95
95
+
type?: string;
92
96
/** When the metadata was retrieved */
93
93
-
retrievedAt?: string
97
97
+
retrievedAt?: string;
94
98
/** Digital Object Identifier (DOI) for academic content */
95
95
-
doi?: string
99
99
+
doi?: string;
96
100
/** International Standard Book Number (ISBN) for books */
97
97
-
isbn?: string
101
101
+
isbn?: string;
98
102
}
99
103
100
100
-
const hashUrlMetadata = 'urlMetadata'
104
104
+
const hashUrlMetadata = 'urlMetadata';
101
105
102
106
export function isUrlMetadata<V>(v: V) {
103
103
-
return is$typed(v, id, hashUrlMetadata)
107
107
+
return is$typed(v, id, hashUrlMetadata);
104
108
}
105
109
106
110
export function validateUrlMetadata<V>(v: V) {
107
107
-
return validate<UrlMetadata & V>(v, id, hashUrlMetadata)
111
111
+
return validate<UrlMetadata & V>(v, id, hashUrlMetadata);
108
112
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
8
12
9
13
const is$typed = _is$typed,
10
10
-
validate = _validate
11
11
-
const id = 'network.cosmik.collection'
14
14
+
validate = _validate;
15
15
+
const id = 'network.cosmik.collection';
12
16
13
17
export interface Record {
14
14
-
$type: 'network.cosmik.collection'
18
18
+
$type: 'network.cosmik.collection';
15
19
/** Name of the collection */
16
16
-
name: string
20
20
+
name: string;
17
21
/** Description of the collection */
18
18
-
description?: string
22
22
+
description?: string;
19
23
/** Access control for the collection */
20
20
-
accessType: 'OPEN' | 'CLOSED' | (string & {})
24
24
+
accessType: 'OPEN' | 'CLOSED' | (string & {});
21
25
/** List of collaborator DIDs who can add cards to closed collections */
22
22
-
collaborators?: string[]
26
26
+
collaborators?: string[];
23
27
/** Timestamp when this collection was created (usually set by PDS). */
24
24
-
createdAt?: string
28
28
+
createdAt?: string;
25
29
/** Timestamp when this collection was last updated. */
26
26
-
updatedAt?: string
27
27
-
[k: string]: unknown
30
30
+
updatedAt?: string;
31
31
+
[k: string]: unknown;
28
32
}
29
33
30
30
-
const hashRecord = 'main'
34
34
+
const hashRecord = 'main';
31
35
32
36
export function isRecord<V>(v: V) {
33
33
-
return is$typed(v, id, hashRecord)
37
37
+
return is$typed(v, id, hashRecord);
34
38
}
35
39
36
40
export function validateRecord<V>(v: V) {
37
37
-
return validate<Record & V>(v, id, hashRecord, true)
41
41
+
return validate<Record & V>(v, id, hashRecord, true);
38
42
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
9
9
-
import type * as NetworkCosmikDefs from './defs.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
13
13
+
import type * as NetworkCosmikDefs from './defs.js';
10
14
11
15
const is$typed = _is$typed,
12
12
-
validate = _validate
13
13
-
const id = 'network.cosmik.collectionLink'
16
16
+
validate = _validate;
17
17
+
const id = 'network.cosmik.collectionLink';
14
18
15
19
export interface Record {
16
16
-
$type: 'network.cosmik.collectionLink'
17
17
-
collection: ComAtprotoRepoStrongRef.Main
18
18
-
card: ComAtprotoRepoStrongRef.Main
19
19
-
originalCard?: ComAtprotoRepoStrongRef.Main
20
20
+
$type: 'network.cosmik.collectionLink';
21
21
+
collection: ComAtprotoRepoStrongRef.Main;
22
22
+
card: ComAtprotoRepoStrongRef.Main;
23
23
+
originalCard?: ComAtprotoRepoStrongRef.Main;
20
24
/** DID of the user who added the card to the collection */
21
21
-
addedBy: string
25
25
+
addedBy: string;
22
26
/** Timestamp when the card was added to the collection. */
23
23
-
addedAt: string
27
27
+
addedAt: string;
24
28
/** Timestamp when this link record was created (usually set by PDS). */
25
25
-
createdAt?: string
26
26
-
provenance?: NetworkCosmikDefs.Provenance
27
27
-
[k: string]: unknown
29
29
+
createdAt?: string;
30
30
+
provenance?: NetworkCosmikDefs.Provenance;
31
31
+
[k: string]: unknown;
28
32
}
29
33
30
30
-
const hashRecord = 'main'
34
34
+
const hashRecord = 'main';
31
35
32
36
export function isRecord<V>(v: V) {
33
33
-
return is$typed(v, id, hashRecord)
37
37
+
return is$typed(v, id, hashRecord);
34
38
}
35
39
36
40
export function validateRecord<V>(v: V) {
37
37
-
return validate<Record & V>(v, id, hashRecord, true)
41
41
+
return validate<Record & V>(v, id, hashRecord, true);
38
42
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
9
13
10
14
const is$typed = _is$typed,
11
11
-
validate = _validate
12
12
-
const id = 'network.cosmik.collectionLinkRemoval'
15
15
+
validate = _validate;
16
16
+
const id = 'network.cosmik.collectionLinkRemoval';
13
17
14
18
export interface Record {
15
15
-
$type: 'network.cosmik.collectionLinkRemoval'
16
16
-
collection: ComAtprotoRepoStrongRef.Main
17
17
-
removedLink: ComAtprotoRepoStrongRef.Main
19
19
+
$type: 'network.cosmik.collectionLinkRemoval';
20
20
+
collection: ComAtprotoRepoStrongRef.Main;
21
21
+
removedLink: ComAtprotoRepoStrongRef.Main;
18
22
/** Timestamp when the link was removed from the collection. */
19
19
-
removedAt: string
20
20
-
[k: string]: unknown
23
23
+
removedAt: string;
24
24
+
[k: string]: unknown;
21
25
}
22
26
23
23
-
const hashRecord = 'main'
27
27
+
const hashRecord = 'main';
24
28
25
29
export function isRecord<V>(v: V) {
26
26
-
return is$typed(v, id, hashRecord)
30
30
+
return is$typed(v, id, hashRecord);
27
31
}
28
32
29
33
export function validateRecord<V>(v: V) {
30
30
-
return validate<Record & V>(v, id, hashRecord, true)
34
34
+
return validate<Record & V>(v, id, hashRecord, true);
31
35
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
9
13
10
14
const is$typed = _is$typed,
11
11
-
validate = _validate
12
12
-
const id = 'network.cosmik.defs'
15
15
+
validate = _validate;
16
16
+
const id = 'network.cosmik.defs';
13
17
14
18
/** Represents the provenance or source of a record. */
15
19
export interface Provenance {
16
16
-
$type?: 'network.cosmik.defs#provenance'
17
17
-
via?: ComAtprotoRepoStrongRef.Main
20
20
+
$type?: 'network.cosmik.defs#provenance';
21
21
+
via?: ComAtprotoRepoStrongRef.Main;
18
22
}
19
23
20
20
-
const hashProvenance = 'provenance'
24
24
+
const hashProvenance = 'provenance';
21
25
22
26
export function isProvenance<V>(v: V) {
23
23
-
return is$typed(v, id, hashProvenance)
27
27
+
return is$typed(v, id, hashProvenance);
24
28
}
25
29
26
30
export function validateProvenance<V>(v: V) {
27
27
-
return validate<Provenance & V>(v, id, hashProvenance)
31
31
+
return validate<Provenance & V>(v, id, hashProvenance);
28
32
}
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
5
5
-
import { type ValidationResult } from '@atproto/lexicon'
5
5
+
import { type ValidationResult } from '@atproto/lexicon';
6
6
7
7
export type OmitKey<T, K extends keyof T> = {
8
8
-
[K2 in keyof T as K2 extends K ? never : K2]: T[K2]
9
9
-
}
8
8
+
[K2 in keyof T as K2 extends K ? never : K2]: T[K2];
9
9
+
};
10
10
11
11
-
export type $Typed<V, T extends string = string> = V & { $type: T }
12
12
-
export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>
11
11
+
export type $Typed<V, T extends string = string> = V & { $type: T };
12
12
+
export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>;
13
13
14
14
export type $Type<Id extends string, Hash extends string> = Hash extends 'main'
15
15
? Id
16
16
-
: `${Id}#${Hash}`
16
16
+
: `${Id}#${Hash}`;
17
17
18
18
function isObject<V>(v: V): v is V & object {
19
19
-
return v != null && typeof v === 'object'
19
19
+
return v != null && typeof v === 'object';
20
20
}
21
21
22
22
function is$type<Id extends string, Hash extends string>(
···
31
31
$type.length === id.length + 1 + hash.length &&
32
32
$type.charCodeAt(id.length) === 35 /* '#' */ &&
33
33
$type.startsWith(id) &&
34
34
-
$type.endsWith(hash)
34
34
+
$type.endsWith(hash);
35
35
}
36
36
37
37
export type $TypedObject<
···
39
39
Id extends string,
40
40
Hash extends string,
41
41
> = V extends {
42
42
-
$type: $Type<Id, Hash>
42
42
+
$type: $Type<Id, Hash>;
43
43
}
44
44
? V
45
45
: V extends { $type?: string }
46
46
? V extends { $type?: infer T extends $Type<Id, Hash> }
47
47
? V & { $type: T }
48
48
: never
49
49
-
: V & { $type: $Type<Id, Hash> }
49
49
+
: V & { $type: $Type<Id, Hash> };
50
50
51
51
export function is$typed<V, Id extends string, Hash extends string>(
52
52
v: V,
53
53
id: Id,
54
54
hash: Hash,
55
55
): v is $TypedObject<V, Id, Hash> {
56
56
-
return isObject(v) && '$type' in v && is$type(v.$type, id, hash)
56
56
+
return isObject(v) && '$type' in v && is$type(v.$type, id, hash);
57
57
}
58
58
59
59
export function maybe$typed<V, Id extends string, Hash extends string>(
···
64
64
return (
65
65
isObject(v) &&
66
66
('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true)
67
67
-
)
67
67
+
);
68
68
}
69
69
70
70
-
export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>
70
70
+
export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>;
71
71
export type ValidatorParam<V extends Validator> =
72
72
-
V extends Validator<infer R> ? R : never
72
72
+
V extends Validator<infer R> ? R : never;
73
73
74
74
/**
75
75
* Utility function that allows to convert a "validate*" utility function into a
···
77
77
*/
78
78
export function asPredicate<V extends Validator>(validate: V) {
79
79
return function <T>(v: T): v is T & ValidatorParam<V> {
80
80
-
return validate(v).success
81
81
-
}
80
80
+
return validate(v).success;
81
81
+
};
82
82
}
···
59
59
export type CollectionCardQueryResultDTO = UrlCardView;
60
60
// Raw data from repository - minimal, just what's stored
61
61
export interface WithCollections {
62
62
-
collections: { id: string; name: string; authorId: string; accessType: string }[];
62
62
+
collections: {
63
63
+
id: string;
64
64
+
name: string;
65
65
+
authorId: string;
66
66
+
accessType: string;
67
67
+
}[];
63
68
}
64
69
65
70
export interface WithLibraries {
···
51
51
router.get(
52
52
'/contributed/:identifier',
53
53
authMiddleware.optionalAuth(),
54
54
-
(req, res) =>
55
55
-
getOpenCollectionsWithContributorController.execute(req, res),
54
54
+
(req, res) => getOpenCollectionsWithContributorController.execute(req, res),
56
55
);
57
56
58
57
// GET /api/collections/at/:handle/:recordKey - Get collection by AT URI
···
110
110
111
111
// Find collections this card belongs to by querying the collection repository
112
112
const allCollections = this.collectionRepository.getAllCollections();
113
113
-
const collections: { id: string; name: string; authorId: string; accessType: string }[] = [];
113
113
+
const collections: {
114
114
+
id: string;
115
115
+
name: string;
116
116
+
authorId: string;
117
117
+
accessType: string;
118
118
+
}[] = [];
114
119
115
120
for (const collection of allCollections) {
116
121
if (