feat: unified error handling + AI service modularisation

Error handling (Prompt 2):
- src/services/errors.ts: AppError class, normalizeError(), throwServiceError() helper
- 6 services wrapped with try/catch (property, match, need, shortlist, futureSignal, inquiry)
- inquiryService aligned from custom ServiceResult<T> to standard ServiceResponse types
- Results, MatchCenter, FutureAvailability pages show <ErrorState onRetry> on query failure

AI modularisation (Prompt 3):
- src/services/aiService.ts reduced from 755 → 19 lines (barrel re-export)
- src/services/ai/IAIService.ts: typed interface + all response types
- src/services/ai/mock/: needParser, compareBuilder, decisionBrief, listingParser, MockAIService
- src/services/ai/openrouter/OpenRouterAIService.ts: model-agnostic skeleton
- src/services/ai/prompts/: 4 prompt template files (needParsing, matchExplanation, compareSummary, decisionBrief)
- src/services/ai/index.ts: factory selects Mock or OpenRouter via VITE_USE_REAL_AI flag
- All existing import paths unchanged — zero call-site modifications

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 00:32:01 +02:00
parent efc72b720e
commit 6515acb7f0
23 changed files with 1468 additions and 955 deletions
+22 -22
View File
@@ -1,6 +1,8 @@
import { MockupInquiryProvider } from '../provider/MockupInquiryProvider'
import type { InquiryFilters } from '../provider/IInquiryProvider'
import type { Inquiry, Attachment } from '../domain/inquiry'
import type { ListResponse, ItemResponse } from './types'
import { throwServiceError } from './errors'
const provider = MockupInquiryProvider
@@ -10,31 +12,29 @@ export interface InquiryReplyPayload {
attachments?: Attachment[]
}
type ServiceResult<T> = { data: T; error: null } | { data: null; error: string }
export const inquiryService = {
async getActiveInquiries(filters?: InquiryFilters): Promise<ServiceResult<Inquiry[]>> {
async getActiveInquiries(filters?: InquiryFilters): Promise<ListResponse<Inquiry>> {
try {
const data = await provider.getAll(filters)
return { data, error: null }
} catch (e) {
return { data: null, error: e instanceof Error ? e.message : 'Unbekannter Fehler' }
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
} catch (err) {
throwServiceError(err)
}
},
async getInquiryById(id: string): Promise<ServiceResult<Inquiry | null>> {
async getInquiryById(id: string): Promise<ItemResponse<Inquiry | null>> {
try {
const data = await provider.getById(id)
return { data, error: null }
} catch (e) {
return { data: null, error: e instanceof Error ? e.message : 'Unbekannter Fehler' }
return { data }
} catch (err) {
throwServiceError(err)
}
},
async sendInquiryReply(
inquiryId: string,
payload: InquiryReplyPayload,
): Promise<ServiceResult<Inquiry>> {
): Promise<ItemResponse<Inquiry>> {
try {
const data = await provider.addMessage(inquiryId, {
senderType: 'supply_user',
@@ -43,27 +43,27 @@ export const inquiryService = {
body: payload.body,
attachments: payload.attachments ?? [],
})
return { data, error: null }
} catch (e) {
return { data: null, error: e instanceof Error ? e.message : 'Unbekannter Fehler' }
return { data }
} catch (err) {
throwServiceError(err)
}
},
async markThreadAsRead(id: string): Promise<ServiceResult<Inquiry>> {
async markThreadAsRead(id: string): Promise<ItemResponse<Inquiry>> {
try {
const data = await provider.markThreadAsRead(id)
return { data, error: null }
} catch (e) {
return { data: null, error: e instanceof Error ? e.message : 'Unbekannter Fehler' }
return { data }
} catch (err) {
throwServiceError(err)
}
},
async getUnreadCount(): Promise<ServiceResult<number>> {
async getUnreadCount(): Promise<ItemResponse<number>> {
try {
const data = await provider.getUnreadCount()
return { data, error: null }
} catch (e) {
return { data: null, error: e instanceof Error ? e.message : 'Unbekannter Fehler' }
return { data }
} catch (err) {
throwServiceError(err)
}
},
}