Skip to content

Commit a3f88eb

Browse files
claudeenko
authored andcommitted
refactor: Replace inline types with dedicated named types
Extract inline object types into properly named, reusable types across the codebase to improve readability and enable safer refactoring. Shared package: - PaginationInfo: consolidates pagination fields from EncounterListResponse and CollectiveListResponse into packages/shared/src/pagination.ts - CollectiveTypeSummary: type summary in collective list items - RelationshipTypeSummary: relationship type metadata in preview items - FriendPreview: friend object within upcoming dates - CircleReorderItem: extracted from inline CircleReorderSchema Auth types: - UserWithPreferencesResponse: GET /api/auth/me response - PreferencesResponse: PATCH /api/auth/preferences response - ForgotPasswordResponse: POST /api/auth/forgot-password response - HealthCheckResponse: GET /api/health response Frontend: - UpcomingDatesOptions: parameters for upcoming dates queries - Replace duplicate PaginationState interfaces in encounters and collectives stores with shared PaginationInfo Backend: - Update auth routes to use shared response types - Update health route to use HealthCheckResponse Closes #126 https://claude.ai/code/session_01Ggmu9YzZBUq3ETBuiyXcXV
1 parent 4e405da commit a3f88eb

File tree

12 files changed

+95
-56
lines changed

12 files changed

+95
-56
lines changed

