import 'package:patbond_flutter/core/network/api_client.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; import 'package:patbond_flutter/features/pets/pet_exceptions.dart'; import 'package:patbond_flutter/features/pets/pet_models.dart'; import 'package:uuid/uuid.dart'; /// pets 域仓库接口(契约 v1.2.0 的 12 路径 / 18 操作全覆盖; /// 页面依赖此抽象,widget 测试注入假实现)。 abstract class PetsRepository { // ---- 宠物 CRUD ---- Future> listPets(); Future createPet(CreatePetRequest request); Future getPet(String petId); Future updatePet(String petId, UpdatePetRequest request); // ---- 字典 ---- Future> listBreeds({PetSpecies? species}); Future> listVaccineCatalog({PetSpecies? species}); // ---- 体重(cursor 分页)---- Future> listWeights( String petId, { int? limit, String? cursor, }); Future createWeight(String petId, CreateWeightRequest request); // ---- 疫苗 ---- Future> listVaccinations(String petId); Future createVaccination( String petId, CreateVaccinationRequest request, ); Future updateVaccination( String vaccinationId, UpdateVaccinationRequest request, ); // ---- 健康事件(cursor 分页)---- Future> listHealthEvents( String petId, { int? limit, String? cursor, }); Future createHealthEvent( String petId, CreateHealthEventRequest request, ); Future updateHealthEvent( String eventId, UpdateHealthEventRequest request, ); // ---- 照护提醒 ---- Future> listCareReminders( String petId, { CareReminderStatus? status, }); Future createCareReminder( String petId, CreateCareReminderRequest request, ); Future updateCareReminder( String reminderId, UpdateCareReminderRequest request, ); // ---- 聚合摘要 ---- Future getPetSummary(String petId, {String? tz}); } /// 基于 [ApiClient] 的实现。全部端点走 Bearer 鉴权(复用既有 token /// 拦截 + 401/40101 单飞刷新重放);pets 域业务错误码升格为类型化异常。 /// /// 幂等:weights / vaccinations / health-events / care-reminders 四个 POST /// 按契约携带可选 `Idempotency-Key`(每次逻辑提交换新键;token 刷新后的 /// 自动重放沿用同一个键,与 auth register 先例一致)。 class ApiPetsRepository implements PetsRepository { ApiPetsRepository({required this._api, this._uuid = const Uuid()}); final ApiClient _api; final Uuid _uuid; Future _request( String path, { String method = 'GET', Object? body, Map? query, bool idempotent = false, }) async { try { return await _api.request( path, method: method, body: body, query: query, headers: idempotent ? {'Idempotency-Key': _uuid.v4()} : null, requiresAuth: true, ); } on ApiBusinessException catch (error) { throw mapPetBusinessException(error); } } Map _asMap(Object? data) => data! as Map; List _asList(Object? data, T Function(Map) fromJson) { return (data! as List) .map((item) => fromJson(item as Map)) .toList(); } @override Future> listPets() async { final data = await _request('/api/v1/pets'); return _asList(data, Pet.fromJson); } @override Future createPet(CreatePetRequest request) async { final data = await _request( '/api/v1/pets', method: 'POST', body: request.toJson(), ); return Pet.fromJson(_asMap(data)); } @override Future getPet(String petId) async { final data = await _request('/api/v1/pets/$petId'); return Pet.fromJson(_asMap(data)); } @override Future updatePet(String petId, UpdatePetRequest request) async { final data = await _request( '/api/v1/pets/$petId', method: 'PATCH', body: request.toJson(), ); return Pet.fromJson(_asMap(data)); } @override Future> listBreeds({PetSpecies? species}) async { final data = await _request( '/api/v1/breeds', query: {if (species != null) 'species': species.name}, ); return _asList(data, Breed.fromJson); } @override Future> listVaccineCatalog({ PetSpecies? species, }) async { final data = await _request( '/api/v1/vaccine-catalog', query: {if (species != null) 'species': species.name}, ); return _asList(data, VaccineCatalogItem.fromJson); } @override Future> listWeights( String petId, { int? limit, String? cursor, }) async { final data = await _request( '/api/v1/pets/$petId/weights', query: {'limit': ?limit, 'cursor': ?cursor}, ); return CursorPage.fromJson(_asMap(data), WeightRecord.fromJson); } @override Future createWeight( String petId, CreateWeightRequest request, ) async { final data = await _request( '/api/v1/pets/$petId/weights', method: 'POST', body: request.toJson(), idempotent: true, ); return WeightRecord.fromJson(_asMap(data)); } @override Future> listVaccinations(String petId) async { final data = await _request('/api/v1/pets/$petId/vaccinations'); return _asList(data, Vaccination.fromJson); } @override Future createVaccination( String petId, CreateVaccinationRequest request, ) async { final data = await _request( '/api/v1/pets/$petId/vaccinations', method: 'POST', body: request.toJson(), idempotent: true, ); return Vaccination.fromJson(_asMap(data)); } @override Future updateVaccination( String vaccinationId, UpdateVaccinationRequest request, ) async { final data = await _request( '/api/v1/vaccinations/$vaccinationId', method: 'PATCH', body: request.toJson(), ); return Vaccination.fromJson(_asMap(data)); } @override Future> listHealthEvents( String petId, { int? limit, String? cursor, }) async { final data = await _request( '/api/v1/pets/$petId/health-events', query: {'limit': ?limit, 'cursor': ?cursor}, ); return CursorPage.fromJson(_asMap(data), HealthEvent.fromJson); } @override Future createHealthEvent( String petId, CreateHealthEventRequest request, ) async { final data = await _request( '/api/v1/pets/$petId/health-events', method: 'POST', body: request.toJson(), idempotent: true, ); return HealthEvent.fromJson(_asMap(data)); } @override Future updateHealthEvent( String eventId, UpdateHealthEventRequest request, ) async { final data = await _request( '/api/v1/health-events/$eventId', method: 'PATCH', body: request.toJson(), ); return HealthEvent.fromJson(_asMap(data)); } @override Future> listCareReminders( String petId, { CareReminderStatus? status, }) async { final data = await _request( '/api/v1/pets/$petId/care-reminders', query: {if (status != null) 'status': status.name}, ); return _asList(data, CareReminder.fromJson); } @override Future createCareReminder( String petId, CreateCareReminderRequest request, ) async { final data = await _request( '/api/v1/pets/$petId/care-reminders', method: 'POST', body: request.toJson(), idempotent: true, ); return CareReminder.fromJson(_asMap(data)); } @override Future updateCareReminder( String reminderId, UpdateCareReminderRequest request, ) async { final data = await _request( '/api/v1/care-reminders/$reminderId', method: 'PATCH', body: request.toJson(), ); return CareReminder.fromJson(_asMap(data)); } @override Future getPetSummary(String petId, {String? tz}) async { final data = await _request( '/api/v1/pets/$petId/summary', query: {'tz': ?tz}, ); return PetSummary.fromJson(_asMap(data)); } }