tangled
alpha
login
or
join now
cosmik.network
/
semble
43
fork
atom
A social knowledge tool for researchers built on ATProto
43
fork
atom
overview
issues
13
pulls
pipelines
use cardId for update url card associations
Wesley Finck
5 months ago
982146f9
79cc7340
+68
-25
3 changed files
expand all
collapse all
unified
split
src
modules
cards
application
useCases
commands
UpdateUrlCardAssociationsUseCase.ts
infrastructure
http
controllers
UpdateUrlCardAssociationsController.ts
tests
application
UpdateUrlCardAssociationsUseCase.test.ts
+33
-9
src/modules/cards/application/useCases/commands/UpdateUrlCardAssociationsUseCase.ts
···
5
import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher';
6
import { ICardRepository } from '../../../domain/ICardRepository';
7
import { INoteCardInput } from '../../../domain/CardFactory';
0
8
import { CollectionId } from '../../../domain/value-objects/CollectionId';
9
import { CuratorId } from '../../../domain/value-objects/CuratorId';
10
import { CardTypeEnum } from '../../../domain/value-objects/CardType';
···
15
import { CardLibraryService } from '../../../domain/services/CardLibraryService';
16
17
export interface UpdateUrlCardAssociationsDTO {
18
-
url: string;
19
curatorId: string;
20
note?: string;
21
addToCollections?: string[];
···
71
}
72
const curatorId = curatorIdResult.value;
73
74
-
// Validate URL
75
-
const urlResult = URL.create(request.url);
76
-
if (urlResult.isErr()) {
77
return err(
78
-
new ValidationError(`Invalid URL: ${urlResult.error.message}`),
0
0
79
);
80
}
81
-
const url = urlResult.value;
82
83
// Find the URL card - it must already exist
84
-
const existingUrlCardResult =
85
-
await this.cardRepository.findUsersUrlCardByUrl(url, curatorId);
86
if (existingUrlCardResult.isErr()) {
87
return err(
88
AppError.UnexpectedError.create(existingUrlCardResult.error),
···
98
);
99
}
100
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
101
let noteCard;
102
103
// Handle note updates/creation
···
141
type: CardTypeEnum.NOTE,
142
text: request.note,
143
parentCardId: urlCard.cardId.getStringValue(),
144
-
url: request.url,
145
};
146
147
const noteCardResult = CardFactory.create({
···
5
import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher';
6
import { ICardRepository } from '../../../domain/ICardRepository';
7
import { INoteCardInput } from '../../../domain/CardFactory';
8
+
import { CardId } from '../../../domain/value-objects/CardId';
9
import { CollectionId } from '../../../domain/value-objects/CollectionId';
10
import { CuratorId } from '../../../domain/value-objects/CuratorId';
11
import { CardTypeEnum } from '../../../domain/value-objects/CardType';
···
16
import { CardLibraryService } from '../../../domain/services/CardLibraryService';
17
18
export interface UpdateUrlCardAssociationsDTO {
19
+
cardId: string;
20
curatorId: string;
21
note?: string;
22
addToCollections?: string[];
···
72
}
73
const curatorId = curatorIdResult.value;
74
75
+
// Validate and create CardId
76
+
const cardIdResult = CardId.createFromString(request.cardId);
77
+
if (cardIdResult.isErr()) {
78
return err(
79
+
new ValidationError(
80
+
`Invalid card ID: ${cardIdResult.error.message}`,
81
+
),
82
);
83
}
84
+
const cardId = cardIdResult.value;
85
86
// Find the URL card - it must already exist
87
+
const existingUrlCardResult = await this.cardRepository.findById(cardId);
0
88
if (existingUrlCardResult.isErr()) {
89
return err(
90
AppError.UnexpectedError.create(existingUrlCardResult.error),
···
100
);
101
}
102
103
+
// Verify it's a URL card
104
+
if (!urlCard.isUrlCard) {
105
+
return err(
106
+
new ValidationError('Card must be a URL card to update associations.'),
107
+
);
108
+
}
109
+
110
+
// Verify ownership
111
+
if (!urlCard.curatorId.equals(curatorId)) {
112
+
return err(
113
+
new ValidationError('You do not have permission to update this card.'),
114
+
);
115
+
}
116
+
117
+
// Get the URL from the card for note operations
118
+
if (!urlCard.url) {
119
+
return err(
120
+
new ValidationError('URL card must have a URL property.'),
121
+
);
122
+
}
123
+
const url = urlCard.url;
124
+
125
let noteCard;
126
127
// Handle note updates/creation
···
165
type: CardTypeEnum.NOTE,
166
text: request.note,
167
parentCardId: urlCard.cardId.getStringValue(),
168
+
url: url.value,
169
};
170
171
const noteCardResult = CardFactory.create({
+5
-4
src/modules/cards/infrastructure/http/controllers/UpdateUrlCardAssociationsController.ts
···
12
13
async executeImpl(req: AuthenticatedRequest, res: Response): Promise<any> {
14
try {
15
-
const { url, note, addToCollections, removeFromCollections } = req.body;
0
16
const curatorId = req.did;
17
18
if (!curatorId) {
19
return this.unauthorized(res);
20
}
21
22
-
if (!url) {
23
-
return this.badRequest(res, 'URL is required');
24
}
25
26
const result = await this.updateUrlCardAssociationsUseCase.execute({
27
-
url,
28
curatorId,
29
note,
30
addToCollections,
···
12
13
async executeImpl(req: AuthenticatedRequest, res: Response): Promise<any> {
14
try {
15
+
const { cardId, note, addToCollections, removeFromCollections } =
16
+
req.body;
17
const curatorId = req.did;
18
19
if (!curatorId) {
20
return this.unauthorized(res);
21
}
22
23
+
if (!cardId) {
24
+
return this.badRequest(res, 'Card ID is required');
25
}
26
27
const result = await this.updateUrlCardAssociationsUseCase.execute({
28
+
cardId,
29
curatorId,
30
note,
31
addToCollections,
+30
-12
src/modules/cards/tests/application/UpdateUrlCardAssociationsUseCase.test.ts
···
81
curatorId: curatorId.value,
82
});
83
expect(addResult.isOk()).toBe(true);
0
84
85
// Now create a note for it
86
const updateRequest = {
87
-
url,
88
curatorId: curatorId.value,
89
note: 'This is my note',
90
};
···
118
119
// Now update the note
120
const updateRequest = {
121
-
url,
122
curatorId: curatorId.value,
123
note: 'Updated note',
124
};
···
140
141
it('should fail if URL card does not exist', async () => {
142
const request = {
143
-
url: 'https://example.com/nonexistent',
144
curatorId: curatorId.value,
145
note: 'This should fail',
146
};
···
183
curatorId: curatorId.value,
184
});
185
expect(addResult.isOk()).toBe(true);
0
186
187
// Add to collections
188
const updateRequest = {
189
-
url,
190
curatorId: curatorId.value,
191
addToCollections: [
192
collection1.collectionId.getStringValue(),
···
229
curatorId: curatorId.value,
230
});
231
expect(addResult.isOk()).toBe(true);
0
232
233
// Remove from collection
234
const updateRequest = {
235
-
url,
236
curatorId: curatorId.value,
237
removeFromCollections: [collection.collectionId.getStringValue()],
238
};
···
283
curatorId: curatorId.value,
284
});
285
expect(addResult.isOk()).toBe(true);
0
286
287
// Add to collection2 and collection3, remove from collection1
288
const updateRequest = {
289
-
url,
290
curatorId: curatorId.value,
291
addToCollections: [
292
collection2.collectionId.getStringValue(),
···
335
curatorId: curatorId.value,
336
});
337
expect(addResult.isOk()).toBe(true);
0
338
339
// Update note and add to collection
340
const updateRequest = {
341
-
url,
342
curatorId: curatorId.value,
343
note: 'My note about this article',
344
addToCollections: [collection.collectionId.getStringValue()],
···
367
});
368
369
describe('Validation', () => {
370
-
it('should fail with invalid URL', async () => {
371
const request = {
372
-
url: 'not-a-valid-url',
373
curatorId: curatorId.value,
374
note: 'This should fail',
375
};
···
378
379
expect(result.isErr()).toBe(true);
380
if (result.isErr()) {
381
-
expect(result.error.message).toContain('Invalid URL');
0
0
382
}
383
});
384
385
it('should fail with invalid curator ID', async () => {
0
0
0
0
0
0
0
0
0
0
386
const request = {
387
-
url: 'https://example.com/article',
388
curatorId: 'invalid-curator-id',
389
note: 'This should fail',
390
};
···
406
curatorId: curatorId.value,
407
});
408
expect(addResult.isOk()).toBe(true);
0
409
410
const request = {
411
-
url,
412
curatorId: curatorId.value,
413
addToCollections: ['invalid-collection-id'],
414
};
···
81
curatorId: curatorId.value,
82
});
83
expect(addResult.isOk()).toBe(true);
84
+
const urlCardId = addResult.unwrap().urlCardId;
85
86
// Now create a note for it
87
const updateRequest = {
88
+
cardId: urlCardId,
89
curatorId: curatorId.value,
90
note: 'This is my note',
91
};
···
119
120
// Now update the note
121
const updateRequest = {
122
+
cardId: addResponse.urlCardId,
123
curatorId: curatorId.value,
124
note: 'Updated note',
125
};
···
141
142
it('should fail if URL card does not exist', async () => {
143
const request = {
144
+
cardId: 'nonexistent-card-id',
145
curatorId: curatorId.value,
146
note: 'This should fail',
147
};
···
184
curatorId: curatorId.value,
185
});
186
expect(addResult.isOk()).toBe(true);
187
+
const urlCardId = addResult.unwrap().urlCardId;
188
189
// Add to collections
190
const updateRequest = {
191
+
cardId: urlCardId,
192
curatorId: curatorId.value,
193
addToCollections: [
194
collection1.collectionId.getStringValue(),
···
231
curatorId: curatorId.value,
232
});
233
expect(addResult.isOk()).toBe(true);
234
+
const urlCardId = addResult.unwrap().urlCardId;
235
236
// Remove from collection
237
const updateRequest = {
238
+
cardId: urlCardId,
239
curatorId: curatorId.value,
240
removeFromCollections: [collection.collectionId.getStringValue()],
241
};
···
286
curatorId: curatorId.value,
287
});
288
expect(addResult.isOk()).toBe(true);
289
+
const urlCardId = addResult.unwrap().urlCardId;
290
291
// Add to collection2 and collection3, remove from collection1
292
const updateRequest = {
293
+
cardId: urlCardId,
294
curatorId: curatorId.value,
295
addToCollections: [
296
collection2.collectionId.getStringValue(),
···
339
curatorId: curatorId.value,
340
});
341
expect(addResult.isOk()).toBe(true);
342
+
const urlCardId = addResult.unwrap().urlCardId;
343
344
// Update note and add to collection
345
const updateRequest = {
346
+
cardId: urlCardId,
347
curatorId: curatorId.value,
348
note: 'My note about this article',
349
addToCollections: [collection.collectionId.getStringValue()],
···
372
});
373
374
describe('Validation', () => {
375
+
it('should fail when card does not exist', async () => {
376
const request = {
377
+
cardId: 'nonexistent-card-id',
378
curatorId: curatorId.value,
379
note: 'This should fail',
380
};
···
383
384
expect(result.isErr()).toBe(true);
385
if (result.isErr()) {
386
+
expect(result.error.message).toContain(
387
+
'URL card not found. Please add the URL to your library first.',
388
+
);
389
}
390
});
391
392
it('should fail with invalid curator ID', async () => {
393
+
const url = 'https://example.com/article';
394
+
395
+
// Add URL to library first
396
+
const addResult = await addUrlToLibraryUseCase.execute({
397
+
url,
398
+
curatorId: curatorId.value,
399
+
});
400
+
expect(addResult.isOk()).toBe(true);
401
+
const urlCardId = addResult.unwrap().urlCardId;
402
+
403
const request = {
404
+
cardId: urlCardId,
405
curatorId: 'invalid-curator-id',
406
note: 'This should fail',
407
};
···
423
curatorId: curatorId.value,
424
});
425
expect(addResult.isOk()).toBe(true);
426
+
const urlCardId = addResult.unwrap().urlCardId;
427
428
const request = {
429
+
cardId: urlCardId,
430
curatorId: curatorId.value,
431
addToCollections: ['invalid-collection-id'],
432
};