apps/backend/src/routes/auth.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {
22
type AuthResponse,
33
ForgotPasswordRequestSchema,
4+
type ForgotPasswordResponse,
45
LoginRequestSchema,
6+
type PreferencesResponse,
57
RefreshRequestSchema,
68
RegisterRequestSchema,
79
ResetPasswordRequestSchema,
810
UpdatePreferencesRequestSchema,
9-
type User,
10-
type UserPreferences,
11+
type UserWithPreferencesResponse,
1112
} from '@freundebuch/shared/index.js';
1213
import { type } from 'arktype';
1314
import { Hono } from 'hono';
@@ -229,7 +230,7 @@ app.post('/forgot-password', passwordResetRateLimitMiddleware, async (c) => {
229230
// Always return success to prevent user enumeration
230231
// Only include resetToken in non-production environments for testing
231232
const config = getConfig();
232-
const response: { message: string; resetToken?: string } = {
233+
const response: ForgotPasswordResponse = {
233234
message: 'If the email exists, a password reset link has been sent',
234235
};
235236

@@ -285,7 +286,7 @@ app.get('/me', authMiddleware, async (c) => {
285286
throw new UserNotFoundError();
286287
}
287288

288-
const response: { user: User; preferences: UserPreferences } = {
289+
const response: UserWithPreferencesResponse = {
289290
user: {
290291
externalId: userWithPrefs.externalId,
291292
email: userWithPrefs.email,
@@ -326,7 +327,7 @@ app.patch('/preferences', authMiddleware, async (c) => {
326327

327328
logger.info({ userId: authUser.userId }, 'User preferences updated');
328329

329-
return c.json<{ preferences: UserPreferences }>({ preferences: updatedPreferences });
330+
return c.json<PreferencesResponse>({ preferences: updatedPreferences });
330331
});
331332

332333
export default app;

apps/backend/src/routes/health.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { HealthCheckResponse } from '@freundebuch/shared/index.js';
12
import { Hono } from 'hono';
23
import type { AppContext } from '../types/context.js';
34
import { toError } from '../utils/errors.js';
@@ -21,7 +22,7 @@ health.get('/', async (c) => {
2122
client.release();
2223
}
2324

24-
const health = {
25+
const health: HealthCheckResponse = {
2526
status: dbHealthy ? 'healthy' : 'unhealthy',
2627
timestamp: new Date().toISOString(),
2728
uptime: process.uptime(),

apps/frontend/src/lib/api/friends.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,16 @@ export async function deleteDate(friendId: string, dateId: string): Promise<{ me
325325
});
326326
}
327327

328+
/** Options for upcoming dates query */
329+
export interface UpcomingDatesOptions {
330+
days?: number;
331+
limit?: number;
332+
}
333+
328334
/**
329335
* Get upcoming important dates across all friends
330336
*/
331-
export async function getUpcomingDates(options?: {
332-
days?: number;
333-
limit?: number;
334-
}): Promise<UpcomingDate[]> {
337+
export async function getUpcomingDates(options?: UpcomingDatesOptions): Promise<UpcomingDate[]> {
335338
const searchParams = new URLSearchParams();
336339
if (options?.days) searchParams.set('days', options.days.toString());
337340
if (options?.limit) searchParams.set('limit', options.limit.toString());

apps/frontend/src/lib/stores/collectives.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
MembershipDeactivate,
99
MembershipInput,
1010
MembershipUpdate,
11+
PaginationInfo,
1112
RelationshipPreviewRequest,
1213
RelationshipPreviewResponse,
1314
} from '$shared';
@@ -16,13 +17,6 @@ import type { CollectiveListParams } from '../api/collectives.js';
1617
import * as collectivesApi from '../api/collectives.js';
1718
import { storeAction } from './storeAction.js';
1819

19-
interface PaginationState {
20-
page: number;
21-
pageSize: number;
22-
totalCount: number;
23-
totalPages: number;
24-
}
25-
2620
interface FilterState {
2721
typeId?: string;
2822
search?: string;
@@ -33,7 +27,7 @@ interface CollectivesState {
3327
collectives: CollectiveListItem[];
3428
currentCollective: Collective | null;
3529
collectiveTypes: CollectiveType[];
36-
pagination: PaginationState;
30+
pagination: PaginationInfo;
3731
filters: FilterState;
3832
isLoading: boolean;
3933
isLoadingTypes: boolean;

apps/frontend/src/lib/stores/encounters.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,12 @@ import type {
55
EncounterListItem,
66
EncounterUpdate,
77
LastEncounterSummary,
8+
PaginationInfo,
89
} from '$shared';
910
import type { EncounterListParams } from '../api/encounters.js';
1011
import * as encountersApi from '../api/encounters.js';
1112
import { storeAction } from './storeAction.js';
1213

13-
interface PaginationState {
14-
page: number;
15-
pageSize: number;
16-
totalCount: number;
17-
totalPages: number;
18-
}
19-
2014
interface FilterState {
2115
friendId?: string;
2216
fromDate?: string;
@@ -27,7 +21,7 @@ interface FilterState {
2721
interface EncountersState {
2822
encounters: EncounterListItem[];
2923
currentEncounter: Encounter | null;
30-
pagination: PaginationState;
24+
pagination: PaginationInfo;
3125
filters: FilterState;
3226
isLoading: boolean;
3327
error: string | null;

packages/shared/src/auth.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,35 @@ export const ResetPasswordRequestSchema = type({
108108

109109
export type ResetPasswordRequest = typeof ResetPasswordRequestSchema.infer;
110110

111+
// ============================================================================
112+
// Endpoint Response Types
113+
// ============================================================================
114+
115+
/** Response from GET /api/auth/me */
116+
export interface UserWithPreferencesResponse {
117+
user: User;
118+
preferences: UserPreferences;
119+
}
120+
121+
/** Response from PATCH /api/auth/preferences */
122+
export interface PreferencesResponse {
123+
preferences: UserPreferences;
124+
}
125+
126+
/** Response from POST /api/auth/forgot-password */
127+
export interface ForgotPasswordResponse {
128+
message: string;
129+
resetToken?: string;
130+
}
131+
132+
/** Response from GET /api/health */
133+
export interface HealthCheckResponse {
134+
status: 'healthy' | 'unhealthy';
135+
timestamp: string;
136+
uptime: number;
137+
database: 'connected' | 'disconnected';
138+
}
139+
111140
// Error response
112141
export interface ErrorResponse {
113142
error: string;

packages/shared/src/circles.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ export const CircleInputSchema = type({
4343
});
4444
export type CircleInput = typeof CircleInputSchema.infer;
4545

46+
/** Single item in a circle reorder request */
47+
export const CircleReorderItemSchema = type({
48+
id: 'string > 0', // external_id
49+
sort_order: 'number >= 0',
50+
});
51+
export type CircleReorderItem = typeof CircleReorderItemSchema.infer;
52+
4653
/** Schema for reordering circles */
4754
export const CircleReorderSchema = type({
48-
circles: type({
49-
id: 'string > 0', // external_id
50-
sort_order: 'number >= 0',
51-
}).array(),
55+
circles: CircleReorderItemSchema.array(),
5256
});
5357
export type CircleReorderInput = typeof CircleReorderSchema.infer;
5458

packages/shared/src/collectives.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type } from 'arktype';
2+
import type { PaginationInfo } from './pagination.js';
23

34
/**
45
* Collective types and validation schemas for Epic 12: Collectives
@@ -236,14 +237,17 @@ export interface Collective {
236237
deletedAt: string | null;
237238
}
238239

240+
/** Type summary for collective list items */
241+
export interface CollectiveTypeSummary {
242+
id: string;
243+
name: string;
244+
}
245+
239246
/** Collective in list responses (fewer details) */
240247
export interface CollectiveListItem {
241248
id: string; // external_id
242249
name: string;
243-
type: {
244-
id: string;
245-
name: string;
246-
};
250+
type: CollectiveTypeSummary;
247251
photoUrl: string | null;
248252
photoThumbnailUrl: string | null;
249253
memberCount: number;
@@ -256,23 +260,21 @@ export interface CollectiveListItem {
256260
/** Paginated collective list response */
257261
export interface CollectiveListResponse {
258262
collectives: CollectiveListItem[];
259-
pagination: {
260-
page: number;
261-
pageSize: number;
262-
totalCount: number;
263-
totalPages: number;
264-
};
263+
pagination: PaginationInfo;
264+
}
265+
266+
/** Relationship type summary for preview items */
267+
export interface RelationshipTypeSummary {
268+
id: string;
269+
label: string;
270+
category: string;
265271
}
266272

267273
/** Preview of a relationship that will be created */
268274
export interface RelationshipPreviewItem {
269275
fromContact: CollectiveMemberContact;
270276
toContact: CollectiveMemberContact;
271-
relationshipType: {
272-
id: string;
273-
label: string;
274-
category: string;
275-
};
277+
relationshipType: RelationshipTypeSummary;
276278
alreadyExists: boolean; // If true, this relationship already exists
277279
}
278280

packages/shared/src/encounters.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type } from 'arktype';
2+
import type { PaginationInfo } from './pagination.js';
23

34
/**
45
* Encounter types and validation schemas for Epic 2: Encounter Management
@@ -127,12 +128,7 @@ export interface EncounterListItem {
127128
/** Paginated encounter list response */
128129
export interface EncounterListResponse {
129130
encounters: EncounterListItem[];
130-
pagination: {
131-
page: number;
132-
pageSize: number;
133-
totalCount: number;
134-
totalPages: number;
135-
};
131+
pagination: PaginationInfo;
136132
}
137133

138134
/** Last encounter summary for friend detail page */

packages/shared/src/friends.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ export interface FriendDate {
366366
createdAt: string;
367367
}
368368

369+
/** Friend preview for embedding in dashboard and summary views */
370+
export interface FriendPreview {
371+
id: string;
372+
displayName: string;
373+
photoThumbnailUrl?: string;
374+
}
375+
369376
/** Upcoming date with friend info for dashboard */
370377
export interface UpcomingDate {
371378
id: string;
@@ -374,11 +381,7 @@ export interface UpcomingDate {
374381
dateType: DateType;
375382
label?: string;
376383
daysUntil: number;
377-
friend: {
378-
id: string;
379-
displayName: string;
380-
photoThumbnailUrl?: string;
381-
};
384+
friend: FriendPreview;
382385
}
383386

384387
/** Met information in API responses */

0 commit comments

Comments
 (0)