Skip to content

Commit d977e5c

Browse files
documents getLatestOf
1 parent bb50abd commit d977e5c

File tree

8 files changed

+185
-10
lines changed

8 files changed

+185
-10
lines changed

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_v2_dao.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ abstract interface class DocumentsV2Dao {
3535
/// Returns null if no matching document is found.
3636
Future<DocumentEntityV2?> getDocument(DocumentRef ref);
3737

38+
/// Finds the latest version of a document.
39+
///
40+
/// Takes a [ref] (which can be loose or exact) and returns a [DocumentRef]
41+
/// pointing to the latest known version of that document.
42+
Future<DocumentRef?> getLatestOf(DocumentRef ref);
43+
3844
/// Saves a single document, ignoring if it conflicts on {id, ver}.
3945
///
4046
/// Delegates to [saveAll] for consistent conflict handling and reuse.
@@ -146,6 +152,24 @@ class DriftDocumentsV2Dao extends DatabaseAccessor<DriftCatalystDatabase>
146152
return query.getSingleOrNull();
147153
}
148154

155+
@override
156+
Future<DocumentRef?> getLatestOf(DocumentRef ref) {
157+
final query = selectOnly(documentsV2)
158+
..addColumns([documentsV2.id, documentsV2.ver])
159+
..where(documentsV2.id.equals(ref.id))
160+
..orderBy([OrderingTerm.desc(documentsV2.createdAt)])
161+
..limit(1);
162+
163+
return query
164+
.map(
165+
(row) => SignedDocumentRef.exact(
166+
id: row.read(documentsV2.id)!,
167+
version: row.read(documentsV2.ver)!,
168+
),
169+
)
170+
.getSingleOrNull();
171+
}
172+
149173
@override
150174
Future<void> save(DocumentWithAuthorsEntity entity) => saveAll([entity]);
151175

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/document_repository.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ abstract interface class DocumentRepository {
8585
CatalystId? authorId,
8686
});
8787

