Scaffold backend gateway and integration docs
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user