161 lines
4.5 KiB
Dart
161 lines
4.5 KiB
Dart
import 'package:clock/clock.dart';
|
|
import 'package:relationship_saver/integrations/backend/backend_gateway.dart';
|
|
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
/// Deterministic fake backend for local development and tests.
|
|
class BackendGatewayFake implements BackendGateway {
|
|
BackendGatewayFake({Clock? clock, Uuid? uuid})
|
|
: _clock = clock ?? const Clock(),
|
|
_uuid = uuid ?? const Uuid();
|
|
|
|
final Clock _clock;
|
|
final Uuid _uuid;
|
|
|
|
static final DateTime _seedTime = DateTime.utc(2026, 1, 1, 8);
|
|
|
|
@override
|
|
Future<void> acknowledgeSignal({
|
|
required String signalId,
|
|
required SignalAction action,
|
|
DateTime? at,
|
|
}) async {
|
|
return;
|
|
}
|
|
|
|
@override
|
|
Future<DownloadUrl> getDownloadUrl({required String fileId}) async {
|
|
return DownloadUrl(
|
|
downloadUrl: 'https://files.example.com/download/$fileId',
|
|
expiresAt: _seedTime.add(const Duration(hours: 1)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<SignalsFeed> getSignalsFeed({
|
|
String? cursor,
|
|
DateTime? since,
|
|
int limit = 50,
|
|
Set<String>? personIds,
|
|
}) async {
|
|
final List<SignalItem> seeded = <SignalItem>[
|
|
SignalItem(
|
|
id: 'sig-1',
|
|
type: 'deal',
|
|
title: 'Weekend coffee promo nearby',
|
|
description: 'Save this for your next date idea.',
|
|
personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-1',
|
|
createdAt: _seedTime,
|
|
metadata: const <String, dynamic>{'merchant': 'Cafe Bloom'},
|
|
),
|
|
SignalItem(
|
|
id: 'sig-2',
|
|
type: 'trend',
|
|
title: 'Books are trending in gift ideas',
|
|
description: 'Reading-themed gifts up 24% this week.',
|
|
personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-1',
|
|
createdAt: _seedTime.add(const Duration(hours: 3)),
|
|
),
|
|
SignalItem(
|
|
id: 'sig-3',
|
|
type: 'recommendation',
|
|
title: 'Try a sunset walk reminder',
|
|
description: 'Low-effort ritual with high emotional impact.',
|
|
personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-2',
|
|
createdAt: _seedTime.add(const Duration(hours: 6)),
|
|
),
|
|
];
|
|
|
|
return SignalsFeed(
|
|
cursor: 'fake-cursor-1',
|
|
items: seeded.take(limit).toList(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<UploadInitResult> initUpload({
|
|
required UploadPurpose purpose,
|
|
required String contentType,
|
|
required int sizeBytes,
|
|
}) async {
|
|
final String fileId = 'file-${_uuid.v4()}';
|
|
return UploadInitResult(
|
|
uploadUrl: 'https://uploads.example.com/$fileId',
|
|
fileId: fileId,
|
|
expiresAt: _seedTime.add(const Duration(minutes: 15)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<UserProfile> me() async {
|
|
return const UserProfile(
|
|
id: 'user-fake',
|
|
email: 'demo@example.com',
|
|
displayName: 'Demo User',
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<SyncPullResult> pullChanges({String? cursor, int limit = 200}) async {
|
|
final List<ChangeEnvelope> changes = <ChangeEnvelope>[
|
|
ChangeEnvelope(
|
|
schemaVersion: 1,
|
|
entityType: 'person',
|
|
entityId: 'person-1',
|
|
op: ChangeOperation.upsert,
|
|
modifiedAt: _seedTime,
|
|
clientMutationId: 'cm-1',
|
|
payload: const <String, dynamic>{'name': 'Alex', 'importance': 5},
|
|
),
|
|
];
|
|
|
|
return SyncPullResult(
|
|
cursor: 'fake-pull-cursor-1',
|
|
changes: changes.take(limit).toList(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<SyncPushResult> pushChanges({
|
|
required List<ChangeEnvelope> changes,
|
|
String? cursor,
|
|
}) async {
|
|
final DateTime now = _clock.now().toUtc();
|
|
final List<MutationAck> accepted = changes
|
|
.map(
|
|
(ChangeEnvelope change) => MutationAck(
|
|
clientMutationId: change.clientMutationId,
|
|
serverMutationId: 'srv-${change.clientMutationId}',
|
|
acceptedAt: now,
|
|
),
|
|
)
|
|
.toList(growable: false);
|
|
|
|
return SyncPushResult(
|
|
cursor: 'fake-push-cursor-1',
|
|
accepted: accepted,
|
|
rejected: const <MutationRejection>[],
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<AuthSession> signIn(SignInRequest request) async {
|
|
final DateTime now = _clock.now().toUtc();
|
|
return AuthSession(
|
|
accessToken: 'fake-access-token',
|
|
refreshToken: 'fake-refresh-token',
|
|
expiresAt: now.add(const Duration(hours: 1)),
|
|
user: UserProfile(
|
|
id: 'user-fake',
|
|
email: request.email ?? 'demo@example.com',
|
|
displayName: 'Demo User',
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> signOut() async {
|
|
return;
|
|
}
|
|
}
|