88+
/// Returns latest matching [DocumentRef] version with same id as [ref].
89+
Future<DocumentRef?> getLatestOf({required DocumentRef ref});
90+
8891
/// Returns count of documents matching [ref] id and [type].
8992
Future<int> getRefCount({
9093
required DocumentRef ref,
@@ -320,6 +323,17 @@ final class DocumentRepositoryImpl implements DocumentRepository {
320323
return [latestDocument, latestDraft].nonNulls.sorted((a, b) => a.compareTo(b)).firstOrNull;
321324
}
322325

326+
// TODO(damian-molinski): consider also checking with remote source.
327+
@override
328+
Future<DocumentRef?> getLatestOf({required DocumentRef ref}) async {
329+
final draft = await _drafts.getLatestOf(ref: ref);
330+
if (draft != null) {
331+
return draft;
332+
}
333+
334+
return _localDocuments.getLatestOf(ref: ref);
335+
}
336+
323337
@override
324338
Future<int> getRefCount({
325339
required DocumentRef ref,

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ final class DatabaseDocumentsDataSource
6969
.then((value) => value?.toModel());
7070
}
7171

72+
@override
73+
Future<DocumentRef?> getLatestOf({required DocumentRef ref}) {
74+
return _database.documentsV2Dao.getLatestOf(ref);
75+
}
76+
7277
@override
7378
Future<List<ProposalDocumentData>> getProposals({
7479
SignedDocumentRef? categoryRef,

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_drafts_data_source.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ final class DatabaseDraftsDataSource implements DraftDataSource {
5050
return _database.draftsDao.queryLatest(authorId: authorId).then((value) => value?.toModel());
5151
}
5252

53+
@override
54+
Future<DocumentRef?> getLatestOf({required DocumentRef ref}) async {
55+
// TODO(damian-molinski): not implemented
56+
return null;
57+
}
58+
5359
@override
5460
Future<List<DocumentData>> queryVersionsOfId({required String id}) async {
5561
final documentEntities = await _database.draftsDao.queryVersionsOfId(id: id);

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_remote_source.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ final class CatGatewayDocumentDataSource implements DocumentDataRemoteSource {
3030
return DocumentDataFactory.create(signedDocument);
3131
}
3232

33+
@override
34+
Future<DocumentRef?> getLatestOf({required DocumentRef ref}) async {
35+
final ver = await getLatestVersion(ref.id);
36+
if (ver == null) {
37+
return null;
38+
}
39+
40+
return SignedDocumentRef(id: ref.id, version: ver);
41+
}
42+
3343
@override
3444
Future<String?> getLatestVersion(String id) {
3545
final ver = allConstantDocumentRefs

catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_source.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart';
33
//ignore: one_member_abstracts
44
abstract interface class DocumentDataSource {
55
Future<DocumentData> get({required DocumentRef ref});
6+
7+
Future<DocumentRef?> getLatestOf({required DocumentRef ref});
68
}

catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/database/dao/documents_v2_dao_test.dart

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,124 @@ void main() {
892892
await expectation;
893893
});
894894
});
895+
896+
group('getLatestOf', () {
897+
test('returns null for non-existing id in empty database', () async {
898+
// Given
899+
const ref = SignedDocumentRef.exact(id: 'non-existent-id', version: 'non-existent-ver');
900+
901+
// When
902+
final result = await dao.getLatestOf(ref);
903+
904+
// Then
905+
expect(result, isNull);
906+
});
907+
908+
test('returns the document ref when only one version exists', () async {
909+
// Given
910+
final entity = _createTestDocumentEntity(id: 'test-id', ver: 'test-ver');
911+
await dao.save(entity);
912+
913+
// And
914+
const ref = SignedDocumentRef.loose(id: 'test-id');
915+
916+
// When
917+
final result = await dao.getLatestOf(ref);
918+
919+
// Then
920+
expect(result, isNotNull);
921+
expect(result!.id, 'test-id');
922+
expect(result.version, 'test-ver');
923+
expect(result.isExact, isTrue);
924+
});
925+
926+
test('returns latest version when multiple versions exist (loose ref input)', () async {
927+
// Given
928+
final oldCreatedAt = DateTime.utc(2023, 1, 1);
929+
final newerCreatedAt = DateTime.utc(2024, 6, 15);
930+
931+
final oldVer = _buildUuidV7At(oldCreatedAt);
932+
final newerVer = _buildUuidV7At(newerCreatedAt);
933+
final entityOld = _createTestDocumentEntity(id: 'test-id', ver: oldVer);
934+
final entityNew = _createTestDocumentEntity(id: 'test-id', ver: newerVer);
935+
await dao.saveAll([entityOld, entityNew]);
936+
937+
// And
938+
const ref = SignedDocumentRef.loose(id: 'test-id');
939+
940+
// When
941+
final result = await dao.getLatestOf(ref);
942+
943+
// Then
944+
expect(result, isNotNull);
945+
expect(result!.id, 'test-id');
946+
expect(result.version, newerVer);
947+
});
948+
949+
test('returns latest version even when exact ref points to older version', () async {
950+
// Given
951+
final oldCreatedAt = DateTime.utc(2023, 1, 1);
952+
final newerCreatedAt = DateTime.utc(2024, 6, 15);
953+
954+
final oldVer = _buildUuidV7At(oldCreatedAt);
955+
final newerVer = _buildUuidV7At(newerCreatedAt);
956+
final entityOld = _createTestDocumentEntity(id: 'test-id', ver: oldVer);
957+
final entityNew = _createTestDocumentEntity(id: 'test-id', ver: newerVer);
958+
await dao.saveAll([entityOld, entityNew]);
959+
960+
// And: exact ref pointing to older version
961+
final ref = SignedDocumentRef.exact(id: 'test-id', version: oldVer);
962+
963+
// When
964+
final result = await dao.getLatestOf(ref);
965+
966+
// Then: still returns the latest version
967+
expect(result, isNotNull);
968+
expect(result!.id, 'test-id');
969+
expect(result.version, newerVer);
970+
});
971+
972+
test('returns null for non-existing id when other documents exist', () async {
973+
// Given
974+
final entity = _createTestDocumentEntity(id: 'other-id', ver: 'other-ver');
975+
await dao.save(entity);
976+
977+
// And
978+
const ref = SignedDocumentRef.loose(id: 'non-existent-id');
979+
980+
// When
981+
final result = await dao.getLatestOf(ref);
982+
983+
// Then
984+
expect(result, isNull);
985+
});
986+
987+
test('returns latest among many versions', () async {
988+
// Given
989+
final dates = [
990+
DateTime.utc(2023, 1, 1),
991+
DateTime.utc(2023, 6, 15),
992+
DateTime.utc(2024, 3, 10),
993+
DateTime.utc(2024, 12, 25),
994+
DateTime.utc(2024, 8, 1),
995+
];
996+
final versions = dates.map(_buildUuidV7At).toList();
997+
final entities = versions
998+
.map((ver) => _createTestDocumentEntity(id: 'multi-ver-id', ver: ver))
999+
.toList();
1000+
await dao.saveAll(entities);
1001+
1002+
// And
1003+
const ref = SignedDocumentRef.loose(id: 'multi-ver-id');
1004+
1005+
// When
1006+
final result = await dao.getLatestOf(ref);
1007+
1008+
// Then: returns the version with latest createdAt (2024-12-25)
1009+
expect(result, isNotNull);
1010+
expect(result!.version, versions[3]);
1011+
});
1012+
});
8951013
});
8961014
}
8971015

catalyst_voices/packages/internal/catalyst_voices_services/lib/src/proposal/proposal_service.dart

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -250,17 +250,13 @@ final class ProposalServiceImpl implements ProposalService {
250250
}
251251

252252
@override
253-
Future<DocumentRef> getLatestProposalVersion({
254-
required DocumentRef ref,
255-
}) async {
256-
final proposalVersions = await _documentRepository.getAllVersionsOfId(
257-
id: ref.id,
258-
);
259-
final refList = List<DocumentRef>.from(
260-
proposalVersions.map((e) => e.metadata.selfRef).toList(),
261-
)..sort();
253+
Future<DocumentRef> getLatestProposalVersion({required DocumentRef ref}) async {
254+
final latest = await _documentRepository.getLatestOf(ref: ref);
255+
if (latest == null) {
256+
throw DocumentNotFoundException(ref: ref);
257+
}
262258

263-
return refList.last;
259+
return latest;
264260
}
265261

266262
@override

0 commit comments

Comments
 (0)