···
150
150
151
151
export class GetUrlCardsUseCase {
152
152
async execute(
153
153
-
query: GetUrlCardsQuery
154
154
-
): Promise<Result<GetUrlCardsResponse, ValidationError | AppError.UnexpectedError>> {
153
153
+
query: GetUrlCardsQuery,
154
154
+
): Promise<
155
155
+
Result<GetUrlCardsResponse, ValidationError | AppError.UnexpectedError>
156
156
+
> {
155
157
// Implementation returns GetUrlCardsResponse type
156
158
return ok({
157
159
cards: enrichedCards,
···
204
206
} from '../../shared/api';
205
207
206
208
export class ApiClient {
207
207
-
async getMyUrlCards(params?: GetMyUrlCardsRequest): Promise<GetUrlCardsResponse> {
209
209
+
async getMyUrlCards(
210
210
+
params?: GetMyUrlCardsRequest,
211
211
+
): Promise<GetUrlCardsResponse> {
208
212
return this.queryClient.getMyUrlCards(params);
209
213
}
210
214
211
211
-
async addUrlToLibrary(request: AddUrlToLibraryRequest): Promise<AddUrlToLibraryResponse> {
215
215
+
async addUrlToLibrary(
216
216
+
request: AddUrlToLibraryRequest,
217
217
+
): Promise<AddUrlToLibraryResponse> {
212
218
return this.cardClient.addUrlToLibrary(request);
213
219
}
214
220
}
···
217
223
## Migration Strategy
218
224
219
225
### Phase 1: Setup Infrastructure
226
226
+
220
227
1. Create `src/shared/api/` directory structure
221
228
2. Move existing webapp types to shared location
222
229
3. Update webapp imports to use shared types
223
230
224
231
### Phase 2: Backend Integration (Per Endpoint)
232
232
+
225
233
For each API endpoint:
234
234
+
226
235
1. **Identify the endpoint** (e.g., `GET /api/cards/my`)
227
236
2. **Update use case** to return shared response type
228
237
3. **Update controller** to use shared types
229
238
4. **Test the endpoint** to ensure types match
230
239
231
240
### Phase 3: Validation (Optional)
241
241
+
232
242
Add runtime validation using libraries like Zod:
233
243
234
244
```typescript
···
254
264
Here's how a complete request/response flow works:
255
265
256
266
1. **Frontend makes request:**
267
267
+
257
268
```typescript
258
269
const response: GetUrlCardsResponse = await apiClient.getMyUrlCards({
259
270
page: 1,
260
271
limit: 20,
261
272
sortBy: 'updatedAt',
262
262
-
sortOrder: 'desc'
273
273
+
sortOrder: 'desc',
263
274
});
264
275
```
265
276
266
277
2. **Controller receives request:**
278
278
+
267
279
```typescript
268
280
// TypeScript ensures the response matches GetUrlCardsResponse
269
281
const result = await this.useCase.execute(query);
···
271
283
```
272
284
273
285
3. **Use case returns typed response:**
286
286
+
274
287
```typescript
275
288
// Return type is enforced by TypeScript
276
289
return ok({
···
283
296
## Best Practices
284
297
285
298
### Type Naming Conventions
299
299
+
286
300
- **Requests**: `{Action}{Resource}Request` (e.g., `GetUrlCardsRequest`)
287
301
- **Responses**: `{Action}{Resource}Response` (e.g., `GetUrlCardsResponse`)
288
302
- **Common types**: Descriptive names (e.g., `User`, `Pagination`)
289
303
290
304
### File Organization
305
305
+
291
306
- Keep related types together in the same file
292
307
- Use barrel exports (`index.ts`) for clean imports
293
308
- Separate common types from endpoint-specific types
294
309
295
310
### Error Handling
311
311
+
296
312
- Define error response types consistently
297
313
- Use discriminated unions for different error types
298
314
- Include error codes and messages in shared types
299
315
300
316
### Versioning
317
317
+
301
318
- Consider API versioning in type names if needed
302
319
- Use semantic versioning for breaking changes
303
320
- Document breaking changes in migration guides
···
9
9
import { IProfileService } from '../../../domain/services/IProfileService';
10
10
import { ICollectionRepository } from '../../../domain/ICollectionRepository';
11
11
import { CollectionId } from '../../../domain/value-objects/CollectionId';
12
12
-
import {
13
13
-
CollectionDTO,
14
14
-
PaginationDTO,
15
15
-
CollectionSortingDTO,
16
16
-
} from '../../dtos';
12
12
+
import { CollectionDTO, PaginationDTO, CollectionSortingDTO } from '../../dtos';
17
13
18
14
export interface GetCollectionsForUrlQuery {
19
15
url: string;
···
8
8
import { IProfileService } from 'src/modules/cards/domain/services/IProfileService';
9
9
import { DIDOrHandle } from 'src/modules/atproto/domain/DIDOrHandle';
10
10
import { IIdentityResolutionService } from 'src/modules/atproto/domain/services/IIdentityResolutionService';
11
11
-
import {
12
12
-
CollectionDTO,
13
13
-
PaginationDTO,
14
14
-
CollectionSortingDTO,
15
15
-
} from '../../dtos';
11
11
+
import { CollectionDTO, PaginationDTO, CollectionSortingDTO } from '../../dtos';
16
12
17
13
export interface GetCollectionsQuery {
18
14
curatorId: string;
···
7
7
} from '../../../domain/ICardQueryRepository';
8
8
import { URL } from '../../../domain/value-objects/URL';
9
9
import { IProfileService } from '../../../domain/services/IProfileService';
10
10
-
import {
11
11
-
NoteCardDTO,
12
12
-
PaginationDTO,
13
13
-
CardSortingDTO,
14
14
-
} from '../../dtos';
10
10
+
import { NoteCardDTO, PaginationDTO, CardSortingDTO } from '../../dtos';
15
11
16
12
export interface GetNoteCardsForUrlQuery {
17
13
url: string;
···
9
9
import { IProfileService } from '../../../domain/services/IProfileService';
10
10
import { ICollectionRepository } from '../../../domain/ICollectionRepository';
11
11
import { CollectionId } from '../../../domain/value-objects/CollectionId';
12
12
-
import {
13
13
-
UserProfileDTO,
14
14
-
CollectionDTO,
15
15
-
} from '../../dtos';
12
12
+
import { UserProfileDTO, CollectionDTO } from '../../dtos';
16
13
17
14
export interface GetUrlCardViewQuery {
18
15
cardId: string;
···
30
30
interface Props {
31
31
isOpen: boolean;
32
32
onClose: () => void;
33
33
-
cardContent: UrlCard["cardContent"];
33
33
+
cardContent: UrlCard['cardContent'];
34
34
cardId: string;
35
35
}
36
36
···
12
12
Tooltip,
13
13
} from '@mantine/core';
14
14
import Link from 'next/link';
15
15
-
import {
16
16
-
GetUrlStatusForMyLibraryResponse,
17
17
-
UrlCard,
18
18
-
} from '@/api-client/types';
15
15
+
import { GetUrlStatusForMyLibraryResponse, UrlCard } from '@/api-client/types';
19
16
import { BiCollection } from 'react-icons/bi';
20
17
import { LuLibrary } from 'react-icons/lu';
21
18
import { getDomain } from '@/lib/utils/link';