@@ -2064,6 +2064,9 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
20642064 const originalChallengePhases = _ . cloneDeep ( challenge . phases || [ ] ) ;
20652065 const auditUserId = _ . toString ( currentUser . userId ) ;
20662066 const existingPrizeType = challengeHelper . validatePrizeSetsAndGetPrizeType ( challenge . prizeSets ) ;
2067+ const payloadIncludesTerms =
2068+ ! _ . isNil ( data ) && Object . prototype . hasOwnProperty . call ( data , "terms" ) ;
2069+ const originalTermsValue = payloadIncludesTerms ? data . terms : undefined ;
20672070
20682071 // No conversion needed - values are already in dollars in the database
20692072
@@ -2111,6 +2114,9 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
21112114
21122115 // Remove fields from data that are not allowed to be updated and that match the existing challenge
21132116 data = sanitizeData ( sanitizeChallenge ( data ) , challenge ) ;
2117+ const sanitizedIncludesTerms = Object . prototype . hasOwnProperty . call ( data , "terms" ) ;
2118+ const shouldReplaceTerms =
2119+ sanitizedIncludesTerms || ( payloadIncludesTerms && originalTermsValue === null ) ;
21142120 logger . debug ( `Sanitized Data: ${ JSON . stringify ( data ) } ` ) ;
21152121
21162122 logger . debug ( `updateChallenge(${ challengeId } ): fetching challenge resources` ) ;
@@ -2639,7 +2645,7 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
26392645 if ( _ . isNil ( updateData . attachment ) ) {
26402646 await tx . attachment . deleteMany ( { where : { challengeId } } ) ;
26412647 }
2642- if ( _ . isNil ( updateData . terms ) ) {
2648+ if ( shouldReplaceTerms ) {
26432649 await tx . challengeTerm . deleteMany ( { where : { challengeId } } ) ;
26442650 }
26452651 // if (_.isNil(updateData.skills)) {
@@ -2987,13 +2993,6 @@ async function ensureScorecardChangeDoesNotConflict({
29872993 updatedReviewers = [ ] ,
29882994 originalChallengePhases = [ ] ,
29892995} ) {
2990- if ( ! config . REVIEW_DB_URL ) {
2991- logger . debug (
2992- `Skipping scorecard change guard for challenge ${ challengeId } because REVIEW_DB_URL is not configured`
2993- ) ;
2994- return ;
2995- }
2996-
29972996 if ( ! Array . isArray ( originalReviewers ) || originalReviewers . length === 0 ) {
29982997 return ;
29992998 }
@@ -3077,15 +3076,31 @@ async function ensureScorecardChangeDoesNotConflict({
30773076 phaseIdToChallengePhaseIds . get ( phaseKey ) . add ( String ( phase . id ) ) ;
30783077 }
30793078
3080- const reviewClient = getReviewClient ( ) ;
3081- const hasReviewModel =
3082- reviewClient &&
3083- typeof reviewClient . review === "object" &&
3084- typeof reviewClient . review ?. count === "function" ;
3085-
3086- if ( ! hasReviewModel ) {
3079+ if ( ! config . REVIEW_DB_URL ) {
30873080 logger . debug (
3088- `Prisma review model is unavailable; falling back to raw query guard for challenge ${ challengeId } `
3081+ `Skipping scorecard change guard for challenge ${ challengeId } because REVIEW_DB_URL is not configured`
3082+ ) ;
3083+ return ;
3084+ }
3085+
3086+ let reviewClient ;
3087+ try {
3088+ reviewClient = getReviewClient ( ) ;
3089+ } catch ( error ) {
3090+ logger . warn (
3091+ `Unable to initialize review Prisma client for challenge ${ challengeId } : ${ error . message } `
3092+ ) ;
3093+ throw new errors . ServiceUnavailableError (
3094+ "Cannot change the scorecard because review status could not be verified. Please try again later."
3095+ ) ;
3096+ }
3097+
3098+ if ( ! reviewClient || typeof reviewClient . $queryRaw !== "function" ) {
3099+ logger . warn (
3100+ `Prisma review client does not support raw queries for challenge ${ challengeId } `
3101+ ) ;
3102+ throw new errors . ServiceUnavailableError (
3103+ "Cannot change the scorecard because review status could not be verified. Please try again later."
30893104 ) ;
30903105 }
30913106
@@ -3113,6 +3128,73 @@ async function ensureScorecardChangeDoesNotConflict({
31133128 openReviewPhaseNameByKey . has ( phaseKey )
31143129 ) ;
31153130
3131+ const reviewPhaseKeysSet = new Set ( removedScorecardsByPhase . keys ( ) ) ;
3132+ const challengePhaseIdsToInspect = new Set ( ) ;
3133+ for ( const phaseKey of reviewPhaseKeysSet ) {
3134+ const challengePhaseIds = phaseIdToChallengePhaseIds . get ( phaseKey ) ;
3135+ if ( ! challengePhaseIds ) {
3136+ continue ;
3137+ }
3138+ for ( const challengePhaseId of challengePhaseIds ) {
3139+ if ( ! _ . isNil ( challengePhaseId ) ) {
3140+ challengePhaseIdsToInspect . add ( String ( challengePhaseId ) ) ;
3141+ }
3142+ }
3143+ }
3144+
3145+ const blockingCountByPhase = new Map ( ) ;
3146+ const blockingCountByPhaseAndScorecard = new Map ( ) ;
3147+
3148+ const challengePhaseIdList = Array . from ( challengePhaseIdsToInspect ) ;
3149+ if ( challengePhaseIdList . length > 0 ) {
3150+ const reviewSchema = String ( config . REVIEW_DB_SCHEMA || "" ) . trim ( ) ;
3151+ const reviewTableIdentifier = Prisma . raw (
3152+ reviewSchema
3153+ ? `"${ reviewSchema . replace ( / " / g, '""' ) } "."review"`
3154+ : '"review"'
3155+ ) ;
3156+
3157+ let blockingReviewRows = [ ] ;
3158+ try {
3159+ blockingReviewRows = await reviewClient . $queryRaw `
3160+ SELECT "phaseId", "scorecardId", COUNT(*)::int AS "count"
3161+ FROM ${ reviewTableIdentifier }
3162+ WHERE "phaseId" IN (${ Prisma . join ( challengePhaseIdList ) } )
3163+ AND "status" IN (${ Prisma . join ( REVIEW_STATUS_BLOCKING ) } )
3164+ GROUP BY "phaseId", "scorecardId"
3165+ ` ;
3166+ } catch ( error ) {
3167+ logger . warn (
3168+ `Failed to query the review database for challenge ${ challengeId } : ${ error . message } `
3169+ ) ;
3170+ throw new errors . ServiceUnavailableError (
3171+ "Cannot change the scorecard because review status could not be verified. Please try again later."
3172+ ) ;
3173+ }
3174+
3175+ for ( const row of blockingReviewRows || [ ] ) {
3176+ const phaseId = _ . isNil ( row ?. phaseId ) ? null : String ( row . phaseId ) ;
3177+ if ( ! phaseId ) {
3178+ continue ;
3179+ }
3180+ const countValue = Number ( _ . get ( row , "count" , 0 ) ) ;
3181+ if ( ! Number . isFinite ( countValue ) || countValue <= 0 ) {
3182+ continue ;
3183+ }
3184+
3185+ blockingCountByPhase . set ( phaseId , ( blockingCountByPhase . get ( phaseId ) || 0 ) + countValue ) ;
3186+
3187+ const scorecardId = _ . isNil ( row ?. scorecardId ) ? null : String ( row . scorecardId ) ;
3188+ if ( scorecardId ) {
3189+ const scorecardKey = `${ phaseId } |${ scorecardId } ` ;
3190+ blockingCountByPhaseAndScorecard . set (
3191+ scorecardKey ,
3192+ ( blockingCountByPhaseAndScorecard . get ( scorecardKey ) || 0 ) + countValue
3193+ ) ;
3194+ }
3195+ }
3196+ }
3197+
31163198 if ( reviewPhaseKeysToCheck . length > 0 ) {
31173199 for ( const phaseKey of reviewPhaseKeysToCheck ) {
31183200 const challengePhaseIds = Array . from ( phaseIdToChallengePhaseIds . get ( phaseKey ) || [ ] ) ;
@@ -3124,38 +3206,8 @@ async function ensureScorecardChangeDoesNotConflict({
31243206 }
31253207
31263208 let activePhaseReviewCount = 0 ;
3127-
3128- if ( hasReviewModel ) {
3129- activePhaseReviewCount = await reviewClient . review . count ( {
3130- where : {
3131- phaseId : { in : challengePhaseIds } ,
3132- status : { in : REVIEW_STATUS_BLOCKING } ,
3133- } ,
3134- } ) ;
3135- } else if ( typeof reviewClient ?. $queryRaw === "function" ) {
3136- try {
3137- const queryResult = await reviewClient . $queryRaw `
3138- SELECT COUNT(*)::int AS count
3139- FROM "review"
3140- WHERE "phaseId" IN (${ Prisma . join ( challengePhaseIds ) } )
3141- AND "status" IN (${ Prisma . join ( REVIEW_STATUS_BLOCKING ) } )
3142- ` ;
3143- const rawCount =
3144- Array . isArray ( queryResult ) && queryResult . length > 0
3145- ? Number ( queryResult [ 0 ] ?. count || 0 )
3146- : 0 ;
3147- activePhaseReviewCount = Number . isFinite ( rawCount ) ? rawCount : 0 ;
3148- } catch ( error ) {
3149- logger . warn (
3150- `Skipping active phase scorecard guard for challenge ${ challengeId } phase ${ phaseKey } due to review DB query failure: ${ error . message } `
3151- ) ;
3152- continue ;
3153- }
3154- } else {
3155- logger . warn (
3156- `Skipping active phase scorecard guard for challenge ${ challengeId } phase ${ phaseKey } because review Prisma client does not support raw queries`
3157- ) ;
3158- continue ;
3209+ for ( const challengePhaseId of challengePhaseIds ) {
3210+ activePhaseReviewCount += blockingCountByPhase . get ( challengePhaseId ) || 0 ;
31593211 }
31603212
31613213 if ( activePhaseReviewCount > 0 ) {
@@ -3182,42 +3234,11 @@ async function ensureScorecardChangeDoesNotConflict({
31823234 continue ;
31833235 }
31843236
3237+ const normalizedScorecardId = String ( scorecardId ) ;
31853238 let conflictingReviews = 0 ;
3186-
3187- if ( hasReviewModel ) {
3188- conflictingReviews = await reviewClient . review . count ( {
3189- where : {
3190- phaseId : { in : challengePhaseIds } ,
3191- scorecardId,
3192- status : { in : REVIEW_STATUS_BLOCKING } ,
3193- } ,
3194- } ) ;
3195- } else if ( typeof reviewClient ?. $queryRaw === "function" ) {
3196- try {
3197- const queryResult = await reviewClient . $queryRaw `
3198- SELECT COUNT(*)::int AS count
3199- FROM "review"
3200- WHERE "phaseId" IN (${ Prisma . join ( challengePhaseIds ) } )
3201- AND "scorecardId" = ${ scorecardId }
3202- AND "status" IN (${ Prisma . join ( REVIEW_STATUS_BLOCKING ) } )
3203- ` ;
3204-
3205- const rawCount =
3206- Array . isArray ( queryResult ) && queryResult . length > 0
3207- ? Number ( queryResult [ 0 ] ?. count || 0 )
3208- : 0 ;
3209- conflictingReviews = Number . isFinite ( rawCount ) ? rawCount : 0 ;
3210- } catch ( error ) {
3211- logger . warn (
3212- `Skipping scorecard change guard for challenge ${ challengeId } phase ${ phaseKey } due to review DB query failure: ${ error . message } `
3213- ) ;
3214- continue ;
3215- }
3216- } else {
3217- logger . warn (
3218- `Skipping scorecard change guard for challenge ${ challengeId } phase ${ phaseKey } because review Prisma client does not support raw queries`
3219- ) ;
3220- continue ;
3239+ for ( const challengePhaseId of challengePhaseIds ) {
3240+ const scorecardKey = `${ challengePhaseId } |${ normalizedScorecardId } ` ;
3241+ conflictingReviews += blockingCountByPhaseAndScorecard . get ( scorecardKey ) || 0 ;
32213242 }
32223243
32233244 if ( conflictingReviews > 0 ) {
0 commit comments