@@ -2089,6 +2089,17 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
20892089 }
20902090 }
20912091
2092+ // Treat incoming `reviews` payloads as an alias for `reviewers`
2093+ if ( ! _ . isNil ( data . reviews ) ) {
2094+ if ( ! Array . isArray ( data . reviews ) ) {
2095+ throw new BadRequestError ( "reviews must be an array" ) ;
2096+ }
2097+ if ( _ . isNil ( data . reviewers ) ) {
2098+ data . reviewers = _ . cloneDeep ( data . reviews ) ;
2099+ }
2100+ delete data . reviews ;
2101+ }
2102+
20922103 // Remove fields from data that are not allowed to be updated and that match the existing challenge
20932104 data = sanitizeData ( sanitizeChallenge ( data ) , challenge ) ;
20942105 logger . debug ( `Sanitized Data: ${ JSON . stringify ( data ) } ` ) ;
@@ -3555,6 +3566,96 @@ async function advancePhase(currentUser, challengeId, data) {
35553566 } ;
35563567}
35573568
3569+ async function closeMarathonMatch ( currentUser , challengeId ) {
3570+ logger . info ( `Close Marathon Match Request - ${ challengeId } ` ) ;
3571+ const machineOrAdmin = currentUser && ( currentUser . isMachine || hasAdminRole ( currentUser ) ) ;
3572+ if ( ! machineOrAdmin ) {
3573+ throw new errors . ForbiddenError (
3574+ `Admin role or an M2M token is required to close the marathon match.`
3575+ ) ;
3576+ }
3577+
3578+ const challenge = await prisma . challenge . findUnique ( {
3579+ where : { id : challengeId } ,
3580+ include : includeReturnFields ,
3581+ } ) ;
3582+
3583+ if ( _ . isNil ( challenge ) || _ . isNil ( challenge . id ) ) {
3584+ throw new errors . NotFoundError ( `Challenge with id: ${ challengeId } doesn't exist.` ) ;
3585+ }
3586+
3587+ if ( ! challenge . type || challenge . type . name !== "Marathon Match" ) {
3588+ throw new errors . BadRequestError (
3589+ `Challenge with id: ${ challengeId } is not a Marathon Match challenge.`
3590+ ) ;
3591+ }
3592+
3593+ const reviewSummations = await helper . getReviewSummations ( challengeId ) ;
3594+ const finalSummations = ( reviewSummations || [ ] ) . filter ( ( summation ) => summation . isFinal === true ) ;
3595+
3596+ const orderedSummations = _ . orderBy (
3597+ finalSummations ,
3598+ [ "aggregateScore" , "createdAt" ] ,
3599+ [ "desc" , "asc" ]
3600+ ) ;
3601+
3602+ const winners = orderedSummations . map ( ( summation , index ) => {
3603+ const parsedUserId = Number ( summation . submitterId ) ;
3604+ if ( ! Number . isFinite ( parsedUserId ) || ! Number . isInteger ( parsedUserId ) ) {
3605+ throw new errors . BadRequestError (
3606+ `Invalid submitterId ${ summation . submitterId } for review summation winner`
3607+ ) ;
3608+ }
3609+
3610+ return {
3611+ userId : parsedUserId ,
3612+ handle : summation . submitterHandle ,
3613+ placement : index + 1 ,
3614+ type : PrizeSetTypeEnum . PLACEMENT ,
3615+ } ;
3616+ } ) ;
3617+
3618+ if ( winners . length > 0 ) {
3619+ const challengeResources = await helper . getChallengeResources ( challengeId ) ;
3620+ const submitterResources = challengeResources . filter (
3621+ ( resource ) => resource . roleId === config . SUBMITTER_ROLE_ID
3622+ ) ;
3623+ const missingResources = winners . filter (
3624+ ( winner ) =>
3625+ ! submitterResources . some (
3626+ ( resource ) => _ . toString ( resource . memberId ) === _ . toString ( winner . userId )
3627+ )
3628+ ) ;
3629+ if ( missingResources . length > 0 ) {
3630+ throw new errors . BadRequestError (
3631+ `Submitter resources are required to close Marathon Match challenge ${ challengeId } . Missing submitter resources for userIds: ${ missingResources
3632+ . map ( ( winner ) => winner . userId )
3633+ . join ( ", " ) } `
3634+ ) ;
3635+ }
3636+ }
3637+
3638+ const closedAt = new Date ( ) . toISOString ( ) ;
3639+ const updatedPhases = ( challenge . phases || [ ] ) . map ( ( phase ) => ( {
3640+ ...phase ,
3641+ isOpen : false ,
3642+ actualEndDate : closedAt ,
3643+ } ) ) ;
3644+
3645+ const updatedChallenge = await updateChallenge (
3646+ currentUser ,
3647+ challengeId ,
3648+ {
3649+ winners,
3650+ phases : updatedPhases ,
3651+ status : ChallengeStatusEnum . COMPLETED ,
3652+ } ,
3653+ { emitEvent : true }
3654+ ) ;
3655+
3656+ return updatedChallenge ;
3657+ }
3658+
35583659advancePhase . schema = {
35593660 currentUser : Joi . any ( ) ,
35603661 challengeId : Joi . id ( ) ,
@@ -3566,6 +3667,11 @@ advancePhase.schema = {
35663667 . required ( ) ,
35673668} ;
35683669
3670+ closeMarathonMatch . schema = {
3671+ currentUser : Joi . any ( ) ,
3672+ challengeId : Joi . id ( ) ,
3673+ } ;
3674+
35693675async function indexChallengeAndPostToKafka ( updatedChallenge , track , type ) {
35703676 const prizeType = challengeHelper . validatePrizeSetsAndGetPrizeType ( updatedChallenge . prizeSets ) ;
35713677
@@ -3610,6 +3716,7 @@ module.exports = {
36103716 getChallengeStatistics,
36113717 sendNotifications,
36123718 advancePhase,
3719+ closeMarathonMatch,
36133720 getDefaultReviewers,
36143721 setDefaultReviewers,
36153722 indexChallengeAndPostToKafka,
0 commit comments