@@ -22,6 +22,7 @@ import { Request } from 'express';
2222import FormData from 'form-data' ;
2323
2424import { ResourceNotFoundError } from '../errors' ;
25+ import { pollEndpoint } from '../helpers/poll-endpoint.helper' ;
2526import { fetchAllQueryResources } from '../helpers/query-service.helper' ;
2627import { logger } from '../services/logger.service' ;
2728import { getUsernameFromAuth } from '../utils/auth-helper' ;
@@ -93,6 +94,7 @@ interface CommitteeDocumentQueryResult {
9394 content_type ?: string ;
9495 description ?: string ;
9596 committee_uid ?: string ;
97+ folder_uid ?: string ;
9698 created_at ?: string ;
9799 updated_at ?: string ;
98100 uploaded_by_username ?: string ;
@@ -792,6 +794,7 @@ export class CommitteeService {
792794 created_at : f . created_at ,
793795 updated_at : f . updated_at ,
794796 uploaded_by : f . uploaded_by_username ,
797+ parent_uid : f . folder_uid ,
795798 committee_uid : f . committee_uid ,
796799 } ) ) ;
797800
@@ -888,10 +891,8 @@ export class CommitteeService {
888891 file_size : uploadData . file_size ,
889892 } ) ;
890893
891- // file_size is intentionally omitted — upstream UploadCommitteeDocumentRequestBody
892- // only declares name, file_name, content_type, file, description. Goa silently
893- // drops unknown multipart fields.
894- // TODO: append folder_uid once upstream accepts it (LFXV2-1632).
894+ // file_size is intentionally omitted — upstream UploadCommitteeDocumentRequestBody declares
895+ // name, file_name, content_type, file, description, folder_uid. Goa drops unknown fields.
895896 const formData = new FormData ( ) ;
896897 formData . append ( 'file' , fileBuffer , {
897898 filename : uploadData . file_name ,
@@ -903,14 +904,19 @@ export class CommitteeService {
903904 if ( uploadData . description ) {
904905 formData . append ( 'description' , uploadData . description ) ;
905906 }
907+ if ( uploadData . folder_uid ) {
908+ formData . append ( 'folder_uid' , uploadData . folder_uid ) ;
909+ }
906910
911+ // X-Sync=true blocks until the upstream indexer ACKs the publish, preventing stale list reads.
907912 const result = await this . microserviceProxy . proxyRequest < CommitteeDocumentUpstreamResponse > (
908913 req ,
909914 'LFX_V2_SERVICE' ,
910915 `/committees/${ committeeId } /documents` ,
911916 'POST' ,
912917 undefined ,
913- formData
918+ formData ,
919+ { 'X-Sync' : 'true' }
914920 ) ;
915921
916922 logger . info ( req , 'upload_committee_document' , 'Committee document uploaded successfully' , {
@@ -920,6 +926,28 @@ export class CommitteeService {
920926 file_size : result . file_size ,
921927 } ) ;
922928
929+ // Poll until the query service sees the new doc — indexer is async to the upstream write.
930+ await pollEndpoint ( {
931+ req,
932+ operation : 'upload_committee_document_index_poll' ,
933+ pollFn : async ( ) => {
934+ const { resources } = await this . microserviceProxy . proxyRequest < QueryServiceResponse < { uid : string } > > (
935+ req ,
936+ 'LFX_V2_SERVICE' ,
937+ '/query/resources' ,
938+ 'GET' ,
939+ {
940+ type : 'committee_document' ,
941+ tags : `committee_document_uid:${ result . uid } ` ,
942+ }
943+ ) ;
944+ return ( resources ?. length ?? 0 ) > 0 ;
945+ } ,
946+ maxRetries : 5 ,
947+ retryDelayMs : 400 ,
948+ metadata : { committee_uid : committeeId , document_uid : result . uid } ,
949+ } ) ;
950+
923951 return {
924952 uid : result . uid ,
925953 type : 'file' ,
0 commit comments