@@ -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 ) } ` ) ;
@@ -3556,6 +3567,96 @@ async function advancePhase(currentUser, challengeId, data) {
35563567 } ;
35573568}
35583569
3570+ async function closeMarathonMatch ( currentUser , challengeId ) {
3571+ logger . info ( `Close Marathon Match Request - ${ challengeId } ` ) ;
3572+ const machineOrAdmin = currentUser && ( currentUser . isMachine || hasAdminRole ( currentUser ) ) ;
3573+ if ( ! machineOrAdmin ) {
3574+ throw new errors . ForbiddenError (
3575+ `Admin role or an M2M token is required to close the marathon match.`
3576+ ) ;
3577+ }
3578+
3579+ const challenge = await prisma . challenge . findUnique ( {
3580+ where : { id : challengeId } ,
3581+ include : includeReturnFields ,
3582+ } ) ;
3583+
3584+ if ( _ . isNil ( challenge ) || _ . isNil ( challenge . id ) ) {
3585+ throw new errors . NotFoundError ( `Challenge with id: ${ challengeId } doesn't exist.` ) ;
3586+ }
3587+
3588+ if ( ! challenge . type || challenge . type . name !== "Marathon Match" ) {
3589+ throw new errors . BadRequestError (
3590+ `Challenge with id: ${ challengeId } is not a Marathon Match challenge.`
3591+ ) ;
3592+ }
3593+
3594+ const reviewSummations = await helper . getReviewSummations ( challengeId ) ;
3595+ const finalSummations = ( reviewSummations || [ ] ) . filter ( ( summation ) => summation . isFinal === true ) ;
3596+
3597+ const orderedSummations = _ . orderBy (
3598+ finalSummations ,
3599+ [ "aggregateScore" , "createdAt" ] ,
3600+ [ "desc" , "asc" ]
3601+ ) ;
3602+
3603+ const winners = orderedSummations . map ( ( summation , index ) => {
3604+ const parsedUserId = Number ( summation . submitterId ) ;
3605+ if ( ! Number . isFinite ( parsedUserId ) || ! Number . isInteger ( parsedUserId ) ) {
3606+ throw new errors . BadRequestError (
3607+ `Invalid submitterId ${ summation . submitterId } for review summation winner`
3608+ ) ;
3609+ }
3610+
3611+ return {
3612+ userId : parsedUserId ,
3613+ handle : summation . submitterHandle ,
3614+ placement : index + 1 ,
3615+ type : PrizeSetTypeEnum . PLACEMENT ,
3616+ } ;
3617+ } ) ;
3618+
3619+ if ( winners . length > 0 ) {
3620+ const challengeResources = await helper . getChallengeResources ( challengeId ) ;
3621+ const submitterResources = challengeResources . filter (
3622+ ( resource ) => resource . roleId === config . SUBMITTER_ROLE_ID
3623+ ) ;
3624+ const missingResources = winners . filter (
3625+ ( winner ) =>
3626+ ! submitterResources . some (
3627+ ( resource ) => _ . toString ( resource . memberId ) === _ . toString ( winner . userId )
3628+ )
3629+ ) ;
3630+ if ( missingResources . length > 0 ) {
3631+ throw new errors . BadRequestError (
3632+ `Submitter resources are required to close Marathon Match challenge ${ challengeId } . Missing submitter resources for userIds: ${ missingResources
3633+ . map ( ( winner ) => winner . userId )
3634+ . join ( ", " ) } `
3635+ ) ;
3636+ }
3637+ }
3638+
3639+ const closedAt = new Date ( ) . toISOString ( ) ;
3640+ const updatedPhases = ( challenge . phases || [ ] ) . map ( ( phase ) => ( {
3641+ ...phase ,
3642+ isOpen : false ,
3643+ actualEndDate : closedAt ,
3644+ } ) ) ;
3645+
3646+ const updatedChallenge = await updateChallenge (
3647+ currentUser ,
3648+ challengeId ,
3649+ {
3650+ winners,
3651+ phases : updatedPhases ,
3652+ status : ChallengeStatusEnum . COMPLETED ,
3653+ } ,
3654+ { emitEvent : true }
3655+ ) ;
3656+
3657+ return updatedChallenge ;
3658+ }
3659+
35593660advancePhase . schema = {
35603661 currentUser : Joi . any ( ) ,
35613662 challengeId : Joi . id ( ) ,
@@ -3567,6 +3668,11 @@ advancePhase.schema = {
35673668 . required ( ) ,
35683669} ;
35693670
3671+ closeMarathonMatch . schema = {
3672+ currentUser : Joi . any ( ) ,
3673+ challengeId : Joi . id ( ) ,
3674+ } ;
3675+
35703676async function indexChallengeAndPostToKafka ( updatedChallenge , track , type ) {
35713677 const prizeType = challengeHelper . validatePrizeSetsAndGetPrizeType ( updatedChallenge . prizeSets ) ;
35723678
@@ -3611,6 +3717,7 @@ module.exports = {
36113717 getChallengeStatistics,
36123718 sendNotifications,
36133719 advancePhase,
3720+ closeMarathonMatch,
36143721 getDefaultReviewers,
36153722 setDefaultReviewers,
36163723 indexChallengeAndPostToKafka,
0 commit comments