diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_cubit.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_cubit.dart index 06c6369c540..7110cd98d4a 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_cubit.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_cubit.dart @@ -38,9 +38,7 @@ class NewProposalCubit extends Cubit throw StateError('Cannot create draft, category not selected'); } - final category = await _campaignService.getCategory(DocumentParameters({categoryRef})); - final templateRef = category.proposalTemplateRef; - final template = await _proposalService.getProposalTemplate(ref: templateRef); + final template = await _proposalService.getProposalTemplate(category: categoryRef); final parameters = template.metadata.parameters; final documentBuilder = DocumentBuilder.fromSchema(schema: template.schema) @@ -56,7 +54,7 @@ class NewProposalCubit extends Cubit return await _proposalService.createDraftProposal( content: documentContent, - templateRef: templateRef, + templateRef: template.metadata.selfRef.toSignedDocumentRef(), parameters: parameters, ); } catch (error, stackTrace) { @@ -83,16 +81,10 @@ class NewProposalCubit extends Cubit // right now user can start creating proposal without selecting category. // Right now every category have the same requirements for title so we can do a fallback for // first category from the list. - final templateRef = campaign.categories - .cast() - .firstWhere( - (e) => e?.selfRef == categoryRef, - orElse: () => campaign.categories.firstOrNull, - ) - ?.proposalTemplateRef; - - final template = templateRef != null - ? await _proposalService.getProposalTemplate(ref: templateRef) + categoryRef ??= campaign.categories.firstOrNull?.selfRef; + + final template = categoryRef != null + ? await _proposalService.getProposalTemplate(category: categoryRef) : null; final titleRange = template?.title?.strLengthRange; diff --git a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/proposal_builder_bloc.dart b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/proposal_builder_bloc.dart index ed471fef91d..edf89f5047a 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/proposal_builder_bloc.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/proposal_builder_bloc.dart @@ -463,9 +463,9 @@ final class ProposalBuilderBloc extends Bloc get props => [ selfRef, - proposalTemplateRef, campaignRef, categoryName, categorySubname, @@ -68,7 +65,6 @@ class CampaignCategory extends Equatable { CampaignCategory copyWith({ SignedDocumentRef? selfRef, - SignedDocumentRef? proposalTemplateRef, SignedDocumentRef? campaignRef, String? categoryName, String? categorySubname, @@ -87,7 +83,6 @@ class CampaignCategory extends Equatable { }) { return CampaignCategory( selfRef: selfRef ?? this.selfRef, - proposalTemplateRef: proposalTemplateRef ?? this.proposalTemplateRef, campaignRef: campaignRef ?? this.campaignRef, categoryName: categoryName ?? this.categoryName, categorySubname: categorySubname ?? this.categorySubname, diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/campaign_filters.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/campaign_filters.dart index 9969674c7b5..c3e52215306 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/campaign_filters.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/campaign_filters.dart @@ -13,6 +13,11 @@ final class CampaignFilters extends Equatable { return CampaignFilters(categoriesIds: categoriesIds); } + factory CampaignFilters.from(Campaign campaign) { + final categoriesIds = campaign.categories.map((e) => e.selfRef.id).toSet().toList(); + return CampaignFilters(categoriesIds: categoriesIds); + } + @override List get props => [categoriesIds]; } diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f14_static_campaign_categories.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f14_static_campaign_categories.dart index fe0dd0e86f9..535be958ff3 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f14_static_campaign_categories.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f14_static_campaign_categories.dart @@ -9,7 +9,6 @@ import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; final f14StaticCampaignCategories = [ CampaignCategory( selfRef: f14ConstDocumentsRefs[0].category, - proposalTemplateRef: f14ConstDocumentsRefs[0].proposal, campaignRef: Campaign.f14Ref, categoryName: 'Cardano Use Case:', categorySubname: 'Partners & Products', @@ -93,7 +92,6 @@ The following will **not** be funded: ), CampaignCategory( selfRef: f14ConstDocumentsRefs[1].category, - proposalTemplateRef: f14ConstDocumentsRefs[1].proposal, campaignRef: Campaign.f14Ref, categoryName: 'Cardano Use Case:', categorySubname: 'Concept', @@ -176,7 +174,6 @@ The following will **not** be funded: ), CampaignCategory( selfRef: f14ConstDocumentsRefs[2].category, - proposalTemplateRef: f14ConstDocumentsRefs[2].proposal, campaignRef: Campaign.f14Ref, categoryName: 'Cardano Open:', categorySubname: 'Developers', @@ -261,7 +258,6 @@ The following will **not** be funded: ), CampaignCategory( selfRef: f14ConstDocumentsRefs[3].category, - proposalTemplateRef: f14ConstDocumentsRefs[3].proposal, campaignRef: Campaign.f14Ref, categoryName: 'Cardano Open:', categorySubname: 'Ecosystem', diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f15_static_campaign_categories.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f15_static_campaign_categories.dart index 524469d2100..3392101cf10 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f15_static_campaign_categories.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/constant/f15_static_campaign_categories.dart @@ -10,8 +10,7 @@ final f15StaticCampaignCategories = [ //Tier-1 Enterprise Integrations CampaignCategory( selfRef: f15ConstDocumentsRefs[0].category, - proposalTemplateRef: f15ConstDocumentsRefs[0].proposal, - campaignRef: f15ConstDocumentsRefs[0].comment, + campaignRef: Campaign.f15Ref, categoryName: 'Cardano Partners:', categorySubname: 'Tier-1 Enterprise Integrations', description: @@ -139,8 +138,7 @@ Use this checklist to ensure your proposal meets all foundational and content re //Cardano Use Cases: Prototype & Launch CampaignCategory( selfRef: f15ConstDocumentsRefs[1].category, - proposalTemplateRef: f15ConstDocumentsRefs[1].proposal, - campaignRef: f15ConstDocumentsRefs[1].comment, + campaignRef: Campaign.f15Ref, categoryName: 'Cardano Use Cases:', categorySubname: 'Prototype & Launch', description: @@ -247,8 +245,7 @@ Use this checklist to ensure your proposal meets all foundational and content re // //Cardano Open: Ecosystem CampaignCategory( selfRef: f15ConstDocumentsRefs[2].category, - proposalTemplateRef: f15ConstDocumentsRefs[2].proposal, - campaignRef: f15ConstDocumentsRefs[2].comment, + campaignRef: Campaign.f15Ref, categoryName: 'Cardano Open:', categorySubname: 'Ecosystem', description: @@ -354,8 +351,7 @@ Use this checklist to ensure your proposal meets all foundational and content re //Midnight: Compact DApps CampaignCategory( selfRef: f15ConstDocumentsRefs[3].category, - proposalTemplateRef: f15ConstDocumentsRefs[3].proposal, - campaignRef: f15ConstDocumentsRefs[3].comment, + campaignRef: Campaign.f15Ref, categoryName: 'Midnight:', categorySubname: 'Compact DApps', description: diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/exception/active_campaign_not_found_exception.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/exception/active_campaign_not_found_exception.dart new file mode 100644 index 00000000000..b9d4b368b43 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/campaign/exception/active_campaign_not_found_exception.dart @@ -0,0 +1,11 @@ +import 'package:equatable/equatable.dart'; + +final class ActiveCampaignNotFoundException extends Equatable implements Exception { + const ActiveCampaignNotFoundException(); + + @override + List get props => []; + + @override + String toString() => 'ActiveCampaignNotFoundException'; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart index c1cae611a56..068e3fb9cf5 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -12,6 +12,7 @@ export 'campaign/campaign_category.dart'; export 'campaign/campaign_filters.dart'; export 'campaign/campaign_phase.dart'; export 'campaign/campaign_timeline.dart'; +export 'campaign/exception/active_campaign_not_found_exception.dart'; export 'common/hi_lo/hi_lo.dart'; export 'common/hi_lo/uuid_hi_lo.dart'; export 'common/markdown_data.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/constant/constant_documents_refs.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/constant/constant_documents_refs.dart index ad8515de7dd..d59250ec438 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/constant/constant_documents_refs.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/document/constant/constant_documents_refs.dart @@ -4,16 +4,20 @@ import 'package:equatable/equatable.dart'; /// Groups related [proposal] and [comment] templates to given [category]. final class CategoryTemplatesRefs extends Equatable { final SignedDocumentRef category; - final SignedDocumentRef proposal; - final SignedDocumentRef comment; + final SignedDocumentRef? proposal; + final SignedDocumentRef? comment; const CategoryTemplatesRefs({ required this.category, - required this.proposal, - required this.comment, + this.proposal, + this.comment, }); - Iterable get all => [category, proposal, comment]; + Iterable get all => [ + category, + ?proposal, + ?comment, + ]; Iterable get allTyped { return [ @@ -21,11 +25,17 @@ final class CategoryTemplatesRefs extends Equatable { ref: category, type: DocumentType.categoryParametersDocument, ), - TypedDocumentRef(ref: proposal, type: DocumentType.proposalTemplate), - TypedDocumentRef(ref: comment, type: DocumentType.commentTemplate), + if (proposal case final value?) + TypedDocumentRef(ref: value, type: DocumentType.proposalTemplate), + if (comment case final value?) + TypedDocumentRef(ref: value, type: DocumentType.commentTemplate), ]; } @override - List get props => [category, proposal, comment]; + List get props => [ + category, + proposal, + comment, + ]; } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_dao.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_dao.dart index aead227bea8..28193a8a7dd 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_dao.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/database/dao/documents_dao.dart @@ -58,13 +58,16 @@ abstract interface class DocumentsDao { Future> queryAll({ DocumentRef? ref, DocumentType? type, + CampaignFilters? campaign, }); /// Returns all known document refs. Future> queryAllTypedRefs(); Future queryLatestDocumentData({ + DocumentType? type, CatalystId? authorId, + DocumentRef? category, }); /// Returns document with matching refTo and type. @@ -219,6 +222,7 @@ class DriftDocumentsDao extends DatabaseAccessor Future> queryAll({ DocumentRef? ref, DocumentType? type, + CampaignFilters? campaign, }) { final query = select(documents); @@ -228,6 +232,9 @@ class DriftDocumentsDao extends DatabaseAccessor if (type != null) { query.where((doc) => doc.type.equals(type.uuid)); } + if (campaign != null) { + query.where((tbl) => tbl.metadata.isInCategoryList(campaign.categoriesIds)); + } return query.get(); } @@ -285,16 +292,26 @@ class DriftDocumentsDao extends DatabaseAccessor @override Future queryLatestDocumentData({ + DocumentType? type, CatalystId? authorId, + DocumentRef? category, }) { final query = select(documents) ..orderBy([(t) => OrderingTerm.desc(t.verHi)]) ..limit(1); + if (type != null) { + query.where((tbl) => tbl.type.equalsValue(type)); + } + if (authorId != null) { query.where((tbl) => tbl.metadata.isAuthor(authorId)); } + if (category != null) { + query.where((tbl) => tbl.metadata.isInCategoryList([category.id])); + } + return query.getSingleOrNull(); } diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/document_repository.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/document_repository.dart index 5ac2adfe7f9..53270285e06 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/document_repository.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/document_repository.dart @@ -85,7 +85,9 @@ abstract interface class DocumentRepository { /// latest [DocumentData] which of [authorId] and check /// username used in [CatalystId] in that document. Future getLatestDocument({ + DocumentType? type, CatalystId? authorId, + DocumentRef? category, }); /// Returns count of documents matching [ref] id and [type]. @@ -94,6 +96,14 @@ abstract interface class DocumentRepository { required DocumentType type, }); + /// Returns list of known refs given provided filters. + Future> getRefs({ + DocumentType? type, + CampaignFilters? campaign, + int limit, + int offset, + }); + Future getRefToDocumentData({ required DocumentRef refTo, required DocumentType type, @@ -328,11 +338,19 @@ final class DocumentRepositoryImpl implements DocumentRepository { }; } + // TODO(damian-molinski): this will be removed and replaced on performance branch so + // i'm not implementing drafts now. @override Future getLatestDocument({ + DocumentType? type, CatalystId? authorId, + DocumentRef? category, }) async { - final latestDocument = await _localDocuments.getLatest(authorId: authorId); + final latestDocument = await _localDocuments.getLatest( + type: type, + authorId: authorId, + category: category, + ); final latestDraft = await _drafts.getLatest(authorId: authorId); return [latestDocument, latestDraft].nonNulls.sorted((a, b) => a.compareTo(b)).firstOrNull; @@ -346,6 +364,21 @@ final class DocumentRepositoryImpl implements DocumentRepository { return _localDocuments.getRefCount(ref: ref, type: type); } + @override + Future> getRefs({ + DocumentType? type, + CampaignFilters? campaign, + int limit = 100, + int offset = 0, + }) { + return _localDocuments.getRefs( + type: type, + campaign: campaign, + limit: limit, + offset: offset, + ); + } + @override Future getRefToDocumentData({ required DocumentRef refTo, diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/exception/document_exception.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/exception/document_exception.dart index e7a342b1ca5..635bbed4d86 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/exception/document_exception.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/exception/document_exception.dart @@ -33,6 +33,15 @@ final class DraftNotFoundException implements DocumentException { String toString() => 'Draft matching $ref not found'; } +final class ProposalTemplateNotFoundException implements DocumentException { + final DocumentRef category; + + const ProposalTemplateNotFoundException({required this.category}); + + @override + String toString() => 'Proposal template for category $category not found'; +} + /// Exception thrown when signed document content type is unknown. final class UnknownDocumentContentTypeException implements DocumentException { final DocumentContentType type; diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart index 4d64c93f801..e196c7e1e36 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/database_documents_data_source.dart @@ -44,10 +44,12 @@ final class DatabaseDocumentsDataSource @override Future getLatest({ + DocumentType? type, CatalystId? authorId, + DocumentRef? category, }) { return _database.documentsDao - .queryLatestDocumentData(authorId: authorId) + .queryLatestDocumentData(type: type, authorId: authorId, category: category) .then((value) => value?.toModel()); } @@ -83,6 +85,19 @@ final class DatabaseDocumentsDataSource return _database.documentsDao.countRefDocumentByType(ref: ref, type: type); } + @override + Future> getRefs({ + DocumentType? type, + CampaignFilters? campaign, + int limit = 100, + int offset = 0, + }) { + // TODO(damian-molinski): This implementation will be replaced at performance branch + return _database.documentsDao + .queryAll(type: type, campaign: campaign) + .then((value) => value.map((e) => e.metadata.selfRef.toSignedDocumentRef()).toList()); + } + @override Future getRefToDocumentData({ required DocumentRef refTo, diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_local_source.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_local_source.dart index 9080c261f43..bb40fd9e8e6 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_local_source.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/document/source/document_data_local_source.dart @@ -47,11 +47,25 @@ abstract interface class DraftDataSource implements DocumentDataLocalSource { abstract interface class SignedDocumentDataSource implements DocumentDataLocalSource { Future deleteAllRespectingLocalDrafts(); + @override + Future getLatest({ + DocumentType? type, + CatalystId? authorId, + DocumentRef? category, + }); + Future getRefCount({ required DocumentRef ref, required DocumentType type, }); + Future> getRefs({ + DocumentType? type, + CampaignFilters? campaign, + int limit, + int offset, + }); + Future getRefToDocumentData({ required DocumentRef refTo, required DocumentType type, diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/proposal/proposal_repository.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/proposal/proposal_repository.dart index f9208d2818a..c480b767279 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/proposal/proposal_repository.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/lib/src/proposal/proposal_repository.dart @@ -45,11 +45,11 @@ abstract interface class ProposalRepository { required ProposalsOrder order, }); - /// Returns [ProposalTemplate] for matching [ref]. + /// Returns [ProposalTemplate] which belongs to [category]. /// - /// Source of data depends whether [ref] is [SignedDocumentRef] or [DraftRef]. - Future getProposalTemplate({ - required DocumentRef ref, + /// Returns null if no matching template is found. + Future getProposalTemplate({ + required DocumentRef category, }); Future publishProposal({ @@ -189,12 +189,19 @@ final class ProposalRepositoryImpl implements ProposalRepository { } @override - Future getProposalTemplate({ - required DocumentRef ref, + Future getProposalTemplate({ + required DocumentRef category, }) async { - final proposalDocument = await _documentRepository.getDocumentData(ref: ref); + final document = await _documentRepository.getLatestDocument( + type: DocumentType.proposalTemplate, + category: category, + ); + + if (document == null) { + return null; + } - return _buildProposalTemplate(documentData: proposalDocument); + return _buildProposalTemplate(documentData: document); } @override diff --git a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_repository_test.dart b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_repository_test.dart index addac1720ab..fe3c5655382 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_repository_test.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_repositories/test/src/document/document_repository_test.dart @@ -408,9 +408,10 @@ void main() { final constTemplatesRefs = activeConstantDocumentRefs .expand( (element) => [ - element.proposal.toTyped(DocumentType.proposalTemplate), + element.proposal?.toTyped(DocumentType.proposalTemplate), ], ) + .nonNulls .toList(); final docsRefs = List.generate( diff --git a/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/proposal/proposal_service.dart b/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/proposal/proposal_service.dart index b4e4a1986c6..57fc63ddbf9 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/proposal/proposal_service.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_services/lib/src/proposal/proposal_service.dart @@ -68,7 +68,7 @@ abstract interface class ProposalService { }); Future getProposalTemplate({ - required DocumentRef ref, + required DocumentRef category, }); /// Imports the proposal from [data] encoded by [encodeProposalForExport]. @@ -322,20 +322,28 @@ final class ProposalServiceImpl implements ProposalService { @override Future getProposalTemplate({ - required DocumentRef ref, + required DocumentRef category, }) async { - final proposalTemplate = await _proposalRepository.getProposalTemplate( - ref: ref, - ); + final template = await _proposalRepository.getProposalTemplate(category: category); + if (template == null) { + throw ProposalTemplateNotFoundException(category: category); + } - return proposalTemplate; + return template; } @override Future importProposal(Uint8List data) async { - final allowTemplateRefs = - _activeCampaignObserver.campaign?.categories.map((e) => e.proposalTemplateRef).toList() ?? - []; + final activeCampaign = _activeCampaignObserver.campaign; + if (activeCampaign == null) { + throw const ActiveCampaignNotFoundException(); + } + + final campaignFilters = CampaignFilters.from(activeCampaign); + final allowTemplateRefs = await _documentRepository.getRefs( + type: DocumentType.proposalTemplate, + campaign: campaignFilters, + ); final parsedDocument = await _documentRepository.parseDocumentForImport(data: data);