From c8f684ec3de9118370dcc6fa41b00df9d6f3352d Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Mon, 16 May 2016 23:22:34 +0800 Subject: [PATCH 01/67] improve control of visibility --- queries/get_user_basic_profile_achievements | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/queries/get_user_basic_profile_achievements b/queries/get_user_basic_profile_achievements index 33963ae6e..6e8897a74 100644 --- a/queries/get_user_basic_profile_achievements +++ b/queries/get_user_basic_profile_achievements @@ -4,7 +4,8 @@ SELECT , -1 as id FROM user_achievement ua JOIN coder AS c ON c.coder_id = ua.coder_id -WHERE ua.achievement_type_id in (1,4,5,6,7,8,9) AND handle_lower = LOWER('@handle@') AND c.status = 'A' +INNER JOIN achievement_type_lu atl ON atl.achievement_type_id = ua.achievement_type_id +WHERE atl.badge = 1 AND handle_lower = LOWER('@handle@') AND c.status = 'A' UNION SELECT From e9ddf034a5f4e4b1d0d86858d6d3c4262cf57fc8 Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Tue, 17 May 2016 10:09:19 -0500 Subject: [PATCH 02/67] changing log level to info --- config/logger.js | 4 ++-- local/docker-compose.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 local/docker-compose.yml diff --git a/config/logger.js b/config/logger.js index 5d4991f1a..0812c59c3 100644 --- a/config/logger.js +++ b/config/logger.js @@ -12,7 +12,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.Console)({ colorize: true, - level: 'debug', + level: 'info', timestamp: api.utils.sqlDateTime, json: false }); @@ -31,7 +31,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.File)({ filename: api.config.general.paths.log[0] + '/actionhero-worker.log', - level: 'debug', + level: 'info', colorize: true, timestamp: api.utils.sqlDateTime, json: false diff --git a/local/docker-compose.yml b/local/docker-compose.yml new file mode 100644 index 000000000..986dbdedb --- /dev/null +++ b/local/docker-compose.yml @@ -0,0 +1,6 @@ +version: '2' +services: + tc-api: + image: "node" + ports: + - "7777:7777" From 1ebea023a3b8b8f30b32280ff7dde4f3e1f54a43 Mon Sep 17 00:00:00 2001 From: API Prod Box Date: Thu, 19 May 2016 10:45:54 -0400 Subject: [PATCH 03/67] adding changes that were made directly to the api instance --- actions/user.js | 2 +- config/logger.js | 4 ++-- initializers/dataAccess.js | 7 +++++-- initializers/helper.js | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actions/user.js b/actions/user.js index c8f297fe2..88bece40f 100644 --- a/actions/user.js +++ b/actions/user.js @@ -361,7 +361,7 @@ function getUserIdentityByAuth0Id(api, connection, next) { function (cb) { try { var splits = auth0id.split('|'); - if (splits[0] === 'ad') { + if (splits[0] === 'ad' || splits[0] === 'auth0') { cb(null, [{ user_id: Number(splits[1]) }]); } else { api.helper.getProviderId(splits[0], function (err, provider) { diff --git a/config/logger.js b/config/logger.js index 5d4991f1a..0812c59c3 100644 --- a/config/logger.js +++ b/config/logger.js @@ -12,7 +12,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.Console)({ colorize: true, - level: 'debug', + level: 'info', timestamp: api.utils.sqlDateTime, json: false }); @@ -31,7 +31,7 @@ exports.default = { logger.transports.push(function (api, winston) { return new (winston.transports.File)({ filename: api.config.general.paths.log[0] + '/actionhero-worker.log', - level: 'debug', + level: 'info', colorize: true, timestamp: api.utils.sqlDateTime, json: false diff --git a/initializers/dataAccess.js b/initializers/dataAccess.js index 33ce3bdfe..9849555db 100644 --- a/initializers/dataAccess.js +++ b/initializers/dataAccess.js @@ -309,10 +309,13 @@ exports.dataAccess = function (api, next) { return; } - sql = queries[queryName].sql; + sql = queries[queryName].sql; if (!isSafeToUseJavaBridge(sql) || !api.helper.readTransaction) { connection = connectionMap[queries[queryName].db]; + api.log("######### MD #########", "info"); + api.log(JSON.stringify(connectionMap), "info"); + api.log(queryName, "info"); error = helper.checkObject(connection, "connection"); } @@ -374,4 +377,4 @@ exports.dataAccess = function (api, next) { } }; next(); -}; \ No newline at end of file +}; diff --git a/initializers/helper.js b/initializers/helper.js index 2acc131d8..2e53f1cc6 100644 --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1219,7 +1219,7 @@ helper.getProviderId = function (provider, callback) { if (provider.startsWith("salesforce")) { providerId = helper.socialProviders.salesforce; } - if (provider.startsWith("ad")) { + if (provider.startsWith("ad") || provider.startsWith("auth0")) { providerId = helper.socialProviders.ad; } if (providerId) { From 294164eccb158a73b15ecf93fe18ad44447c9541 Mon Sep 17 00:00:00 2001 From: ykohata Date: Tue, 26 Jul 2016 13:35:51 +0900 Subject: [PATCH 04/67] Fix for the issue: Do not allow a member to register for challenge if country is not set https://app.asana.com/0/152805928309317/156182574694106 --- initializers/challengeHelper.js | 6 ++++++ queries/challenge_registration_validations | 3 +++ 2 files changed, 9 insertions(+) diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 2c8b7e6d1..1a0d16748 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -161,6 +161,12 @@ exports.challengeHelper = function (api, next) { return; } + // Do not allow a member to register for challenge if country is not set + if (rows[0].comp_country_is_null) { + cb(new ForbiddenError('You cannot participate in this challenge as your country is not set.')); + return; + } + if (rows[0].project_category_id === COPILOT_POSTING_PROJECT_TYPE) { if (!rows[0].user_is_copilot && rows[0].copilot_type && rows[0].copilot_type.indexOf("Marathon Match") < 0) { cb(new ForbiddenError('You cannot participate in this challenge because you are not an active member of the copilot pool.')); diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 874e26b32..298d7888c 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -7,6 +7,7 @@ select (ce.contest_eligibility_id IS NULL) as no_elgibility_req, (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, + (coder2.comp_country_code IS NULL) as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, (pctl.name) as copilot_type from project p @@ -50,6 +51,8 @@ left outer join ( on coder.comp_country_code=country.country_code and country.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) ) on coder.coder_id = @userId@ +-- Get coder to check comp_country_code +left outer join informixoltp:coder coder2 on coder2.coder_id = @userId@ -- Get the copilot type left join ( project_copilot_type pct join project_copilot_type_lu pctl From a3f2076e5ccac57e239f74568a3e5634c663ae78 Mon Sep 17 00:00:00 2001 From: ykohata Date: Wed, 3 Aug 2016 14:00:49 +0900 Subject: [PATCH 05/67] Fix for the issue: Do not allow a member to register for challenge if country is not set (https://app.asana.com/0/152805928309317/156182574694106) - Added check for the case that the competition country is empty - Modified error messages --- initializers/challengeHelper.js | 4 ++-- queries/challenge_registration_validations | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 1a0d16748..2622da0b4 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -157,13 +157,13 @@ exports.challengeHelper = function (api, next) { } if (rows[0].user_country_banned) { - cb(new ForbiddenError('You cannot participate in this challenge as your country is banned.')); + cb(new ForbiddenError('You are not eligible to participate in this challenge because of your country of residence. Please see our terms of service for more information.')); return; } // Do not allow a member to register for challenge if country is not set if (rows[0].comp_country_is_null) { - cb(new ForbiddenError('You cannot participate in this challenge as your country is not set.')); + cb(new ForbiddenError('You are not eligible to participate in this challenge because you have not specified your country of residence. Please go to your Settings and enter a country. Please see our terms of service for more information.')); return; } diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 298d7888c..29fa1a4e8 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -7,7 +7,7 @@ select (ce.contest_eligibility_id IS NULL) as no_elgibility_req, (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, - (coder2.comp_country_code IS NULL) as comp_country_is_null, + (coder2.comp_country_code IS NULL OR coder2.comp_country_code = '') as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, (pctl.name) as copilot_type from project p From 496a28e1188272336de97755b5ea13360733deee Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 3 Aug 2016 16:45:08 -0400 Subject: [PATCH 06/67] correct submission links in registration email --- actions/challengeRegistration.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 5b0c09319..f50077b66 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -378,11 +378,13 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat } user = result[0]; - projectName = componentInfo.project_name + api.helper.getPhaseName(componentInfo.phase_id) + ' Contest'; + projectName = componentInfo.project_name; documentationDetails = ''; // we need to set up a new environment variable for the web server name specifici to each environment //submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/'; - submitURL = 'https://www.topcoder.com/challenge-details/' + challengeId + '/submit/'; + // Set the default submission URL. + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; + reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review'; if (componentInfo.phase_id === 112) { @@ -399,9 +401,11 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat if (challengeType === CHALLENGE_TYPE.DEVELOP) { forumURL = api.config.tcConfig.developForumsUrlPrefix + activeForumCategoryId; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review/actions/ViewProjectDetails?pid=' + challengeId; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; } else if (challengeType === CHALLENGE_TYPE.DESIGN) { forumURL = api.config.tcConfig.studioForumsUrlPrefix + activeForumCategoryId; - submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; + //submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; + submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenges/' + challengeId + '/submit/file/'; } if (componentInfo.review_type && componentInfo.review_type == 'PEER') From e9bdea12fc7a0899042e6f459351f6b855ab89cc Mon Sep 17 00:00:00 2001 From: ykohata Date: Mon, 8 Aug 2016 16:01:02 +0900 Subject: [PATCH 07/67] Fix for the issue: Do not allow a member to register for challenge if country is not set (https://app.asana.com/0/152805928309317/156182574694106) - Checking coder.home_country_code --- queries/challenge_registration_validations | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 29fa1a4e8..1b20283f4 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -48,8 +48,10 @@ left outer join ( -- Check coder's country left outer join ( informixoltp:coder coder join informixoltp:country country - on coder.comp_country_code=country.country_code - and country.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) + on ( + coder.comp_country_code=country.country_code OR + coder.home_country_code=country.country_code + ) and country.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) ) on coder.coder_id = @userId@ -- Get coder to check comp_country_code left outer join informixoltp:coder coder2 on coder2.coder_id = @userId@ From a90de5e57513af1de0f3d88c9327623597531334 Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Mon, 8 Aug 2016 17:52:59 +0200 Subject: [PATCH 08/67] added to noauth terms endpoint --- actions/challenges.js | 36 ++++++--- initializers/.challengeHelper.js.swp | Bin 0 -> 24576 bytes initializers/challengeHelper.js | 89 +++++++++++++++++++++ queries/challenge_terms_of_use_noauth | 13 +++ queries/challenge_terms_of_use_noauth.json | 5 ++ 5 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 initializers/.challengeHelper.js.swp create mode 100644 queries/challenge_terms_of_use_noauth create mode 100644 queries/challenge_terms_of_use_noauth.json diff --git a/actions/challenges.js b/actions/challenges.js index f4c342168..8d19e99cb 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -2237,7 +2237,7 @@ exports.getChallengeTerms = { description: "getChallengeTerms", inputs: { required: ["challengeId"], - optional: ["role"] + optional: ["role", "noauth"] }, blockedConnectionTypes: [], outputExample: {}, @@ -2251,14 +2251,26 @@ exports.getChallengeTerms = { var challengeId = Number(connection.params.challengeId), role = connection.params.role, error; async.waterfall([ function (cb) { - api.challengeHelper.getChallengeTerms( - connection, - challengeId, - role, - true, - connection.dbConnectionMap, - cb - ); + if (connection.params.noauth) { + api.challengeHelper.getChallengeTermsNoAuth( + connection, + challengeId, + role, + true, + connection.dbConnectionMap, + cb + ); + } + else { + api.challengeHelper.getChallengeTerms( + connection, + challengeId, + role, + true, + connection.dbConnectionMap, + cb + ); + } }, function (results, cb) { var res = _.find(results, function (row) { return row.agreeabilityType === 'DocuSignable' && !row.templateId; @@ -2672,7 +2684,7 @@ exports.submitForDevelopChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'write', + transaction: 'read', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp"], run: function (api, connection, next) { @@ -2697,7 +2709,7 @@ exports.uploadForDevelopChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'write', + transaction: 'read', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp"], run: function (api, connection, next) { @@ -3311,7 +3323,7 @@ exports.submitForDesignChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'write', + transaction: 'read', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp", "informixoltp"], run: function (api, connection, next) { diff --git a/initializers/.challengeHelper.js.swp b/initializers/.challengeHelper.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..bb3c5f5b0d1105ea53af51a2da0c0eb3d0f9a6cd GIT binary patch literal 24576 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 G^Zx-X{Vae0 literal 0 HcmV?d00001 diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 1a0d16748..0f53489a3 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -228,7 +228,96 @@ exports.challengeHelper = function (api, next) { } next(null, result.terms); }); + }, + getChallengeTermsNoAuth : function (connection, challengeId, role, requireRegOpen, dbConnectionMap, next) { + + var helper = api.helper, + sqlParams = {}, + result = {}, + userId = connection.caller.userId; + + async.waterfall([ + function (cb) { + + //Simple validations of the incoming parameters + var error = helper.checkPositiveInteger(challengeId, 'challengeId') || + helper.checkMaxInt(challengeId, 'challengeId'); + + if (error) { + cb(error); + return; + } + + sqlParams.challengeId = challengeId; + + // We are here. So all validations have passed. + // Next we get all roles + api.dataAccess.executeQuery("all_resource_roles", {}, dbConnectionMap, cb); + }, function (rows, cb) { + // Prepare a comma separated string of resource role names that must match + var commaSepRoleIds = "", + compiled = _.template("<%= resource_role_id %>,"), + ctr = 0, + resourceRoleFound; + if (_.isUndefined(role)) { + rows.forEach(function (row) { + commaSepRoleIds += compiled({resource_role_id: row.resource_role_id}); + ctr += 1; + if (ctr === rows.length) { + commaSepRoleIds = commaSepRoleIds.slice(0, -1); + } + }); + } else { + resourceRoleFound = _.find(rows, function (row) { + return (row.name === role); + }); + if (_.isUndefined(resourceRoleFound)) { + //The role passed in is not recognized + cb(new BadRequestError("The role: " + role + " was not found.")); + return; + } + commaSepRoleIds = resourceRoleFound.resource_role_id; + } + + // Get the terms + sqlParams.resourceRoleIds = commaSepRoleIds; + api.dataAccess.executeQuery("challenge_terms_of_use_noauth", sqlParams, dbConnectionMap, cb); + }, function (rows, cb) { + //We could just have down result.data = rows; but we need to change keys to camel case as per requirements + result.terms = []; + _.each(rows, function (row) { + + result.terms.push({ + termsOfUseId: row.terms_of_use_id, + title: row.title, + url: row.url, + agreeabilityType: row.agreeability_type, + agreed: row.agreed, + templateId: row.docusign_template_id + }); + }); + + var ids = {}; + result.terms = result.terms.filter(function(row) { + if (ids[row.termsOfUseId]) { + return false; + } else { + ids[row.termsOfUseId] = true; + return true; + } + }); + + cb(); + } + ], function (err) { + if (err) { + next(err); + return; + } + next(null, result.terms); + }); } + }; next(); diff --git a/queries/challenge_terms_of_use_noauth b/queries/challenge_terms_of_use_noauth new file mode 100644 index 000000000..315661747 --- /dev/null +++ b/queries/challenge_terms_of_use_noauth @@ -0,0 +1,13 @@ +SELECT tou.terms_of_use_id as terms_of_use_id, + tou.title as title, + tou.url as url, + touat.name as agreeability_type, + dtx.docusign_template_id +FROM project_role_terms_of_use_xref +INNER JOIN terms_of_use tou ON project_role_terms_of_use_xref.terms_of_use_id = tou.terms_of_use_id +INNER JOIN terms_of_use_agreeability_type_lu touat ON touat.terms_of_use_agreeability_type_id = tou.terms_of_use_agreeability_type_id +LEFT JOIN user_terms_of_use_xref utuox ON utuox.terms_of_use_id = tou.terms_of_use_id +LEFT JOIN terms_of_use_docusign_template_xref dtx ON dtx.terms_of_use_id = project_role_terms_of_use_xref.terms_of_use_id +WHERE project_id = @challengeId@ AND +resource_role_id IN (@resourceRoleIds@) +ORDER BY group_ind, sort_order diff --git a/queries/challenge_terms_of_use_noauth.json b/queries/challenge_terms_of_use_noauth.json new file mode 100644 index 000000000..c5e697f4b --- /dev/null +++ b/queries/challenge_terms_of_use_noauth.json @@ -0,0 +1,5 @@ +{ + "name" : "challenge_terms_of_use_noauth", + "db" : "common_oltp", + "sqlfile" : "challenge_terms_of_use_noauth" +} From 20e20e68a686abc7eeeeab99adaf5484de2cca3c Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Mon, 8 Aug 2016 17:56:39 +0200 Subject: [PATCH 09/67] fix mistakes --- initializers/.challengeHelper.js.swp | Bin 24576 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 initializers/.challengeHelper.js.swp diff --git a/initializers/.challengeHelper.js.swp b/initializers/.challengeHelper.js.swp deleted file mode 100644 index bb3c5f5b0d1105ea53af51a2da0c0eb3d0f9a6cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 G^Zx-X{Vae0 From 5b3040e423aaa93dfcc6d4ef959a19c8a0c7aadc Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Mon, 8 Aug 2016 17:57:37 +0200 Subject: [PATCH 10/67] fix more mistakes --- actions/challenges.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/challenges.js b/actions/challenges.js index 8d19e99cb..8d41277a7 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -2684,7 +2684,7 @@ exports.submitForDevelopChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'read', + transaction: 'write', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp"], run: function (api, connection, next) { @@ -2709,7 +2709,7 @@ exports.uploadForDevelopChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'read', + transaction: 'write', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp"], run: function (api, connection, next) { From 40855a46ada8b825153fff1593e0fb9ede7e983d Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Mon, 8 Aug 2016 17:58:16 +0200 Subject: [PATCH 11/67] one more mistake --- actions/challenges.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/challenges.js b/actions/challenges.js index 8d41277a7..0c40bbf2b 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -3323,7 +3323,7 @@ exports.submitForDesignChallenge = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', - transaction: 'read', + transaction: 'write', cacheEnabled : false, databases: ["tcs_catalog", "common_oltp", "informixoltp"], run: function (api, connection, next) { From aba1c4097c36f40ae9765fda53debf84935b172e Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Wed, 21 Sep 2016 14:55:20 +0200 Subject: [PATCH 12/67] Added noauth terms endpoint --- actions/terms.js | 11 ++++++----- queries/get_terms_of_use_noauth | 6 ++++++ queries/get_terms_of_use_noauth.json | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 queries/get_terms_of_use_noauth create mode 100644 queries/get_terms_of_use_noauth.json diff --git a/actions/terms.js b/actions/terms.js index 0baeb0efb..8d3e057c7 100755 --- a/actions/terms.js +++ b/actions/terms.js @@ -49,16 +49,17 @@ function validateTermsOfUseId(connection, helper, sqlParams, callback) { var getTermsOfUse = function (api, connection, dbConnectionMap, next) { var helper = api.helper, sqlParams = {}, - result = {}; + result = {}, + noauth = connection.params.noauth == "true"; //Check if the user is logged-in - if (connection.caller.accessLevel === "anon") { + if (!noauth && connection.caller.accessLevel === "anon") { helper.handleError(api, connection, new UnauthorizedError("Authentication credential was missing.")); next(connection, true); return; } - sqlParams.userId = connection.caller.userId; + sqlParams.userId = connection.caller ? connection.caller.userId || '' : ''; async.waterfall([ function (cb) { @@ -66,7 +67,7 @@ var getTermsOfUse = function (api, connection, dbConnectionMap, next) { validateTermsOfUseId(connection, helper, sqlParams, cb); }, function (cb) { - api.dataAccess.executeQuery("get_terms_of_use", sqlParams, dbConnectionMap, cb); + api.dataAccess.executeQuery(noauth ? "get_terms_of_use_noauth" : "get_terms_of_use", sqlParams, dbConnectionMap, cb); }, function (rows, cb) { if (rows.length === 0) { cb(new NotFoundError('No such terms of use exists.')); @@ -211,7 +212,7 @@ exports.getTermsOfUse = { description: "getTermsOfUse", inputs: { required: ["termsOfUseId"], - optional: [] + optional: ["noauth"] }, blockedConnectionTypes: [], outputExample: {}, diff --git a/queries/get_terms_of_use_noauth b/queries/get_terms_of_use_noauth new file mode 100644 index 000000000..68a5909fe --- /dev/null +++ b/queries/get_terms_of_use_noauth @@ -0,0 +1,6 @@ +SELECT tou.terms_of_use_id as terms_of_use_id, + tou.title as title, + tou.url as url, + tou.terms_text as text +FROM terms_of_use tou +WHERE tou.terms_of_use_id = @termsOfUseId@ diff --git a/queries/get_terms_of_use_noauth.json b/queries/get_terms_of_use_noauth.json new file mode 100644 index 000000000..b00f1ea31 --- /dev/null +++ b/queries/get_terms_of_use_noauth.json @@ -0,0 +1,5 @@ +{ + "name" : "get_terms_of_use_noauth", + "db" : "common_oltp", + "sqlfile" : "get_terms_of_use_noauth" +} From d22acdf9e554a17326b04f0ab8a259bd5fc09d0b Mon Sep 17 00:00:00 2001 From: Tom Ladendorf Date: Fri, 23 Sep 2016 15:55:01 +0200 Subject: [PATCH 13/67] updated terms noauth query --- queries/get_terms_of_use_noauth | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/queries/get_terms_of_use_noauth b/queries/get_terms_of_use_noauth index 68a5909fe..ee815774e 100644 --- a/queries/get_terms_of_use_noauth +++ b/queries/get_terms_of_use_noauth @@ -1,6 +1,10 @@ SELECT tou.terms_of_use_id as terms_of_use_id, tou.title as title, tou.url as url, - tou.terms_text as text + tou.terms_text as text, + touat.terms_of_use_agreeability_type_id as agreeability_type_id, + toudtx.docusign_template_id as docusign_template_id FROM terms_of_use tou +INNER JOIN terms_of_use_agreeability_type_lu touat ON touat.terms_of_use_agreeability_type_id = tou.terms_of_use_agreeability_type_id +LEFT JOIN terms_of_use_docusign_template_xref toudtx ON toudtx.terms_of_use_id = tou.terms_of_use_id WHERE tou.terms_of_use_id = @termsOfUseId@ From 1e67169bf45d6c2e7fc8a8f0cf43acb4e07cf747 Mon Sep 17 00:00:00 2001 From: test Date: Tue, 27 Sep 2016 18:06:45 -0700 Subject: [PATCH 14/67] Admin App - TC API Reviewer Management API --- actions/admins.js | 271 +++ actions/copilots.js | 228 ++ actions/reviewers.js | 276 ++- apiary-admin.apib | 611 +++++ ...in App - TC API Reviewer Management API.md | 121 + errors/DuplicateResourceError.js | 30 + initializers/helper.js | 111 +- queries/clear_user_rating | 1 + queries/clear_user_rating.json | 5 + queries/get_admin_resource | 1 + queries/get_admin_resource.json | 5 + queries/get_admins | 7 + queries/get_admins.json | 5 + queries/get_copilot_profile_id_by_user_id | 1 + .../get_copilot_profile_id_by_user_id.json | 5 + queries/get_copilots | 3 + queries/get_copilots.json | 5 + queries/get_next_admin_resource_id | 1 + queries/get_next_admin_resource_id.json | 5 + queries/get_next_admin_user_group_id | 1 + queries/get_next_admin_user_group_id.json | 5 + queries/get_project_category_by_category_id | 1 + .../get_project_category_by_category_id.json | 5 + queries/get_reviewer | 1 + queries/get_reviewer.json | 5 + queries/get_reviewers | 4 + queries/get_reviewers.json | 5 + queries/get_user_id_by_handle | 1 + queries/get_user_id_by_handle.json | 5 + queries/insert_admin_group | 1 + queries/insert_admin_group.json | 5 + queries/insert_admin_role | 1 + queries/insert_admin_role.json | 5 + queries/insert_new_admin_resource | 2 + queries/insert_new_admin_resource.json | 5 + queries/insert_new_admin_resource_info | 2 + queries/insert_new_admin_resource_info.json | 5 + queries/insert_new_copilot | 6 + queries/insert_new_copilot.json | 5 + queries/insert_reviewer | 1 + queries/insert_reviewer.json | 5 + queries/remove_admin_group | 1 + queries/remove_admin_group.json | 5 + queries/remove_admin_resource | 1 + queries/remove_admin_resource.json | 5 + queries/remove_admin_resource_info | 1 + queries/remove_admin_resource_info.json | 5 + queries/remove_admin_role | 1 + queries/remove_admin_role.json | 5 + queries/remove_copilot | 1 + queries/remove_copilot.json | 5 + queries/remove_reviewer | 1 + queries/remove_reviewer.json | 5 + routes.js | 37 +- test/docker/docker-compose.yml | 6 + test/postman/Reviewer_Management_API.json | 2041 +++++++++++++++++ .../Reviewer_Management_API_environment.json | 34 + test/scripts/bridge.js | 134 ++ test/sqls/admins/tcs_catalog__clean | 4 + .../sqls/admins/tcs_catalog__insert_test_data | 15 + test/sqls/copilots/tcs_catalog__clean | 1 + .../copilots/tcs_catalog__insert_test_data | 6 + test/sqls/reviewers/tcs_catalog__clean | 1 + .../reviewers/tcs_catalog__insert_test_data | 1 + test/test.admins.js | 143 ++ test/test.copilots.js | 149 ++ test/test.createAdmin.js | 185 ++ test/test.createCopilot.js | 207 ++ test/test.createReviewer.js | 284 +++ test/test.removeAdmin.js | 171 ++ test/test.removeCopilot.js | 183 ++ test/test.removeReviewer.js | 208 ++ test/test.reviewers.js | 149 ++ .../admins/expect_create_admin.json | 4 + .../expect_create_admin_with_empty_body.json | 3 + ...reate_admin_with_exist_admin_resource.json | 4 + test/test_files/admins/expect_get_admins.json | 11 + .../admins/expect_remove_admin.json | 4 + .../expect_remove_admin_with_empty_body.json | 3 + .../copilots/expect_create_copilot.json | 4 + ...expect_create_copilot_with_empty_body.json | 3 + ...opilot_with_missing_isSoftwareCopilot.json | 3 + ..._copilot_with_missing_isStudioCopilot.json | 3 + .../copilots/expect_get_copilots.json | 10 + .../copilots/expect_remove_copilot.json | 4 + ...expect_remove_copilot_with_empty_body.json | 3 + .../reviewers/expect_create_reviewer.json | 4 + ...xpect_create_reviewer_with_empty_body.json | 3 + ...eate_reviewer_with_missing_categoryId.json | 3 + .../reviewers/expect_get_reviewers.json | 12 + .../reviewers/expect_remove_reviewer.json | 4 + ...xpect_remove_reviewer_with_empty_body.json | 3 + ...move_reviewer_with_missing_categoryId.json | 3 + 93 files changed, 5842 insertions(+), 12 deletions(-) create mode 100755 actions/admins.js create mode 100755 actions/copilots.js mode change 100644 => 100755 actions/reviewers.js create mode 100755 apiary-admin.apib create mode 100755 docs/Admin App - TC API Reviewer Management API.md create mode 100755 errors/DuplicateResourceError.js mode change 100644 => 100755 initializers/helper.js create mode 100755 queries/clear_user_rating create mode 100755 queries/clear_user_rating.json create mode 100755 queries/get_admin_resource create mode 100755 queries/get_admin_resource.json create mode 100755 queries/get_admins create mode 100755 queries/get_admins.json create mode 100755 queries/get_copilot_profile_id_by_user_id create mode 100755 queries/get_copilot_profile_id_by_user_id.json create mode 100755 queries/get_copilots create mode 100755 queries/get_copilots.json create mode 100755 queries/get_next_admin_resource_id create mode 100755 queries/get_next_admin_resource_id.json create mode 100755 queries/get_next_admin_user_group_id create mode 100755 queries/get_next_admin_user_group_id.json create mode 100755 queries/get_project_category_by_category_id create mode 100755 queries/get_project_category_by_category_id.json create mode 100755 queries/get_reviewer create mode 100755 queries/get_reviewer.json create mode 100755 queries/get_reviewers create mode 100755 queries/get_reviewers.json create mode 100755 queries/get_user_id_by_handle create mode 100755 queries/get_user_id_by_handle.json create mode 100755 queries/insert_admin_group create mode 100755 queries/insert_admin_group.json create mode 100755 queries/insert_admin_role create mode 100755 queries/insert_admin_role.json create mode 100755 queries/insert_new_admin_resource create mode 100755 queries/insert_new_admin_resource.json create mode 100755 queries/insert_new_admin_resource_info create mode 100755 queries/insert_new_admin_resource_info.json create mode 100755 queries/insert_new_copilot create mode 100755 queries/insert_new_copilot.json create mode 100755 queries/insert_reviewer create mode 100755 queries/insert_reviewer.json create mode 100755 queries/remove_admin_group create mode 100755 queries/remove_admin_group.json create mode 100755 queries/remove_admin_resource create mode 100755 queries/remove_admin_resource.json create mode 100755 queries/remove_admin_resource_info create mode 100755 queries/remove_admin_resource_info.json create mode 100755 queries/remove_admin_role create mode 100755 queries/remove_admin_role.json create mode 100755 queries/remove_copilot create mode 100755 queries/remove_copilot.json create mode 100755 queries/remove_reviewer create mode 100755 queries/remove_reviewer.json create mode 100755 test/docker/docker-compose.yml create mode 100755 test/postman/Reviewer_Management_API.json create mode 100755 test/postman/Reviewer_Management_API_environment.json create mode 100755 test/scripts/bridge.js create mode 100755 test/sqls/admins/tcs_catalog__clean create mode 100755 test/sqls/admins/tcs_catalog__insert_test_data create mode 100755 test/sqls/copilots/tcs_catalog__clean create mode 100755 test/sqls/copilots/tcs_catalog__insert_test_data create mode 100755 test/sqls/reviewers/tcs_catalog__clean create mode 100755 test/sqls/reviewers/tcs_catalog__insert_test_data create mode 100755 test/test.admins.js create mode 100755 test/test.copilots.js create mode 100755 test/test.createAdmin.js create mode 100755 test/test.createCopilot.js create mode 100755 test/test.createReviewer.js create mode 100755 test/test.removeAdmin.js create mode 100755 test/test.removeCopilot.js create mode 100755 test/test.removeReviewer.js create mode 100755 test/test.reviewers.js create mode 100755 test/test_files/admins/expect_create_admin.json create mode 100755 test/test_files/admins/expect_create_admin_with_empty_body.json create mode 100755 test/test_files/admins/expect_create_admin_with_exist_admin_resource.json create mode 100755 test/test_files/admins/expect_get_admins.json create mode 100755 test/test_files/admins/expect_remove_admin.json create mode 100755 test/test_files/admins/expect_remove_admin_with_empty_body.json create mode 100755 test/test_files/copilots/expect_create_copilot.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_empty_body.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json create mode 100755 test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json create mode 100755 test/test_files/copilots/expect_get_copilots.json create mode 100755 test/test_files/copilots/expect_remove_copilot.json create mode 100755 test/test_files/copilots/expect_remove_copilot_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_create_reviewer.json create mode 100755 test/test_files/reviewers/expect_create_reviewer_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json create mode 100755 test/test_files/reviewers/expect_get_reviewers.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json create mode 100755 test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json diff --git a/actions/admins.js b/actions/admins.js new file mode 100755 index 000000000..842ebeae9 --- /dev/null +++ b/actions/admins.js @@ -0,0 +1,271 @@ +/*jslint nomen: true */ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +var _ = require('underscore'); +var async = require('async'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); + +/** + * This is the function that will actually get all admins. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getAdmins = function (api, connection, dbConnectionMap, next) { + var helper = api.helper; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + api.dataAccess.executeQuery("get_admins", {}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = {}, i, entity, type, id; + for (i = 0; i < result.length; i = i + 1) { + type = result[i].type.trim(); + id = result[i].user_id; + if (!ret[id]) { + ret[id] = { + id: result[i].user_id, + name: result[i].handle, + adminGroup: false, + adminRole: false, + managerResource: false + }; + } + entity = ret[id]; + if (type === 'Admin Group') { + entity.adminGroup = true; + } else if (type === 'Admin Role') { + entity.adminRole = true; + } else if (type === 'Manager Resource') { + entity.managerResource = true; + } + } + cb(null, { + allAdmins: _.values(ret) + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all admins + */ +exports.admins = { + name: "admins", + description: "retrieve all TopCoder admins", + inputs: { + required: [], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute admins#run", 'debug'); + getAdmins(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create admin. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createAdmin = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, userId, operatorId, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + async.auto({ + nextUserGroupId: function (ca) { + api.dataAccess.executeQuery("get_next_admin_user_group_id", {}, dbConnectionMap, ca); + }, + nextResourceId: function (ca) { + api.dataAccess.executeQuery("get_next_admin_resource_id", {}, dbConnectionMap, ca); + } + }, cb); + }, function (results, cb) { + parameters = { + userId: userId, + userGroupId: results.nextUserGroupId[0].next_id, + operatorId: operatorId, + resourceId: results.nextResourceId[0].next_id + }; + api.dataAccess.executeQuery("insert_admin_group", parameters, dbConnectionMap, function (err) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " has already been added to Admin Group", err)); + } else { + cb(err); + } + }); + }, function (cb) { + api.dataAccess.executeQuery("clear_user_rating", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("get_admin_resource", { + userId: userId + }, dbConnectionMap, cb); + }, function (resourceIds, cb) { + if (!resourceIds || !resourceIds.length) { + api.dataAccess.executeQuery("insert_new_admin_resource", parameters, dbConnectionMap, function (err) { + if (err) { + return cb(err); + } + api.dataAccess.executeQuery("insert_new_admin_resource_info", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }); + } else { + cb(null); + } + }, function (cb) { + api.dataAccess.executeQuery("insert_admin_role", parameters, dbConnectionMap, function (err) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " has already been assigned Admin role", err)); + } else { + cb(err); + } + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + result.message = username + " has been successfully added as TopCoder Admin"; + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating admin + */ +exports.createAdmin = { + name: "createAdmin", + description: "create TopCoder admin", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createAdmin#run", 'debug'); + createAdmin(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove admin. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeAdmin = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, operatorId, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (userId, cb) { + parameters = { + userId: userId, + operatorId: operatorId + }; + api.dataAccess.executeQuery("remove_admin_group", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_resource_info", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_resource", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }, function (cb) { + api.dataAccess.executeQuery("remove_admin_role", parameters, dbConnectionMap, function (err) { + cb(err); + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + result.message = "TopCoder Admin: " + username + " has been successfully removed"; + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing admin + */ +exports.removeAdmin = { + name: "removeAdmin", + description: "remove TopCoder admin", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeAdmin#run", 'debug'); + removeAdmin(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; diff --git a/actions/copilots.js b/actions/copilots.js new file mode 100755 index 000000000..a0fa9a4cf --- /dev/null +++ b/actions/copilots.js @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; + +var async = require('async'); +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); +var NotFoundError = require('../errors/NotFoundError'); + +/** + * This is the function that will actually get all copilots. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getCopilots = function (api, connection, dbConnectionMap, next) { + var helper = api.helper; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + api.dataAccess.executeQuery("get_copilots", {}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = [], i, entity; + for (i = 0; i < result.length; i = i + 1) { + entity = {}; + entity.id = result[i].user_id; + entity.name = result[i].handle; + entity.softwareCopilot = result[i].is_software_copilot; + entity.studioCopilot = result[i].is_studio_copilot; + ret.push(entity); + } + cb(null, { + allCopilots: ret + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all copilots + */ +exports.copilots = { + name: "copilots", + description: "retrieve all copilots", + inputs: { + required: [], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute copilots#run", 'debug'); + getCopilots(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create copilot. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createCopilot = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + isSoftwareCopilot = connection.params.isSoftwareCopilot, + isStudioCopilot = connection.params.isStudioCopilot, + userId, + operatorId, + parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkBoolean(isSoftwareCopilot, 'isSoftwareCopilot') + || helper.checkBoolean(isStudioCopilot, 'isStudioCopilot')); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + if (!isStudioCopilot && !isSoftwareCopilot) { + return cb(new IllegalArgumentError("Studio Copilot and Software Copilot Checkbox should have at least one checked")); + } + helper.getCopilotProfileIdByUserId(userId, dbConnectionMap, cb); + }, function (copilotProfileId, cb) { + if (copilotProfileId > 0) { + return cb(new DuplicateResourceError("The user " + username + " is already added as copilot")); + } + parameters = { + userId: userId, + isSoftwareCopilot: isSoftwareCopilot ? 't' : 'f', + isStudioCopilot: isStudioCopilot ? 't' : 'f', + operatorId: operatorId + }; + api.dataAccess.executeQuery("insert_new_copilot", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows === 1) { + result.message = "Copilot " + username + " has been successfully added"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating Copilot + */ +exports.createCopilot = { + name: "createCopilot", + description: "create copilot", + inputs: { + required: ['username', 'isSoftwareCopilot', 'isStudioCopilot'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createCopilot#run", 'debug'); + createCopilot(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove copilot. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeCopilot = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, username = connection.params.username, parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.')); + }, function (cb) { + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (userId, cb) { + parameters = { + userId: userId + }; + helper.getCopilotProfileIdByUserId(userId, dbConnectionMap, cb); + }, function (copilotProfileId, cb) { + if (copilotProfileId <= 0) { + return cb(new NotFoundError(username + " is not in the copilot pool")); + } + api.dataAccess.executeQuery("remove_copilot", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows === 1) { + result.message = "Copilot " + username + " has been successfully removed"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing copilot + */ +exports.removeCopilot = { + name: "removeCopilot", + description: "remove copilot", + inputs: { + required: ['username'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeCopilot#run", 'debug'); + removeCopilot(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; diff --git a/actions/reviewers.js b/actions/reviewers.js old mode 100644 new mode 100755 index a68421aee..510ab4ea0 --- a/actions/reviewers.js +++ b/actions/reviewers.js @@ -1,10 +1,35 @@ +/*jslint nomen: true */ /* - * Copyright (C) 2013 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013-2016 TopCoder Inc., All Rights Reserved. * - * @version 1.0 - * @author Sky_ + * @version 1.1 + * @author Sky_,TCSCODER + * Changes in 1.1: + * - add routes for Reviewer Management API + * - Add Reviewer + * - Remove Reviewer + * - Get All Reviewers */ "use strict"; +var _ = require('underscore'); +var async = require('async'); +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var NotFoundError = require('../errors/NotFoundError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); + +/** + * The project type id for studio (design). + */ +var STUDIO_PROJECT_TYPE_ID = 3; +/** + * The category id of CODE. + */ +var CODE_CATEGORY_ID = 39; + +/** + * The category id of First2Finish. + */ +var F2F_CATEGORY_ID = 38; /** * Sample result from specification for Challenge Reviewers Collection @@ -12,8 +37,8 @@ var sampleReviewers; /** -* The API for getting challenge reviewers collection -*/ + * The API for getting challenge reviewers collection + */ exports.action = { name: "getChallengeReviewers", description: "getChallengeReviewers", @@ -60,4 +85,245 @@ sampleReviewers = { "photo": "4.gif" } ] +}; + +/** + * This is the function that will actually get all reviewers. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var getReviewers = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, categoryId = Number(connection.params.categoryId); + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId')); + }, function (cb) { + api.dataAccess.executeQuery("get_reviewers", {categoryId: categoryId}, dbConnectionMap, cb); + }, function (result, cb) { + var ret = [], i, entity; + for (i = 0; i < result.length; i = i + 1) { + entity = {}; + entity.id = result[i].user_id; + entity.name = result[i].handle; + entity.projectCategoryId = result[i].project_category_id; + entity.projectCategoryName = result[i].project_category_name; + entity.immune = Number(result[i].immune_ind) === 1; + ret.push(entity); + } + cb(null, { + categoryId: categoryId, + reviewers: ret + }); + }], function (err, result) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); +}; + +/** + * The API for getting all reviewers of review board of a specific challenge category + */ +exports.reviewers = { + name: "reviewers", + description: "retrieve the reviewers of review board of a specific challenge category", + inputs: { + required: ['categoryId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'read', // this action is read-only + databases: ['tcs_catalog'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute reviewers#run", 'debug'); + getReviewers(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually create reviewer. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var createReviewer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + categoryId = Number(connection.params.categoryId), + immune = connection.params.immune, + userId, + operatorId, + parameters, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId') || + (_.isUndefined(immune) ? null : helper.checkBoolean(immune, 'immune'))); + }, function (cb) { + operatorId = connection.caller.userId; + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + helper.getProjectCategoryByCategoryId(categoryId, dbConnectionMap, cb); + }, function (projectCategory, cb) { + if (!projectCategory) { + return cb(new IllegalArgumentError("Category Id " + categoryId + " is not a valid category ID")); + } + var isImmunity = projectCategory.typeId === STUDIO_PROJECT_TYPE_ID || categoryId === CODE_CATEGORY_ID || categoryId === F2F_CATEGORY_ID; + // will use immune from user input if exist + if (!_.isUndefined(immune)) { + isImmunity = immune; + } + // will use 1 or 0 finally + isImmunity = isImmunity ? 1 : 0; + parameters = { + userId: userId, + operatorId: operatorId, + categoryId: categoryId, + isImmunity: isImmunity + }; + api.dataAccess.executeQuery("insert_reviewer", parameters, dbConnectionMap, function (err, effectedRows) { + if (helper.isDuplicateResourceError(err)) { + cb(new DuplicateResourceError("User " + username + " is in the specific review board", err)); + } else { + if (!err && effectedRows === 1) { + result.message = username + " has been successfully added into " + projectCategory.name + " Review Board"; + } + cb(err); + } + }); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for creating reviewer + */ +exports.createReviewer = { + name: "createReviewer", + description: "add reviewer", + inputs: { + required: ['username', 'categoryId'], + optional: ['immune'] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute createReviewer#run", 'debug'); + createReviewer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } +}; + +/** + * This is the function that will actually remove reviewer. + * + * @param {Object} api The api object that is used to access the global infrastructure + * @param {Object} connection The connection object for the current request + * @param {Object} dbConnectionMap The database connection map for the current request + * @param {Function} next The callback to be called after this function is done + */ +var removeReviewer = function (api, connection, dbConnectionMap, next) { + var helper = api.helper, + username = connection.params.username, + categoryId = Number(connection.params.categoryId), + parameters, + userId, + projectCategory, + result = { + success: true + }; + async.waterfall([function (cb) { + cb(helper.checkAdmin(connection, "You need to login for this api.", 'You don\'t have access to this api.') + || helper.checkIdParameter(categoryId, 'categoryId')); + }, function (cb) { + helper.validateUserAndGetUserId(username, dbConnectionMap, cb); + }, function (id, cb) { + userId = id; + helper.getProjectCategoryByCategoryId(categoryId, dbConnectionMap, cb); + }, function (projectCategoryResult, cb) { + projectCategory = projectCategoryResult; + if (!projectCategory) { + return cb(new IllegalArgumentError("Category Id " + categoryId + " is not a valid category ID")); + } + parameters = { + userId: userId, + categoryId: categoryId + }; + api.dataAccess.executeQuery("get_reviewer", parameters, dbConnectionMap, cb); + }, function (userIds, cb) { + if (!userIds || !userIds.length) { + return cb(new NotFoundError("There is no reviewer with the username:" + username + " in category: " + projectCategory.name)); + } + api.dataAccess.executeQuery("remove_reviewer", parameters, dbConnectionMap, cb); + }, function (effectedRows, cb) { + if (effectedRows >= 1) { + result.message = username + " has been successfully removed from " + projectCategory.name + " Review Board"; + } + cb(null); + }], function (err) { + if (err) { + helper.handleError(api, connection, err); + } else { + connection.response = result; + } + next(connection, true); + }); + +}; + +/** + * The API for removing reviewer + */ +exports.removeReviewer = { + name: "removeReviewer", + description: "remove reviewer", + inputs: { + required: ['username', 'categoryId'], + optional: [] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + cacheEnabled: false, + transaction: 'write', + databases: ['tcs_catalog', 'common_oltp'], + run: function (api, connection, next) { + if (connection.dbConnectionMap) { + api.log("Execute removeReviewer#run", 'debug'); + removeReviewer(api, connection, connection.dbConnectionMap, next); + } else { + api.helper.handleNoConnection(api, connection, next); + } + } }; \ No newline at end of file diff --git a/apiary-admin.apib b/apiary-admin.apib new file mode 100755 index 000000000..0bdb7b4f3 --- /dev/null +++ b/apiary-admin.apib @@ -0,0 +1,611 @@ +FORMAT: 1A +HOST: http://api.topcoder.com/v2 + +# Reviewer Management API + +We extend the tc-api to provide new endpoints to handle admin / copilot / reviewer related +tasks. + +## Admins Collection [/admin/admins] + +### List All admins [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "allAdmins": [ + { + "id": 132456, + "name": "heffan", + "adminGroup": true, + "adminRole": true, + "managerResource": true + }] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Admin [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully added as TopCoder Admin" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Admin [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "TopCoder Admin: dok_tester has been successfully removed" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +## Copilots Collection [/admin/copilots] + +### List All copilots [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "allCopilots": [ + { + "id": 20, + "name": "dok_tester", + "softwareCopilot": true, + "studioCopilot": true + } + ] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Copilot [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + isSoftwareCopilot: `true` (required, boolean) ... The isSoftwareCopilot flag + + isStudioCopilot: `true` (required, boolean) ... The isStudioCopilot flag + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "Copilot dok_tester has been successfully added" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Copilot [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "Copilot dok_tester has been successfully removed" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +## Reviewers Collection [/admin/reviewers?categoryId={categoryId}] + +### List All reviewers [GET] ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Parameters + + categoryId (required, number, `7`) ... Project Category Id + ++ Response 200 (application/json) + + { + "categoryId": 7, + "reviewers": [ + { + "id": 20, + "name": "dok_tester", + "projectCategoryId": 7, + "projectCategoryName": "Architecture", + "immune": false + } + ] + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Create New Reviewer [POST] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + categoryId: `14` (required, number) ... The project category id, you can use category id in query but recommend to use category in body. + + immune: `true` (optional, boolean) ... The immune_ind flag + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully added into Assembly Competition Review Board" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 409 (application/json) + + { + "name":"Duplicate Resource", + "value":"409", + "description":"The request is understood, but has duplicate resource." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } + +### Remove Reviewer [DELETE] ++ Attributes + + username: `dok_tester` (required, string) ... The username + + categoryId: `14` (required, number) ... The project category id, you can use category id in query but recommend to use category in body. + ++ Request (application/json) + + + Headers + + Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZHwxMzI0NTYiLCJleHAiOjEzOTI4MTc4ODQsImF1ZCI6InRvcGNvZGVyIiwiaWF0IjoxMzkyNzU3ODg0fQ.7X2IKkiyyI1ExSM5GNpdhJ8fGGK5-oAjzccX6YL_BKY + + + ++ Response 200 (application/json) + + { + "success": true, + "message": "dok_tester has been successfully removed from Assembly Competition Review Board" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The request was invalid. An accompanying message will explain why." + } + ++ Response 401 (application/json) + + { + "name":"Unauthorized", + "value":"401", + "description":"Authentication credentials were missing or incorrect." + } + ++ Response 403 (application/json) + + { + "name":"Forbidden", + "value":"403", + "description":"The request is understood, but it has been refused or access is not allowed." + } + ++ Response 404 (application/json) + + { + "name":"Not Found", + "value":"404", + "description":"The URI requested is invalid or the requested resource does not exist." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } diff --git a/docs/Admin App - TC API Reviewer Management API.md b/docs/Admin App - TC API Reviewer Management API.md new file mode 100755 index 000000000..58b40e995 --- /dev/null +++ b/docs/Admin App - TC API Reviewer Management API.md @@ -0,0 +1,121 @@ +# Admin App - TC API Reviewer Management API +In this challenge, we need to enhance / extend the tc-api to provide new endpoints to handle admin / copilot / reviewer related tasks. + +## Setup +- [nodejs 0.10.x](https://nodejs.org/) +- [Docker](https://docs.docker.com/engine/installation/) +- [docker-compose](https://docs.docker.com/compose/install/) +- java(required by https://github.com/appirio-tech/informix-wrapper) +It will actually use [node-java](https://github.com/joeferner/node-java), if you meet any issues please check there. + +informix docker service +``` +cd test/docker +docker-compose up +``` + +Follow exist wiki to setup application [wiki](https://github.com/appirio-tech/tc-api/wiki), please do not run `npm test` since old tests are broken. +TC_VM_IP could be `127.0.0.1` under linux or `192.168.99.100` under mac or windows using docker tool box. +``` +# follow wiki to start applications(all previous steps are required) +npm start +``` + +mock bridge service +``` +# you must prepare same environment variables as the tc-api https://github.com/appirio-tech/tc-api/wiki/Configure-Environment-Variables +# I assume you have install all dependencies +node test/scripts/bridge +``` +Or you can user real java version using **java8**. +Download tc-api-jdbc-bridge-dev.zip in https://apps.topcoder.com/forums//?module=Thread&threadID=891500&start=0 +unzip and run `mvn clean package` +update `src/main/resources/bridge.yml` as expected +authDomain: topcoder-dev.com +dbStore/dwStore should change to match configurations in `tc-api/deploy/development.bat` or `development.sh` +``` +TC_DB_NAME=informixoltp_tcp +TC_DB_HOST=$VM_IP +TC_DB_PORT=2021 +TC_DB_USER=informix +TC_DB_PASSWORD=1nf0rm1x + +TC_DW_NAME=informixoltp_tcp +TC_DW_HOST=$VM_IP +TC_DW_PORT=2021 +TC_DW_USER=informix +TC_DW_PASSWORD=1nf0rm1x +``` + +## lint +``` +# you may need to add sudo under linux or mac +npm install jslint -g +jslint routes.js +jslint actions/admins.js +jslint actions/copilots.js +jslint actions/reviewers.js +jslint errors/DuplicateResourceError.js + +# exist lint errors in old codes will not fix +jslint initializers/helper.js + +jslint test/scripts/bridge.js + +jslint test/test.admins.js +jslint test/test.createAdmin.js +jslint test/test.removeAdmin.js + +jslint test/test.copilots.js +jslint test/test.createCopilot.js +jslint test/test.removeCopilot.js + +jslint test/test.reviewers.js +jslint test/test.createReviewer.js +jslint test/test.removeReviewer.js +``` + +## Verify by postman +Import postman collection `test/postman/Reviewer_Management_API.json` and environment `test/postman/Reviewer_Management_API_environment.json`. +Make sure tc api is listening `8080` of localhost rightly or url in environment is right for `http://localhost:8080/api/v2`(mocha test will use this url too). +Make sure informix, bridge is also running. +You can verify requests in different folder. +If token is expired please run requests in `login` folder and Log in as admin or ordinary user and update `adminToken`, `userToken` in environment. + +## Verify by mocha +It will run similar requests as postman including failure and success cases. + +Please make sure informix, tc-api, bridge service is running, it is slow to run single test +and easy to occur max connection numbers issues so it is better to test files one by one and restart all applications if meets any error. +``` +# you must prepare same environment variables as the tc-api https://github.com/appirio-tech/tc-api/wiki/Configure-Environment-Variables +# you may need to add sudo under linux or mac +npm install mocha -g +mocha test/test.admins.js +mocha test/test.createAdmin.js +mocha test/test.removeAdmin.js + +mocha test/test.copilots.js +mocha test/test.createCopilot.js +mocha test/test.removeCopilot.js + +mocha test/test.reviewers.js +mocha test/test.createReviewer.js +mocha test/test.removeReviewer.js +``` + +## api doc +Register account in https://apiary.io and create new api and copy document `tc-api/apiary-admin.apib` and validate document, +Save and you can click Documentation tab on top to view api doc. + + +## Max connection number issue +The docker image of informix is limited to have 20 connections.It is very easy to occur **Open Timeout** +or **Timed out without obtaining all DB connections** or **Error: The server experienced an internal error** during test,postman requests. +You should close all services/applications include bridge service and stop and restart informix again with previous steps. +You may change `MAXPOOL` environment variable since it will be shared in tc api, bridge,test applications so use `20` is not proper actually. + +## Pass data to api +when add/remove reviewers you could use categoryId/username in query or body, but I recommend you to send data in body +since category id in query will be ignored if exists categoryId in body +and it could send number directly in body(provide postman requests for category id in query too). \ No newline at end of file diff --git a/errors/DuplicateResourceError.js b/errors/DuplicateResourceError.js new file mode 100755 index 000000000..909368cde --- /dev/null +++ b/errors/DuplicateResourceError.js @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ +"use strict"; + +/** + * This file defines DuplicateResourceError + * + * @author TCSCODER + * @version 1.0 + */ + +/** + * Constructor of DuplicateResourceError + * @param {Object} message the error message + * @param {Object} cause the error cause + */ +var DuplicateResourceError = function (message, cause) { + //captureStackTrace + Error.call(this); + Error.captureStackTrace(this); + this.message = message || "DuplicateResource Error"; + this.cause = cause; +}; + +//use Error as prototype +require('util').inherits(DuplicateResourceError, Error); +DuplicateResourceError.prototype.name = 'DuplicateResource Error'; + +module.exports = DuplicateResourceError; diff --git a/initializers/helper.js b/initializers/helper.js old mode 100644 new mode 100755 index 2e53f1cc6..7170bd95d --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1,12 +1,12 @@ /*jslint node: true, nomen: true, unparam: true, plusplus: true, bitwise: true */ /** - * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013 - 2016 TopCoder Inc., All Rights Reserved. */ /** * This module contains helper functions. * @author Sky_, Ghost_141, muzehyun, kurtrips, isv, LazyChild, hesibo, panoptimum, flytoj2ee, TCSASSEMBLER - * @version 1.42 + * @version 1.43 * changes in 1.1: * - add mapProperties * changes in 1.2: @@ -112,6 +112,12 @@ * - Update apiName2dbNameMap to add entries for srm schedule API. * Changes in 1.42: * - Add checkAdminOrWebArenaSuper to check if user has web arena super role. + * Changes in 1.43: + * - Add validateUserAndGetUserId to validate username and get user id. + * - Add isDuplicateResourceError to check whether error is duplicate resource error + * - Add getCopilotProfileIdByUserId to get copilot profile id by user id + * - Add getProjectCategoryByCategoryId to get project category with type,name by category id + * - Update apiCode, handleError to handle duplicate resource error */ "use strict"; @@ -136,6 +142,7 @@ var BadRequestError = require('../errors/BadRequestError'); var UnauthorizedError = require('../errors/UnauthorizedError'); var ForbiddenError = require('../errors/ForbiddenError'); var RequestTooLargeError = require('../errors/RequestTooLargeError'); +var DuplicateResourceError = require('../errors/DuplicateResourceError'); var helper = {}; var crypto = require("crypto"); var bigdecimal = require('bigdecimal'); @@ -1039,6 +1046,9 @@ helper.consts.ALLOWABLE_DATE_TYPE = [helper.consts.AFTER, helper.consts.AFTER_CU /** * Api codes + * changes in 1.43 + * -add code,value,description for duplicate resource error + * @since 1.43 */ helper.apiCodes = { OK: { @@ -1071,6 +1081,11 @@ helper.apiCodes = { value: 413, description: 'The request is understood, but is larger than the server is willing or able to process.' }, + duplicateResource: { + name: 'Duplicate Resource', + value: 409, + description: 'The request is understood, but has duplicate resource.' + }, notFound: { name: 'Not Found', value: 404, @@ -1089,6 +1104,9 @@ helper.apiCodes = { * @param {Object} api - The api object that is used to access the global infrastructure * @param {Object} connection - The connection object for the current request * @param {Object} err - The error to return + * changes in 1.43 + * -handle duplicate resource error + * @since 1.43 */ helper.handleError = function (api, connection, err) { api.log("Error occurred: " + err + " " + (err.stack || ''), "error"); @@ -1108,6 +1126,9 @@ helper.handleError = function (api, connection, err) { if (err instanceof RequestTooLargeError) { baseError = helper.apiCodes.requestTooLarge; } + if (err instanceof DuplicateResourceError) { + baseError = helper.apiCodes.duplicateResource; + } errdetail = _.clone(baseError); errdetail.details = err.message; connection.rawConnection.responseHttpCode = baseError.value; @@ -1980,6 +2001,92 @@ var getCoderIdFromActivationCode = function (activationCode) { helper.getCoderIdFromActivationCode = getCoderIdFromActivationCode; helper.generateActivationCode = generateActivationCode; +/** + * Validate the given user username and get user id. + * @param {String} username - the user username. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.validateUserAndGetUserId = function (username, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkStringPopulated(username, 'username')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_user_id_by_handle", + { handle: username }, dbConnectionMap, cb); + }, function (result, cb) { + if (!result || !result.length) { + return cb(new NotFoundError("User with the username: " + username + " does not exist")); + } + cb(null, result[0].user_id); + } + ], callback); +}; + +/** + * Check whether given error is duplicate resource error. + * @param e the error object + * @returns true if contains duplicate in error message. + * @since 1.43 + */ +helper.isDuplicateResourceError = function (e) { + return e && e.message && e.message.indexOf('duplicate') !== -1; +}; + +/** + * Get copilot profile id by user id. + * @param {Number} userId - the user id. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.getCopilotProfileIdByUserId = function (userId, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkIdParameter(userId, 'userId')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_copilot_profile_id_by_user_id", + { userId: userId }, dbConnectionMap, cb); + }, function (result, cb) { + cb(null, (!result || !result.length) ? 0 : result[0].copilot_profile_id); + } + ], callback); +}; + + +/** + * Gets project category with project type id,name by project category ID. + * @param {Number} categoryId - the project category id. + * @param {Object} dbConnectionMap - the database connection map + * @param {Function} callback - The callback function. + * @since 1.43 + */ +helper.getProjectCategoryByCategoryId = function (categoryId, dbConnectionMap, callback) { + async.waterfall([ + function (cb) { + cb(helper.checkIdParameter(categoryId, 'categoryId')); + }, + function (cb) { + helper.api.dataAccess.executeQuery("get_project_category_by_category_id", + {categoryId: categoryId}, dbConnectionMap, cb); + }, function (result, cb) { + var projectCategory = null; + if (result && result.length) { + projectCategory = { + id: result[0].project_category_id, + name: result[0].name, + typeId: Number(result[0].project_type_id) + }; + } + cb(null, projectCategory); + } + ], callback); +}; + + /** * Expose the "helper" utility. * diff --git a/queries/clear_user_rating b/queries/clear_user_rating new file mode 100755 index 000000000..6a8d7b175 --- /dev/null +++ b/queries/clear_user_rating @@ -0,0 +1 @@ +UPDATE informixoltp:rating SET rating = -1 WHERE coder_id = @userId@ \ No newline at end of file diff --git a/queries/clear_user_rating.json b/queries/clear_user_rating.json new file mode 100755 index 000000000..3f313986a --- /dev/null +++ b/queries/clear_user_rating.json @@ -0,0 +1,5 @@ +{ + "name": "clear_user_rating", + "db": "tcs_catalog", + "sqlfile": "clear_user_rating" +} \ No newline at end of file diff --git a/queries/get_admin_resource b/queries/get_admin_resource new file mode 100755 index 000000000..23e22d01a --- /dev/null +++ b/queries/get_admin_resource @@ -0,0 +1 @@ +select resource_id from resource where project_id IS NULL and resource_role_id = 13 and user_id = @userId@ \ No newline at end of file diff --git a/queries/get_admin_resource.json b/queries/get_admin_resource.json new file mode 100755 index 000000000..04bbb3a4d --- /dev/null +++ b/queries/get_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name": "get_admin_resource", + "db": "tcs_catalog", + "sqlfile": "get_admin_resource" +} \ No newline at end of file diff --git a/queries/get_admins b/queries/get_admins new file mode 100755 index 000000000..f654a5770 --- /dev/null +++ b/queries/get_admins @@ -0,0 +1,7 @@ +select t.user_id, t.type, u.handle +FROM (select unique user_id, 'Manager Resource' as type from resource where project_id IS NULL and resource_role_id = 13 +UNION +select unique login_id as user_id, 'Admin Group' as type from user_group_xref where group_id = 2000115 +UNION +select unique login_id as user_id, 'Admin Role' as type from user_role_xref where role_id = 2087) as t, user u +WHERE t.user_id = u.user_id diff --git a/queries/get_admins.json b/queries/get_admins.json new file mode 100755 index 000000000..82ac59fb0 --- /dev/null +++ b/queries/get_admins.json @@ -0,0 +1,5 @@ +{ + "name" : "get_admins", + "db" : "tcs_catalog", + "sqlfile" : "get_admins" +} \ No newline at end of file diff --git a/queries/get_copilot_profile_id_by_user_id b/queries/get_copilot_profile_id_by_user_id new file mode 100755 index 000000000..6c98ddce9 --- /dev/null +++ b/queries/get_copilot_profile_id_by_user_id @@ -0,0 +1 @@ +SELECT copilot_profile_id FROM copilot_profile WHERE user_id = @userId@ \ No newline at end of file diff --git a/queries/get_copilot_profile_id_by_user_id.json b/queries/get_copilot_profile_id_by_user_id.json new file mode 100755 index 000000000..0f5e02acc --- /dev/null +++ b/queries/get_copilot_profile_id_by_user_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_copilot_profile_id_by_user_id", + "db" : "tcs_catalog", + "sqlfile" : "get_copilot_profile_id_by_user_id" +} \ No newline at end of file diff --git a/queries/get_copilots b/queries/get_copilots new file mode 100755 index 000000000..b7076889b --- /dev/null +++ b/queries/get_copilots @@ -0,0 +1,3 @@ +select cp.user_id, handle, is_software_copilot, is_studio_copilot +from copilot_profile cp, user u +where cp.user_id = u.user_id and cp.copilot_profile_status_id = 1 \ No newline at end of file diff --git a/queries/get_copilots.json b/queries/get_copilots.json new file mode 100755 index 000000000..a43d82492 --- /dev/null +++ b/queries/get_copilots.json @@ -0,0 +1,5 @@ +{ + "name" : "get_copilots", + "db" : "tcs_catalog", + "sqlfile" : "get_copilots" +} \ No newline at end of file diff --git a/queries/get_next_admin_resource_id b/queries/get_next_admin_resource_id new file mode 100755 index 000000000..7f59c9ebc --- /dev/null +++ b/queries/get_next_admin_resource_id @@ -0,0 +1 @@ +SELECT (NVL(max(resource_id),0) + 1) as next_id FROM resource WHERE resource_id <5000 \ No newline at end of file diff --git a/queries/get_next_admin_resource_id.json b/queries/get_next_admin_resource_id.json new file mode 100755 index 000000000..a19d8082b --- /dev/null +++ b/queries/get_next_admin_resource_id.json @@ -0,0 +1,5 @@ +{ + "name": "get_next_admin_resource_id", + "db": "tcs_catalog", + "sqlfile": "get_next_admin_resource_id" +} \ No newline at end of file diff --git a/queries/get_next_admin_user_group_id b/queries/get_next_admin_user_group_id new file mode 100755 index 000000000..0b1be8031 --- /dev/null +++ b/queries/get_next_admin_user_group_id @@ -0,0 +1 @@ +SELECT (NVL(MIN(user_group_id), 0) - 1) as next_id FROM user_group_xref \ No newline at end of file diff --git a/queries/get_next_admin_user_group_id.json b/queries/get_next_admin_user_group_id.json new file mode 100755 index 000000000..58b6e863c --- /dev/null +++ b/queries/get_next_admin_user_group_id.json @@ -0,0 +1,5 @@ +{ + "name": "get_next_admin_user_group_id", + "db": "tcs_catalog", + "sqlfile": "get_next_admin_user_group_id" +} \ No newline at end of file diff --git a/queries/get_project_category_by_category_id b/queries/get_project_category_by_category_id new file mode 100755 index 000000000..2a5585ef5 --- /dev/null +++ b/queries/get_project_category_by_category_id @@ -0,0 +1 @@ +select project_category_id,project_type_id,name from project_category_lu where display = 't' and project_category_id =@categoryId@ diff --git a/queries/get_project_category_by_category_id.json b/queries/get_project_category_by_category_id.json new file mode 100755 index 000000000..a65bcec28 --- /dev/null +++ b/queries/get_project_category_by_category_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_project_category_by_category_id", + "db" : "tcs_catalog", + "sqlfile" : "get_project_category_by_category_id" +} diff --git a/queries/get_reviewer b/queries/get_reviewer new file mode 100755 index 000000000..d8b55f3b9 --- /dev/null +++ b/queries/get_reviewer @@ -0,0 +1 @@ +SELECT user_id FROM rboard_user WHERE user_id = @userId@ AND project_type_id = @categoryId@ \ No newline at end of file diff --git a/queries/get_reviewer.json b/queries/get_reviewer.json new file mode 100755 index 000000000..dc4d36389 --- /dev/null +++ b/queries/get_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "get_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "get_reviewer" +} diff --git a/queries/get_reviewers b/queries/get_reviewers new file mode 100755 index 000000000..d44830348 --- /dev/null +++ b/queries/get_reviewers @@ -0,0 +1,4 @@ +select u.user_id, pcl.project_category_id, u.handle, pcl.name as project_category_name, immune_ind +from rboard_user ru, user u, project_category_lu pcl +where u.user_id = ru.user_id AND ru.project_type_id = pcl.project_category_id AND pcl.project_category_id = @categoryId@ +group by 1,2,3,4,5 \ No newline at end of file diff --git a/queries/get_reviewers.json b/queries/get_reviewers.json new file mode 100755 index 000000000..8b34c5a16 --- /dev/null +++ b/queries/get_reviewers.json @@ -0,0 +1,5 @@ +{ + "name" : "get_reviewers", + "db" : "tcs_catalog", + "sqlfile" : "get_reviewers" +} diff --git a/queries/get_user_id_by_handle b/queries/get_user_id_by_handle new file mode 100755 index 000000000..bf5a319c2 --- /dev/null +++ b/queries/get_user_id_by_handle @@ -0,0 +1 @@ +select user_id from user where handle = '@handle@' \ No newline at end of file diff --git a/queries/get_user_id_by_handle.json b/queries/get_user_id_by_handle.json new file mode 100755 index 000000000..f16209326 --- /dev/null +++ b/queries/get_user_id_by_handle.json @@ -0,0 +1,5 @@ +{ + "name": "get_user_id_by_handle", + "db": "common_oltp", + "sqlfile": "get_user_id_by_handle" +} \ No newline at end of file diff --git a/queries/insert_admin_group b/queries/insert_admin_group new file mode 100755 index 000000000..94f91b568 --- /dev/null +++ b/queries/insert_admin_group @@ -0,0 +1 @@ +INSERT INTO user_group_xref VALUES(@userGroupId@, @userId@, 2000115, @operatorId@, 1, current) \ No newline at end of file diff --git a/queries/insert_admin_group.json b/queries/insert_admin_group.json new file mode 100755 index 000000000..fff9f859d --- /dev/null +++ b/queries/insert_admin_group.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_admin_group", + "db" : "tcs_catalog", + "sqlfile" : "insert_admin_group" +} \ No newline at end of file diff --git a/queries/insert_admin_role b/queries/insert_admin_role new file mode 100755 index 000000000..52b9a92d0 --- /dev/null +++ b/queries/insert_admin_role @@ -0,0 +1 @@ +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), @userId@, 2087, @operatorId@, 1) \ No newline at end of file diff --git a/queries/insert_admin_role.json b/queries/insert_admin_role.json new file mode 100755 index 000000000..24aa65f84 --- /dev/null +++ b/queries/insert_admin_role.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_admin_role", + "db" : "tcs_catalog", + "sqlfile" : "insert_admin_role" +} \ No newline at end of file diff --git a/queries/insert_new_admin_resource b/queries/insert_new_admin_resource new file mode 100755 index 000000000..2d76f63a1 --- /dev/null +++ b/queries/insert_new_admin_resource @@ -0,0 +1,2 @@ +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(@resourceId@, 13, @userId@, @operatorId@, current, @operatorId@, current) \ No newline at end of file diff --git a/queries/insert_new_admin_resource.json b/queries/insert_new_admin_resource.json new file mode 100755 index 000000000..bd2f0b879 --- /dev/null +++ b/queries/insert_new_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_admin_resource", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_admin_resource" +} diff --git a/queries/insert_new_admin_resource_info b/queries/insert_new_admin_resource_info new file mode 100755 index 000000000..c9c6a1f6f --- /dev/null +++ b/queries/insert_new_admin_resource_info @@ -0,0 +1,2 @@ +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(@resourceId@, 1 , @userId@, @operatorId@, current, @operatorId@, current) \ No newline at end of file diff --git a/queries/insert_new_admin_resource_info.json b/queries/insert_new_admin_resource_info.json new file mode 100755 index 000000000..ae950aa10 --- /dev/null +++ b/queries/insert_new_admin_resource_info.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_admin_resource_info", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_admin_resource_info" +} diff --git a/queries/insert_new_copilot b/queries/insert_new_copilot new file mode 100755 index 000000000..120f1919a --- /dev/null +++ b/queries/insert_new_copilot @@ -0,0 +1,6 @@ +INSERT INTO copilot_profile (copilot_profile_id,user_id,copilot_profile_status_id,suspension_count, +reliability,activation_date,show_copilot_earnings,create_user, create_date, +update_user, update_date, is_software_copilot, is_studio_copilot) +VALUES ((select NVL(max(copilot_profile_id), 0) + 1 from copilot_profile), +@userId@,1,0,100.00, current,'t', @operatorId@, +current, @operatorId@, current, '@isSoftwareCopilot@', '@isStudioCopilot@') \ No newline at end of file diff --git a/queries/insert_new_copilot.json b/queries/insert_new_copilot.json new file mode 100755 index 000000000..c66d0a50b --- /dev/null +++ b/queries/insert_new_copilot.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_new_copilot", + "db" : "tcs_catalog", + "sqlfile" : "insert_new_copilot" +} \ No newline at end of file diff --git a/queries/insert_reviewer b/queries/insert_reviewer new file mode 100755 index 000000000..289153270 --- /dev/null +++ b/queries/insert_reviewer @@ -0,0 +1 @@ +INSERT INTO rboard_user VALUES (@userId@, @categoryId@, 4, 100, @isImmunity@) \ No newline at end of file diff --git a/queries/insert_reviewer.json b/queries/insert_reviewer.json new file mode 100755 index 000000000..bd4300485 --- /dev/null +++ b/queries/insert_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "insert_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "insert_reviewer" +} diff --git a/queries/remove_admin_group b/queries/remove_admin_group new file mode 100755 index 000000000..bcee9ce54 --- /dev/null +++ b/queries/remove_admin_group @@ -0,0 +1 @@ +DELETE FROM user_group_xref WHERE login_id = @userId@ AND group_id = 2000115 \ No newline at end of file diff --git a/queries/remove_admin_group.json b/queries/remove_admin_group.json new file mode 100755 index 000000000..10ff55e15 --- /dev/null +++ b/queries/remove_admin_group.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_group", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_group" +} \ No newline at end of file diff --git a/queries/remove_admin_resource b/queries/remove_admin_resource new file mode 100755 index 000000000..90ba7760f --- /dev/null +++ b/queries/remove_admin_resource @@ -0,0 +1 @@ +DELETE FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id = @userId@ \ No newline at end of file diff --git a/queries/remove_admin_resource.json b/queries/remove_admin_resource.json new file mode 100755 index 000000000..b309c6aa4 --- /dev/null +++ b/queries/remove_admin_resource.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_resource", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_resource" +} \ No newline at end of file diff --git a/queries/remove_admin_resource_info b/queries/remove_admin_resource_info new file mode 100755 index 000000000..9bbcd7d59 --- /dev/null +++ b/queries/remove_admin_resource_info @@ -0,0 +1 @@ +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id = @userId@) \ No newline at end of file diff --git a/queries/remove_admin_resource_info.json b/queries/remove_admin_resource_info.json new file mode 100755 index 000000000..a8d90d387 --- /dev/null +++ b/queries/remove_admin_resource_info.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_resource_info", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_resource_info" +} \ No newline at end of file diff --git a/queries/remove_admin_role b/queries/remove_admin_role new file mode 100755 index 000000000..8b8e70b3b --- /dev/null +++ b/queries/remove_admin_role @@ -0,0 +1 @@ +DELETE FROM user_role_xref WHERE role_id=2087 AND login_id = @userId@ \ No newline at end of file diff --git a/queries/remove_admin_role.json b/queries/remove_admin_role.json new file mode 100755 index 000000000..13be813dd --- /dev/null +++ b/queries/remove_admin_role.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_admin_role", + "db" : "tcs_catalog", + "sqlfile" : "remove_admin_role" +} \ No newline at end of file diff --git a/queries/remove_copilot b/queries/remove_copilot new file mode 100755 index 000000000..296479222 --- /dev/null +++ b/queries/remove_copilot @@ -0,0 +1 @@ +DELETE FROM copilot_profile WHERE user_id = @userId@ \ No newline at end of file diff --git a/queries/remove_copilot.json b/queries/remove_copilot.json new file mode 100755 index 000000000..685beb670 --- /dev/null +++ b/queries/remove_copilot.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_copilot", + "db" : "tcs_catalog", + "sqlfile" : "remove_copilot" +} \ No newline at end of file diff --git a/queries/remove_reviewer b/queries/remove_reviewer new file mode 100755 index 000000000..27d8b3b9e --- /dev/null +++ b/queries/remove_reviewer @@ -0,0 +1 @@ +DELETE FROM rboard_user WHERE user_id = @userId@ AND project_type_id = @categoryId@ \ No newline at end of file diff --git a/queries/remove_reviewer.json b/queries/remove_reviewer.json new file mode 100755 index 000000000..db152c918 --- /dev/null +++ b/queries/remove_reviewer.json @@ -0,0 +1,5 @@ +{ + "name" : "remove_reviewer", + "db" : "tcs_catalog", + "sqlfile" : "remove_reviewer" +} \ No newline at end of file diff --git a/routes.js b/routes.js index e9d94651f..57af4d99d 100755 --- a/routes.js +++ b/routes.js @@ -1,9 +1,9 @@ /* - * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. + * Copyright (C) 2013 - 2016 TopCoder Inc., All Rights Reserved. * - * @version 1.68 + * @version 1.69 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee, - * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER + * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER,TCSCODER * * Changes in 1.1: * - add routes for search challenges @@ -156,6 +156,18 @@ * - Added get user develop challenges api. * Changed in 1.68: * - Added get rounds api. + * Changed in 1.69: + * - Added routes for reviewer management api: + * - Add Reviewer + * - Remove Reviewer + * - Get All Reviewers + * - Add Copilot + * - Remove Copilot + * - Get All Copilots + * - Create Admin + * - Remove Admin + * - Get All Admins + */ /*jslint node:true, nomen: true */ "use strict"; @@ -361,6 +373,11 @@ exports.routes = { { path: "/:apiVersion/auth0/callback", action: "auth0Callback" }, { path: "/:apiVersion/data/rounds", action: "getRounds" }, + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "admins" }, + { path: "/:apiVersion/admin/copilots", action: "copilots" }, + { path: "/:apiVersion/admin/reviewers", action: "reviewers" }, + //Stubs APIs { path: "/:apiVersion/data/reviewOpportunities/:id", action: "getAlgorithmsReviewOpportunity" }, { path: "/:apiVersion/data/reviewOpportunities", action: "getAlgorithmsReviewOpportunities" }, @@ -407,7 +424,12 @@ exports.routes = { { path: "/:apiVersion/data/srm/rounds/:roundId/terms", action: "setRoundTerms"}, { path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" }, { path: "/:apiVersion/src2image", action: "convertSourceCodeToImage" }, - { path: "/:apiVersion/dump", action: "dumpMemory"} + { path: "/:apiVersion/dump", action: "dumpMemory"}, + + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "createAdmin" }, + { path: "/:apiVersion/admin/copilots", action: "createCopilot" }, + { path: "/:apiVersion/admin/reviewers", action: "createReviewer" } ], put: [ @@ -418,6 +440,11 @@ exports.routes = { delete: [ { path: "/:apiVersion/data/srm/rounds/:questionId/question", action: "deleteRoundQuestion" }, { path: "/:apiVersion/data/srm/rounds/:roundId", action: "deleteSRMContestRound" }, - { path: "/:apiVersion/data/srm/answer/:answerId", action: "deleteRoundQuestionAnswer" } + { path: "/:apiVersion/data/srm/answer/:answerId", action: "deleteRoundQuestionAnswer" }, + + //Admin App - TC API Reviewer Management API + { path: "/:apiVersion/admin/admins", action: "removeAdmin" }, + { path: "/:apiVersion/admin/copilots", action: "removeCopilot" }, + { path: "/:apiVersion/admin/reviewers", action: "removeReviewer" } ] }; diff --git a/test/docker/docker-compose.yml b/test/docker/docker-compose.yml new file mode 100755 index 000000000..9124c1379 --- /dev/null +++ b/test/docker/docker-compose.yml @@ -0,0 +1,6 @@ +version: '2' +services: + tc-informix: + image: appiriodevops/informix:1b3d4ef + ports: + - "2021:2021" diff --git a/test/postman/Reviewer_Management_API.json b/test/postman/Reviewer_Management_API.json new file mode 100755 index 000000000..cd7d0837e --- /dev/null +++ b/test/postman/Reviewer_Management_API.json @@ -0,0 +1,2041 @@ +{ + "id": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "name": "Admin App - TC API Reviewer Management API", + "description": "", + "order": [], + "folders": [ + { + "id": "142d5b1b-b304-bced-c1f3-b59e8187d2aa", + "name": "create admin", + "description": "", + "order": [ + "17cd6d9b-17b2-1f5c-a0d8-e0948c718d26", + "fc7d7ac4-d1e1-a441-6717-ecb50b9ad7f6", + "9bccd2a2-8aac-1931-b78e-8b61a99415a4", + "8b843cb6-1fc6-8c0b-1d6d-f54d9470cf76", + "88ae8323-4232-1675-8796-e06c6997f3f9", + "2297e0e7-4871-b68e-6258-b59a1da3acd4", + "59d2ab3f-1ef2-3925-42c4-754eced98658", + "43dbe6f5-32e5-f0f0-5735-42a3a9f08120" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "8052cbf5-e206-af03-0392-e853f3f06bf4", + "name": "create copilot", + "description": "", + "order": [ + "3a87b2a7-3761-5089-38d9-6c7d3796e984", + "2cb65342-ba7f-b473-6afc-8a729da04563", + "e058d344-3c5d-951a-c86e-ebfeba41bab8", + "42fc7559-c2a2-8b85-67e3-3812ae0d5998", + "518db876-c166-6aa7-d484-b8b778297b4e", + "9fb05ea1-f85e-209e-7619-e40450acd7f3", + "79444594-2c40-1e6e-9f71-f22d57828026", + "26e53a5b-bef9-17f2-fb02-34595bf8a8c4", + "9ff114f0-56ca-f56c-08e4-3bb4dcaac10f", + "6727946e-aa76-0996-e734-800bb07cf5c6", + "49c5d6f4-5b86-3e9f-8c82-ef7430411264", + "858050d9-d3f2-900f-79a8-4a56c7ca586b", + "68d189c2-eb05-ded4-1e97-414ebde03ab8", + "34ac4817-1082-2274-ed98-f17b63e62786", + "477195d0-14ca-6a6e-274f-513e7d50a45c" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "7544e9b2-c615-4376-7145-511bb968c906", + "name": "create reviewer", + "description": "", + "order": [ + "ebcf31ae-035b-49ac-41d4-fa4484eff6a1", + "b84bd2e1-cd0b-3489-3c11-917a1624249e", + "4a6e3f4b-cb9e-978d-281f-81a57545b0ac", + "ac18e672-1e47-37fa-1ae8-85eed1beab2d", + "202e4545-829b-f158-372f-206e632d609f", + "8679c69e-e94d-7188-60fd-bbe506b851d1", + "2f652b9b-7dc4-8140-9ac5-e6995f1a3fba", + "a8e7c6c2-1cfb-e3ad-af5c-9c27bd71924b", + "d0a6e94f-3c59-2398-f0f5-a1d54a6f4e15", + "03ce940d-9b2d-b46b-0f84-48da5529615c", + "24bc8058-d1ef-1f33-eb64-ede9b9663f49", + "84cd43c0-c407-365b-f1f4-07929e2876cc", + "8a43eff3-3f9a-83a2-6734-a683e29252c8", + "790064a6-e429-1f9c-24b5-7112ce1fb8b2", + "0865091c-8f04-f18c-f9e5-e5c182bc8ab3", + "9e458d5b-5aa5-05ec-6240-7d171c75cf5e", + "0e55b155-b8b7-ae58-5505-f1ff9ba0eb36", + "a466f2b7-2f9c-4a14-fc0e-eee7981cdb42", + "16044f47-5b3d-2ba2-1787-8e6f7e78af97" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "2964b89d-893b-aada-2e0e-c4136b920508", + "name": "get all admins", + "description": "", + "order": [ + "b0bfa529-5f58-d5a6-e1bc-2099abfa253f", + "b83cdcb1-5abe-0f73-1c9c-b558c0634baf", + "144d7cd5-cedb-1e15-fec5-27c5e56863df", + "e10c59ae-1669-54bd-03bb-4de19d51fb5e", + "828d7e47-ee45-5ed2-fe37-2114815e84f1" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "1a80812c-dfa2-5dc4-aca7-299502bbc807", + "name": "get all copilots", + "description": "", + "order": [ + "67db2b5a-4b85-4e18-2709-5c475849329f", + "2d18170b-9631-3c97-fc68-51d0ed76766c", + "a47c9a54-d12a-6801-02a1-c57c420237cf", + "21bdc1e1-c5b2-d063-fd54-386ab396b224", + "897d4baa-72d5-1eca-ff61-51db0d14408c" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "81516934-d74e-f97d-262f-21d87d5961d1", + "name": "get all reviewers", + "description": "", + "order": [ + "08364378-8daf-8159-547e-fac22ca27847", + "c28b4a81-8ee7-24ba-fc8b-54b1b3642fab", + "8bd1727c-34f8-ced7-eb92-d50cc6e56772", + "fe0a03f9-4969-651a-5eed-01de9398498e", + "ddf839fa-733a-056a-8a2c-a1e56d0e9072", + "ef7fd1f5-a302-b7b6-a772-a43eb3b82062", + "c0db4362-622a-b556-1991-80df568707b7", + "f784d0d8-8645-7633-91c6-54ec81aa95ff", + "c1d0d1a9-a8b9-8ee7-c6e6-e539a52fbf35" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "fedff379-68f7-3322-d2e9-29471d82cc60", + "name": "login", + "description": "", + "order": [ + "f6c44b3f-570d-e48f-3b7e-8419a9ebe9b6", + "a495567b-a450-037f-ca8e-c9da52116890" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea", + "name": "remove admin", + "description": "", + "order": [ + "d2bf20ce-e0ed-a347-69dc-577e34dbefd0", + "07358622-f7d4-9233-8dc1-204acb7b1ccf", + "3f0e98fb-e97a-e6ed-8dc7-e864b1447e03", + "c8075e84-bb8d-7596-ba92-5990705f93bc", + "b8d3fef6-214c-5032-3002-f6c16f3288e2", + "2f633003-2ac7-99ec-e52a-8b1f2e0534d4", + "600d723c-706d-42ca-9ecc-94a32e280063", + "1e4fffd8-a809-e9dc-0659-0df467954407" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "2afb4105-1932-6ef0-866c-43ecb13c0048", + "name": "remove copilot", + "description": "", + "order": [ + "ccc25e86-5365-aaf6-8ce8-9838898142eb", + "85022108-999a-7200-89cd-e6b11f66bcb8", + "357caf52-98e8-90a9-8d13-1bf8d330915e", + "34b27e13-3b96-0a9e-25e6-8a339c517518", + "49952d3d-864a-25ff-3648-abbb79550dc2", + "ad152575-1e23-f242-96d2-f4b49a616c56", + "538a99a4-4701-1ee0-527c-6d2db316dae6", + "19a2adaa-d44b-5fd7-9ee7-a6d3452e4492" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + }, + { + "id": "f11d599f-4472-bcef-b9e3-7c86ac139e35", + "name": "remove reviewer", + "description": "", + "order": [ + "49557532-d98e-55e3-5cc4-4c702b62e3ed", + "3453a16f-6a34-c92c-91ed-d2107b505e7a", + "c3383672-4018-ec1b-2688-515b73987d36", + "636f8c2c-7cc2-4d90-179e-7808ae8bbba1", + "a887c7eb-8b15-90aa-07ef-6d998297bcee", + "e6b6464b-da41-67ee-c2ca-53b1d5ba417d", + "4b8d4933-247c-d204-23c6-3acd8ec2e66a", + "da88d593-5117-3c1f-f56c-47ac74724c79", + "13c71689-cfe5-512d-f5b6-d971fa88129a", + "6fd784f5-bda9-b183-1d85-c30879a47427", + "37b02c2b-7018-6af5-4e3c-57b2abde2b0e", + "05ed43fe-61be-43b3-36c7-0c4a12f98efd", + "12d79b9a-e008-ea3d-6fa4-21c7e72e4e94", + "6fcaeb1e-8611-9adf-319f-92f61ac2d62d" + ], + "owner": 0, + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + } + ], + "timestamp": 1474156790593, + "owner": 0, + "public": false, + "published": false, + "hasRequests": true, + "requests": [ + { + "id": "03ce940d-9b2d-b46b-0f84-48da5529615c", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188017748, + "name": "create reviewer with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": \"wrong number\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "05ed43fe-61be-43b3-36c7-0c4a12f98efd", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189158880, + "name": "remove reviewer with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 1.1\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "07358622-f7d4-9233-8dc1-204acb7b1ccf", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173280471, + "name": "remove admin with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "08364378-8daf-8159-547e-fac22ca27847", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474268286128, + "name": "get all reviewers with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "0865091c-8f04-f18c-f9e5-e5c182bc8ab3", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474268192451, + "name": "create reviewer with invalid immune", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Yoshi\",\n \"categoryId\": 14,\n \"immune\": \"invalid boolean\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "0e55b155-b8b7-ae58-5505-f1ff9ba0eb36", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282763352, + "name": "create reviewer with code category id", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Hung\",\n \"categoryId\": 39\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "12d79b9a-e008-ea3d-6fa4-21c7e72e4e94", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189177783, + "name": "remove reviewer without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "13c71689-cfe5-512d-f5b6-d971fa88129a", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282495801, + "name": "remove reviewer with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "144d7cd5-cedb-1e15-fec5-27c5e56863df", + "headers": "", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174192935, + "name": "get all admins without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "16044f47-5b3d-2ba2-1787-8e6f7e78af97", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474284611393, + "name": "create reviewer with categoryId in query and body at same time", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\":7\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "17cd6d9b-17b2-1f5c-a0d8-e0948c718d26", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168970336, + "name": "create admin with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "19a2adaa-d44b-5fd7-9ee7-a6d3452e4492", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177129491, + "name": "remove copilot with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "1e4fffd8-a809-e9dc-0659-0df467954407", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173533574, + "name": "remove admin with notexist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "202e4545-829b-f158-372f-206e632d609f", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187893955, + "name": "create reviewer without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "21bdc1e1-c5b2-d063-fd54-386ab396b224", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176769090, + "name": "get all copilots without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "2297e0e7-4871-b68e-6258-b59a1da3acd4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474169000065, + "name": "create admin without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "24bc8058-d1ef-1f33-eb64-ede9b9663f49", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188046380, + "name": "create reviewer with nagative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": -1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "26e53a5b-bef9-17f2-fb02-34595bf8a8c4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178883094, + "name": "create copilot without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "2cb65342-ba7f-b473-6afc-8a729da04563", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179347234, + "name": "create copilot with isSoftwareCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": 0,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "2d18170b-9631-3c97-fc68-51d0ed76766c", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176739711, + "name": "get all copilots with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "2f633003-2ac7-99ec-e52a-8b1f2e0534d4", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173290943, + "name": "remove admin without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "2f652b9b-7dc4-8140-9ac5-e6995f1a3fba", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187901930, + "name": "create reviewer without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "3453a16f-6a34-c92c-91ed-d2107b505e7a", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189053598, + "name": "remove reviewer with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "34ac4817-1082-2274-ed98-f17b63e62786", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179249694, + "name": "create copilot with invalid isStudioCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isStudioCopilot\": \"invalid boolean\",\n \"isSoftwareCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "34b27e13-3b96-0a9e-25e6-8a339c517518", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177113168, + "name": "remove copilot without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "357caf52-98e8-90a9-8d13-1bf8d330915e", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177110084, + "name": "remove copilot without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "37b02c2b-7018-6af5-4e3c-57b2abde2b0e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189145913, + "name": "remove reviewer with negative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": -1\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "3a87b2a7-3761-5089-38d9-6c7d3796e984", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474272371352, + "name": "create copilot with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "3f0e98fb-e97a-e6ed-8dc7-e864b1447e03", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173276705, + "name": "remove admin without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "42fc7559-c2a2-8b85-67e3-3812ae0d5998", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177780363, + "name": "create copilot with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "43dbe6f5-32e5-f0f0-5735-42a3a9f08120", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173399023, + "name": "create admin with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "477195d0-14ca-6a6e-274f-513e7d50a45c", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179290066, + "name": "create copilot with isStudioCopilot/isSoftwareCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isStudioCopilot\": false,\n \"isSoftwareCopilot\":false\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "49557532-d98e-55e3-5cc4-4c702b62e3ed", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474266891365, + "name": "remove reviewer with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "49952d3d-864a-25ff-3648-abbb79550dc2", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177116518, + "name": "remove copilot with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "49c5d6f4-5b86-3e9f-8c82-ef7430411264", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179169639, + "name": "create copilot with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "4a6e3f4b-cb9e-978d-281f-81a57545b0ac", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187884890, + "name": "create reviewer with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "4b8d4933-247c-d204-23c6-3acd8ec2e66a", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189091821, + "name": "remove reviewer without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "518db876-c166-6aa7-d484-b8b778297b4e", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178853689, + "name": "create copilot without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "538a99a4-4701-1ee0-527c-6d2db316dae6", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177126290, + "name": "remove copilot with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "59d2ab3f-1ef2-3925-42c4-754eced98658", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474169674465, + "name": "create admin with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "600d723c-706d-42ca-9ecc-94a32e280063", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173326368, + "name": "remove admin without empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "636f8c2c-7cc2-4d90-179e-7808ae8bbba1", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189062608, + "name": "remove reviewer without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "6727946e-aa76-0996-e734-800bb07cf5c6", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189246551, + "name": "create copilot without isStudioCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "67db2b5a-4b85-4e18-2709-5c475849329f", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176736874, + "name": "get all copilots with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "68d189c2-eb05-ded4-1e97-414ebde03ab8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179220315, + "name": "create copilot with invalid isSoftwareCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isSoftwareCopilot\": \"invalid boolean\",\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "6fcaeb1e-8611-9adf-319f-92f61ac2d62d", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474284520276, + "name": "remove reviewer with categoryId in query and body at same time", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\":7\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "6fd784f5-bda9-b183-1d85-c30879a47427", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189135829, + "name": "remove reviewer with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": \"wrong number\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "790064a6-e429-1f9c-24b5-7112ce1fb8b2", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474283473763, + "name": "create reviewer with immune", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"Yoshi\",\n \"categoryId\": 14,\n \"immune\":1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "79444594-2c40-1e6e-9f71-f22d57828026", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178865110, + "name": "create copilot with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "828d7e47-ee45-5ed2-fe37-2114815e84f1", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174234342, + "name": "get all admins with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "84cd43c0-c407-365b-f1f4-07929e2876cc", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188081763, + "name": "create reviewer with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 1.1\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "85022108-999a-7200-89cd-e6b11f66bcb8", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177106806, + "name": "remove copilot with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "858050d9-d3f2-900f-79a8-4a56c7ca586b", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179176872, + "name": "create copilot with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "8679c69e-e94d-7188-60fd-bbe506b851d1", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187897602, + "name": "create reviewer with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "88ae8323-4232-1675-8796-e06c6997f3f9", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168987848, + "name": "create admin with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "897d4baa-72d5-1eca-ff61-51db0d14408c", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176766163, + "name": "get all copilots with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "8a43eff3-3f9a-83a2-6734-a683e29252c8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189200056, + "name": "create reviewer without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "8b843cb6-1fc6-8c0b-1d6d-f54d9470cf76", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168982704, + "name": "create admin without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "8bd1727c-34f8-ced7-eb92-d50cc6e56772", + "headers": "", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282217351, + "name": "get all reviewers without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "9bccd2a2-8aac-1931-b78e-8b61a99415a4", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474168977872, + "name": "create admin without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "9e458d5b-5aa5-05ec-6240-7d171c75cf5e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282760160, + "name": "create reviewer with studio type", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"cartajs\",\n \"categoryId\": 17\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "9fb05ea1-f85e-209e-7619-e40450acd7f3", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474178858906, + "name": "create copilot without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": true,\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "9ff114f0-56ca-f56c-08e4-3bb4dcaac10f", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189226479, + "name": "create copilot without isSoftwareCopilot", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isStudioCopilot\":true\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "a466f2b7-2f9c-4a14-fc0e-eee7981cdb42", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282767080, + "name": "create reviewer with f2f category id", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"liquid_user\",\n \"categoryId\": 38\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "a47c9a54-d12a-6801-02a1-c57c420237cf", + "headers": "", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474176772030, + "name": "get all copilots without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "1a80812c-dfa2-5dc4-aca7-299502bbc807" + }, + { + "id": "a495567b-a450-037f-ca8e-c9da52116890", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159245944, + "name": "Log in as ordinary user", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"user\", \n \"password\": \"password\"\n}", + "folder": "fedff379-68f7-3322-d2e9-29471d82cc60" + }, + { + "id": "a887c7eb-8b15-90aa-07ef-6d998297bcee", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189071606, + "name": "remove reviewer without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "a8e7c6c2-1cfb-e3ad-af5c-9c27bd71924b", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282655617, + "name": "create reviewer with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ac18e672-1e47-37fa-1ae8-85eed1beab2d", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187888883, + "name": "create reviewer without Authorization header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ad152575-1e23-f242-96d2-f4b49a616c56", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177119734, + "name": "remove copilot without username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "b0bfa529-5f58-d5a6-e1bc-2099abfa253f", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173887249, + "name": "get all admins with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "b83cdcb1-5abe-0f73-1c9c-b558c0634baf", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174015997, + "name": "get all admins with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "b84bd2e1-cd0b-3489-3c11-917a1624249e", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282455411, + "name": "create reviewer with categoryId in query", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "b8d3fef6-214c-5032-3002-f6c16f3288e2", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173287352, + "name": "remove admin with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "c0db4362-622a-b556-1991-80df568707b7", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=-4", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474184022183, + "name": "get all reviewers with negative categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c1d0d1a9-a8b9-8ee7-c6e6-e539a52fbf35", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189283486, + "name": "get all reviewers without categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c28b4a81-8ee7-24ba-fc8b-54b1b3642fab", + "headers": "Authorization: Bearer {{userToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282212758, + "name": "get all reviewers with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "c3383672-4018-ec1b-2688-515b73987d36", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers?categoryId=14", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474273020582, + "name": "remove reviewer with categoryId in query", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "c8075e84-bb8d-7596-ba92-5990705f93bc", + "headers": "Authorization: wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173283865, + "name": "remove admin without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "ccc25e86-5365-aaf6-8ce8-9838898142eb", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474177185914, + "name": "remove copilot with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "2afb4105-1932-6ef0-866c-43ecb13c0048" + }, + { + "id": "d0a6e94f-3c59-2398-f0f5-a1d54a6f4e15", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282659872, + "name": "create reviewer with not exist username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"notexist\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "d2bf20ce-e0ed-a347-69dc-577e34dbefd0", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474173258538, + "name": "remove admin with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "78aeaf8b-80c8-40df-6ed6-2af5acdbf2ea" + }, + { + "id": "da88d593-5117-3c1f-f56c-47ac74724c79", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282491538, + "name": "remove reviewer with empty username", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \" \",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "ddf839fa-733a-056a-8a2c-a1e56d0e9072", + "headers": "Authorization: Bearer wrong\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282226901, + "name": "get all reviewers with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "e058d344-3c5d-951a-c86e-ebfeba41bab8", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/copilots", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474179507223, + "name": "create copilot with isStudioCopilot false", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"isSoftwareCopilot\": 1,\n \"isStudioCopilot\":0\n}", + "folder": "8052cbf5-e206-af03-0392-e853f3f06bf4" + }, + { + "id": "e10c59ae-1669-54bd-03bb-4de19d51fb5e", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474174209799, + "name": "get all admins without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "2964b89d-893b-aada-2e0e-c4136b920508" + }, + { + "id": "e6b6464b-da41-67ee-c2ca-53b1d5ba417d", + "headers": "Authorization: Bearer wrong\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474189083334, + "name": "remove reviewer with wrong Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "f11d599f-4472-bcef-b9e3-7c86ac139e35" + }, + { + "id": "ebcf31ae-035b-49ac-41d4-fa4484eff6a1", + "headers": "Authorization: Bearer {{adminToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/reviewers", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474187872946, + "name": "create reviewer with admin token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\",\n \"categoryId\": 14\n}", + "folder": "7544e9b2-c615-4376-7145-511bb968c906" + }, + { + "id": "ef7fd1f5-a302-b7b6-a772-a43eb3b82062", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=wrong", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474184004438, + "name": "get all reviewers with invalid categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "f6c44b3f-570d-e48f-3b7e-8419a9ebe9b6", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159263289, + "name": "Login as admin user", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", + "folder": "fedff379-68f7-3322-d2e9-29471d82cc60" + }, + { + "id": "f784d0d8-8645-7633-91c6-54ec81aa95ff", + "headers": "Authorization: Bearer {{adminToken}}\n", + "url": "{{url}}/admin/reviewers?categoryId=1.1", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474188155542, + "name": "get all reviewers with non-integer categoryId", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + }, + { + "id": "fc7d7ac4-d1e1-a441-6717-ecb50b9ad7f6", + "headers": "Authorization: Bearer {{userToken}}\nContent-Type: application/json\n", + "url": "{{url}}/admin/admins", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474172375481, + "name": "create admin with user token", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "rawModeData": "{\n \"username\": \"dok_tester\"\n}", + "folder": "142d5b1b-b304-bced-c1f3-b59e8187d2aa" + }, + { + "id": "fe0a03f9-4969-651a-5eed-01de9398498e", + "headers": "Authorization: wrong\n", + "url": "{{url}}/admin/reviewers?categoryId=7", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474282221279, + "name": "get all reviewers without Bearer header", + "description": "", + "collectionId": "b4745ce1-c766-7823-c9cf-fa73e6b9cb2b", + "responses": [], + "folder": "81516934-d74e-f97d-262f-21d87d5961d1" + } + ] +} \ No newline at end of file diff --git a/test/postman/Reviewer_Management_API_environment.json b/test/postman/Reviewer_Management_API_environment.json new file mode 100755 index 000000000..ec4342a23 --- /dev/null +++ b/test/postman/Reviewer_Management_API_environment.json @@ -0,0 +1,34 @@ +{ + "id": "a10333e6-0eac-fbd1-136c-b3c8451c9d29", + "name": "Admin App - TC API Reviewer Management API", + "values": [ + { + "key": "url", + "value": "http://localhost:8080/api/v2", + "type": "text", + "enabled": true + }, + { + "key": "adminToken", + "type": "text", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU2IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNTkyNjgsImlhdCI6MTQ3NDE1OTI2OH0.KRgW9TxNOEiEu5YdQnXQO1nKFULIuy7JlzDZdq9QFQY", + "enabled": true + }, + { + "key": "userToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNzI0MDgsImlhdCI6MTQ3NDE3MjQwOH0.sIG2FoNiCldizzcTMQ9iAFh-PCigNGBAlicxms6uTkk", + "type": "text", + "enabled": true + }, + { + "key": "authToken", + "type": "text", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAyODI4MDMsImlhdCI6MTQ3NDI4MjgwM30.s6q_FRFryMslkWCkR0wPSWwTopkZhHH8g9R_4GPf9m4", + "enabled": true + } + ], + "timestamp": 1474282803634, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2016-09-19T11:00:24.778Z", + "_postman_exported_using": "Postman/4.7.1" +} \ No newline at end of file diff --git a/test/scripts/bridge.js b/test/scripts/bridge.js new file mode 100755 index 000000000..7a8d90eb3 --- /dev/null +++ b/test/scripts/bridge.js @@ -0,0 +1,134 @@ +/*jslint nomen: true */ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * This is the simple service that provides a "bridge" between a + * client and the database server. It accepts SQL queries and executes them. + * + * Added just to solve a deployment problem. Can (and probably should) be removed + * when this problem will be fixed + * + * @author TCSCODER + * @version 1.0 + */ +"use strict"; + +var http = require("http"), + async = require("async"), + Jdbc = require("informix-wrapper"), + path = require("path"), + tcConfig = require(path.dirname(path.dirname(__dirname)) + "/config/tc-config.js").tcConfig; + +var connections = {}; + +var server = http.createServer(function (req, res) { + + if (req.method !== "POST" || req.url !== "/bridge") { + res.writeHead(404, "Not found"); + res.end(); + return; + } + + var header = {"Content-Type": "application/json"}, + body = []; + + req.on("data", function (chunk) { + body.push(chunk); + }).on("end", function () { + body = Buffer.concat(body).toString(); + + var query; + try { + query = JSON.parse(body); + query.sql = new Buffer(query.sql, "base64").toString(); + } catch (x) { + res.writeHead(400, "Bad request", header); + res.end(JSON.stringify(x.toString())); + return; + } + + if (!query.db) { + res.writeHead(400, "Bad request", header); + res.end("'db' parameter is required"); + return; + } + if (!query.sql) { + res.writeHead(400, "Bad request", header); + res.end("'sql' parameter is required"); + return; + } + try { + async.waterfall([ + function (next) { + var jdbc, prefix, settings; + + if (connections[query.db] && connections[query.db].isConnected()) { + next(null, connections[query.db]); + } else { + jdbc = connections[query.db]; + + if (!jdbc) { + prefix = tcConfig.databaseMapping[query.db]; + if (!prefix) { + res.writeHead(400, "Bad request", header); + res.end(query.db + "- unknown database"); + return; + } + settings = { + user: process.env[prefix + "_USER"], + host: process.env[prefix + "_HOST"], + port: parseInt(process.env[prefix + "_PORT"], 10), + password: process.env[prefix + "_PASSWORD"], + database: query.db, + server: process.env[prefix + "_NAME"], + minpool: parseInt(process.env.MINPOOL, 10) || 1, + maxpool: parseInt(process.env.MAXPOOL, 10) || 60, + maxsize: parseInt(process.env.MAXSIZE, 10) || 0, + idleTimeout: parseInt(process.env.IDLETIMEOUT, 10) || 3600, + timeout: parseInt(process.env.TIMEOUT, 10) || 30000 + }; + jdbc = connections[query.db] = new Jdbc(settings, console.log).initialize(); + } + jdbc.connect(function (err) { + next(err, jdbc); + }); + } + }, + function (connection, next) { + connection.query(query.sql, next).execute(); + } + ], function (err, rows) { + res.writeHead(200, header); + if (err) { + res.end(JSON.stringify({exception: err.toString()})); + } else { + res.end(JSON.stringify({results: rows})); + } + }); + } catch (x) { + res.writeHead(200, header); + res.end(JSON.stringify({exception: x.toString()})); + } + }); + +}); + +/** + * Close database connection when application exit. + */ +function gracefulShutdown() { + var db, conn; + for (db in connections) { + if (connections.hasOwnProperty(db)) { + conn = connections[db]; + if (conn.isConnected()) { + conn.disconnect(); + } + } + } + process.exit(); +} + +process.on('SIGINT', gracefulShutdown); +process.on('SIGTERM', gracefulShutdown); +server.listen(8082); diff --git a/test/sqls/admins/tcs_catalog__clean b/test/sqls/admins/tcs_catalog__clean new file mode 100755 index 000000000..64ed1e235 --- /dev/null +++ b/test/sqls/admins/tcs_catalog__clean @@ -0,0 +1,4 @@ +DELETE FROM user_group_xref WHERE login_id !=132456 AND group_id = 2000115; +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id !=132456); +DELETE FROM resource WHERE resource_role_id = 13 AND project_id IS NULL AND user_id !=132456; +DELETE FROM user_role_xref WHERE role_id=2087 AND login_id !=132456; \ No newline at end of file diff --git a/test/sqls/admins/tcs_catalog__insert_test_data b/test/sqls/admins/tcs_catalog__insert_test_data new file mode 100755 index 000000000..169012497 --- /dev/null +++ b/test/sqls/admins/tcs_catalog__insert_test_data @@ -0,0 +1,15 @@ +INSERT INTO user_group_xref VALUES(100000, 20, 2000115, 132456, 1, current); +UPDATE informixoltp:rating SET rating = -1 WHERE coder_id = 20; +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(100000, 13, 20, 132456, current, 132456, current); +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(100000, 1 , 20, 132456, current, 132456, current); +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), 20, 2087, 132456, 1); + +INSERT INTO resource (resource_id, resource_role_id, user_id, create_user, create_date, modify_user, modify_date) +VALUES(100001, 13, 124856, 132456, current, 132456, current); +INSERT INTO resource_info (resource_id, resource_info_type_id, value, create_user, create_date, modify_user, modify_date) +VALUES(100001, 1 , 124856, 132456, current, 132456, current); + + +INSERT INTO user_role_xref VALUES ((SELECT NVL(MIN(user_role_id), 0) - 1 FROM user_role_xref), 124857, 2087, 132456, 1); diff --git a/test/sqls/copilots/tcs_catalog__clean b/test/sqls/copilots/tcs_catalog__clean new file mode 100755 index 000000000..d3aa9c12d --- /dev/null +++ b/test/sqls/copilots/tcs_catalog__clean @@ -0,0 +1 @@ +DELETE FROM copilot_profile; \ No newline at end of file diff --git a/test/sqls/copilots/tcs_catalog__insert_test_data b/test/sqls/copilots/tcs_catalog__insert_test_data new file mode 100755 index 000000000..c99025668 --- /dev/null +++ b/test/sqls/copilots/tcs_catalog__insert_test_data @@ -0,0 +1,6 @@ +INSERT INTO copilot_profile (copilot_profile_id,user_id,copilot_profile_status_id,suspension_count, +reliability,activation_date,show_copilot_earnings,create_user, create_date, +update_user, update_date, is_software_copilot, is_studio_copilot) +VALUES ((select NVL(max(copilot_profile_id), 0) + 1 from copilot_profile), +20 ,1,0,100.00, current,'t', 132456, +current, 132456, current, 't', 't'); \ No newline at end of file diff --git a/test/sqls/reviewers/tcs_catalog__clean b/test/sqls/reviewers/tcs_catalog__clean new file mode 100755 index 000000000..41655f22e --- /dev/null +++ b/test/sqls/reviewers/tcs_catalog__clean @@ -0,0 +1 @@ +DELETE FROM rboard_user WHERE project_type_id IN (7,17,38, 39); \ No newline at end of file diff --git a/test/sqls/reviewers/tcs_catalog__insert_test_data b/test/sqls/reviewers/tcs_catalog__insert_test_data new file mode 100755 index 000000000..7c97f24de --- /dev/null +++ b/test/sqls/reviewers/tcs_catalog__insert_test_data @@ -0,0 +1 @@ +INSERT INTO rboard_user VALUES (20, 7, 4, 100, 1); \ No newline at end of file diff --git a/test/test.admins.js b/test/test.admins.js new file mode 100755 index 000000000..df930b457 --- /dev/null +++ b/test/test.admins.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('Get Admins API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + clearDb(done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return admins", function (done) { + assertResponse(adminHeader, "expect_get_admins", done); + }); + +}); diff --git a/test/test.copilots.js b/test/test.copilots.js new file mode 100755 index 000000000..e15ffa518 --- /dev/null +++ b/test/test.copilots.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('GET copilots API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return copilots", function (done) { + assertResponse(adminHeader, "expect_get_copilots", done); + }); + +}); diff --git a/test/test.createAdmin.js b/test/test.createAdmin.js new file mode 100755 index 000000000..09bc20d32 --- /dev/null +++ b/test/test.createAdmin.js @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = {username: username}; + +describe('Create Admin API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_admin_with_empty_body", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, {username: ' \n \t \r'}, + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, {username: true}, + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, {username: 'notexist'}, + "User with the username: notexist does not exist", done); + }); + + it("should return already exist error for exist admin username", function (done) { + assertErrorResponse(409, adminHeader, {username: 'dok_tester'}, + "User dok_tester has already been added to Admin Group", done); + }); + + it("should create admin successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_admin", done); + }); + + it("should create admin successfully if exist admin resource", function (done) { + assertResponse(adminHeader, {username: 'wyzmo'}, "expect_create_admin_with_exist_admin_resource", done); + }); + + it("should return admin role exist role if exist admin role", function (done) { + assertErrorResponse(409, adminHeader, {username: 'cartajs'}, + "User cartajs has already been assigned Admin role", done); + }); +}); diff --git a/test/test.createCopilot.js b/test/test.createCopilot.js new file mode 100755 index 000000000..86b9fe953 --- /dev/null +++ b/test/test.createCopilot.js @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = { + username: username, + isSoftwareCopilot: true, + isStudioCopilot: false +}; + +describe('Create Copilot API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_copilot_with_empty_body", done); + }); + + + it("should return required error for missing isSoftwareCopilot", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'isSoftwareCopilot'), + "expect_create_copilot_with_missing_isSoftwareCopilot", done); + }); + + it("should return required error for missing isStudioCopilot", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'isStudioCopilot'), + "expect_create_copilot_with_missing_isStudioCopilot", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid isStudioCopilot", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {isStudioCopilot: 'invalid boolean'}), + "isStudioCopilot should be 0, 1, true or false.", done); + }); + + it("should return validation error for invalid isSoftwareCopilot", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {isSoftwareCopilot: 'invalid boolean'}), + "isSoftwareCopilot should be 0, 1, true or false.", done); + }); + + it("should return validation error for isSoftwareCopilot/isStudioCopilot false at same time", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), { isStudioCopilot: 0, isSoftwareCopilot: false }), + "Studio Copilot and Software Copilot Checkbox should have at least one checked", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return duplicate resource error if exist copilot", function (done) { + assertErrorResponse(409, adminHeader, _.extend(_.clone(testBody), {username: 'dok_tester'}), + "The user dok_tester is already added as copilot", done); + }); + + it("should create copilot successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_copilot", done); + }); +}); diff --git a/test/test.createReviewer.js b/test/test.createReviewer.js new file mode 100755 index 000000000..f22b48991 --- /dev/null +++ b/test/test.createReviewer.js @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester1'; +var testBody = { + username: username, + categoryId: 7, + immune: 0 +}; + +describe('Create Reviewer API', function () { + this.timeout(6000000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/reviewers", + req = request(API_ENDPOINT) + .post(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (file) { + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + } + done(); + }); + } + + /** + * Create reviewer and validate exist such reviewer with immune = true + * @param {Object} postData - the data post to api. + * @param {Boolean} immune - the immune flag + * @param {Function} done - the callback + */ + function assertCreateAndGetAllResponse(postData, immune, done) { + var authHeader = adminHeader; + assertResponse(adminHeader, postData, null, function (err) { + if (err) { + return done(err); + } + request(API_ENDPOINT) + .get("/v2/admin/reviewers?categoryId=" + postData.categoryId) + .set('Accept', 'application/json') + .set('Authorization', authHeader) + .expect('Content-Type', /json/) + .expect(200) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var reviewer = _.find(res.body.reviewers, function (data) { + return data.name === postData.username; + }); + assert.ok(reviewer); + assert.equal(reviewer.immune, immune); + done(); + }); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_create_reviewer_with_empty_body", done); + }); + + + it("should return required error for missing categoryId", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'categoryId'), + "expect_create_reviewer_with_missing_categoryId", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 'invalid number'}), + "categoryId should be number.", done); + }); + + it("should return validation error for negative categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: -2}), + "categoryId should be positive.", done); + }); + + it("should return validation error for non-integer categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 1.1}), + "categoryId should be Integer.", done); + }); + + it("should return validation error for invalid immune", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {immune: 'invalid boolean'}), + "immune should be 0, 1, true or false.", done); + }); + + it("should return validation error for not valid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 99999}), + "Category Id 99999 is not a valid category ID", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return duplicate resource error if exist reviewer", function (done) { + assertErrorResponse(409, adminHeader, _.extend(_.clone(testBody), {username: 'dok_tester'}), + "User dok_tester is in the specific review board", done); + }); + + it("should create reviewer successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_create_reviewer", done); + }); + + it("should create immune=true reviewer successfully with immune=1 in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), {username: 'wyzmo', immune: 1}), true, done); + }); + + it("should create immune=false reviewer successfully with immune=0 in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), { + username: 'ksmith', + immune: 0 + }), false, done); + }); + + it("should create immune=true reviewer successfully with studio type", function (done) { + // 17 = Web Design = Studio type + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), { + username: 'cartajs', + categoryId: 17 + }), true, done); + }); + + it("should create immune=false reviewer successfully with immune=false in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'Yoshi', categoryId: 17, immune: false}), false, done); + }); + + it("should create immune=true reviewer successfully with code category id in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'Hung', categoryId: 39}), true, done); + }); + + + it("should create immune=true reviewer successfully with f2f category id in body", function (done) { + assertCreateAndGetAllResponse(_.extend(_.omit(testBody, 'immune'), + {username: 'liquid_user', categoryId: 38}), true, done); + }); + +}); diff --git a/test/test.removeAdmin.js b/test/test.removeAdmin.js new file mode 100755 index 000000000..1904ccb21 --- /dev/null +++ b/test/test.removeAdmin.js @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/admins/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester'; +var testBody = {username: username}; + +describe('Remove Admin API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/admins", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/admins/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_admin_with_empty_body", done); + }); + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, {username: ' \n \t \r'}, + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, {username: true}, + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, {username: 'notexist'}, + "User with the username: notexist does not exist", done); + }); + + it("should remove admin successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_admin", done); + }); +}); diff --git a/test/test.removeCopilot.js b/test/test.removeCopilot.js new file mode 100755 index 000000000..48ae05c01 --- /dev/null +++ b/test/test.removeCopilot.js @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/copilots/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var username = 'dok_tester'; +var testBody = { + username: username, + isSoftwareCopilot: true, + isStudioCopilot: false +}; + +describe('Remove Copilot API', function () { + this.timeout(600000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/copilots", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/copilots/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_copilot_with_empty_body", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return not found error if not exist copilot", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'user'}), + "user is not in the copilot pool", done); + }); + + + it("should remove copilot successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_copilot", done); + }); +}); diff --git a/test/test.removeReviewer.js b/test/test.removeReviewer.js new file mode 100755 index 000000000..1290fd1aa --- /dev/null +++ b/test/test.removeReviewer.js @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var _ = require('underscore'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; +var testBody = { + username: 'dok_tester', + categoryId: 7 +}; + +describe('Remove Reviewer API', function () { + this.timeout(6000000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @return {Object} request + */ + function createRequest(statusCode, authHeader, postData) { + var url = "/v2/admin/reviewers", + req = request(API_ENDPOINT) + .del(url) + .send(postData) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, postData, errorMessage, done) { + createRequest(statusCode, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {Object} postData - the data post to api. + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, postData, file, done) { + createRequest(200, authHeader, postData) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (file) { + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + } + done(); + }); + } + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, testBody, "You need to login for this api.", done); + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, testBody, "You don\'t have access to this api.", done); + }); + + it("should return required error for empty body or missing username", function (done) { + assertResponse(adminHeader, {}, "expect_remove_reviewer_with_empty_body", done); + }); + + + it("should return required error for missing categoryId", function (done) { + assertResponse(adminHeader, _.omit(testBody, 'categoryId'), + "expect_remove_reviewer_with_missing_categoryId", done); + }); + + + it("should return validation error for empty username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: ' \n \t \r'}), + "username should be non-null and non-empty string.", done); + }); + + it("should return validation error for invalid username", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {username: true}), + "username should be string.", done); + }); + + it("should return validation error for invalid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 'invalid number'}), + "categoryId should be number.", done); + }); + + it("should return validation error for negative categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: -2}), + "categoryId should be positive.", done); + }); + + it("should return validation error for non-integer categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 1.1}), + "categoryId should be Integer.", done); + }); + + + it("should return validation error for not valid categoryId", function (done) { + assertErrorResponse(400, adminHeader, _.extend(_.clone(testBody), {categoryId: 99999}), + "Category Id 99999 is not a valid category ID", done); + }); + + it("should return not found error if not exist user", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'notexist'}), + "User with the username: notexist does not exist", done); + }); + + it("should return not found error if not reviewer", function (done) { + assertErrorResponse(404, adminHeader, _.extend(_.clone(testBody), {username: 'user'}), + "There is no reviewer with the username:user in category: Architecture", done); + }); + + it("should remove reviewer successfully", function (done) { + assertResponse(adminHeader, testBody, "expect_remove_reviewer", done); + }); +}); diff --git a/test/test.reviewers.js b/test/test.reviewers.js new file mode 100755 index 000000000..e17af97c2 --- /dev/null +++ b/test/test.reviewers.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSCODER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint nomen: true */ + +/** + * Module dependencies. + */ +var fs = require('fs'); +var request = require('supertest'); +var assert = require('chai').assert; +var async = require("async"); + +var testHelper = require('./helpers/testHelper'); +var SQL_DIR = __dirname + "/sqls/reviewers/"; +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + + +describe('Get Reviewers API', function () { + this.timeout(60000); // The api with testing remote db could be quit slow + var adminHeader, memberHeader; + + /** + * Create authorization header before each test + * @param {Function} done the callback + */ + beforeEach(function (done) { + adminHeader = "Bearer " + testHelper.getAdminJwt(); + memberHeader = "Bearer " + testHelper.getMemberJwt(); + done(); + }); + + /** + * Clear database + * @param {Function} done the callback + */ + function clearDb(done) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__clean", "tcs_catalog", done); + } + + /** + * This function is run before all tests. + * Generate tests data. + * @param {Function} done the callback + */ + before(function (done) { + async.waterfall([ + function (cb) { + clearDb(cb); + }, function (cb) { + testHelper.runSqlFile(SQL_DIR + "tcs_catalog__insert_test_data", "tcs_catalog", cb); + } + ], done); + }); + + /** + * This function is run after all tests. + * Clean up all data. + * @param {Function} done the callback + */ + after(function (done) { + clearDb(done); + }); + + /** + * Create request and return it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @return {Object} request + */ + function createRequest(statusCode, authHeader) { + var url = "/v2/admin/reviewers?categoryId=7", + req = request(API_ENDPOINT) + .get(url) + .set('Accept', 'application/json'); + if (authHeader) { + req = req.set('Authorization', authHeader); + } + return req.expect('Content-Type', /json/).expect(statusCode); + } + + /** + * Get response and assert it + * @param {Number} statusCode the expected status code + * @param {String} authHeader the Authorization header. Optional + * @param {String} errorMessage the expected error message header. Optional + * @param {Function} done the callback + */ + function assertErrorResponse(statusCode, authHeader, errorMessage, done) { + createRequest(statusCode, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + if (errorMessage) { + assert.ok(res.body); + assert.ok(res.body.error); + if (statusCode === 200) { + assert.equal(res.body.error, errorMessage); + } else { + assert.equal(res.body.error.details, errorMessage); + } + } + done(); + }); + } + + /** + * Make request to checkpoint API and compare response with given file + * @param {String} authHeader the Authorization header. Optional + * @param {String} file - the file which contains expected response + * @param {Function} done - the callback + */ + function assertResponse(authHeader, file, done) { + createRequest(200, authHeader) + .end(function (err, res) { + if (err) { + done(err); + return; + } + var body = res.body, expected = require("./test_files/reviewers/" + file); + delete body.serverInformation; + delete body.requesterInformation; + assert.deepEqual(body, expected, "Invalid response"); + done(); + }); + } + + + it("should return unauthorized error for missing Authorization header", function (done) { + assertErrorResponse(401, null, "You need to login for this api.", done); + + }); + + it("should return forbidden error for not admin token", function (done) { + assertErrorResponse(403, memberHeader, "You don\'t have access to this api.", done); + }); + + it("should return reviewers", function (done) { + assertResponse(adminHeader, "expect_get_reviewers", done); + }); + +}); diff --git a/test/test_files/admins/expect_create_admin.json b/test/test_files/admins/expect_create_admin.json new file mode 100755 index 000000000..6f0dbd086 --- /dev/null +++ b/test/test_files/admins/expect_create_admin.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "dok_tester1 has been successfully added as TopCoder Admin" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_create_admin_with_empty_body.json b/test/test_files/admins/expect_create_admin_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/admins/expect_create_admin_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json b/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json new file mode 100755 index 000000000..9653f022b --- /dev/null +++ b/test/test_files/admins/expect_create_admin_with_exist_admin_resource.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "wyzmo has been successfully added as TopCoder Admin" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_get_admins.json b/test/test_files/admins/expect_get_admins.json new file mode 100755 index 000000000..2e3c7cdb5 --- /dev/null +++ b/test/test_files/admins/expect_get_admins.json @@ -0,0 +1,11 @@ +{ + "allAdmins": [ + { + "adminGroup": true, + "adminRole": true, + "id": 132456, + "managerResource": true, + "name": "heffan" + } + ] +} \ No newline at end of file diff --git a/test/test_files/admins/expect_remove_admin.json b/test/test_files/admins/expect_remove_admin.json new file mode 100755 index 000000000..b07185a05 --- /dev/null +++ b/test/test_files/admins/expect_remove_admin.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "TopCoder Admin: dok_tester has been successfully removed" +} \ No newline at end of file diff --git a/test/test_files/admins/expect_remove_admin_with_empty_body.json b/test/test_files/admins/expect_remove_admin_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/admins/expect_remove_admin_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot.json b/test/test_files/copilots/expect_create_copilot.json new file mode 100755 index 000000000..4a17102f0 --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot.json @@ -0,0 +1,4 @@ +{ + "message": "Copilot dok_tester1 has been successfully added", + "success": true +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_empty_body.json b/test/test_files/copilots/expect_create_copilot_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json b/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json new file mode 100755 index 000000000..a043993ba --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_missing_isSoftwareCopilot.json @@ -0,0 +1,3 @@ +{ + "error": "Error: isSoftwareCopilot is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json b/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json new file mode 100755 index 000000000..756831d32 --- /dev/null +++ b/test/test_files/copilots/expect_create_copilot_with_missing_isStudioCopilot.json @@ -0,0 +1,3 @@ +{ + "error": "Error: isStudioCopilot is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_get_copilots.json b/test/test_files/copilots/expect_get_copilots.json new file mode 100755 index 000000000..4a83f8652 --- /dev/null +++ b/test/test_files/copilots/expect_get_copilots.json @@ -0,0 +1,10 @@ +{ + "allCopilots": [ + { + "id": 20, + "name": "dok_tester", + "softwareCopilot": true, + "studioCopilot": true + } + ] +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_remove_copilot.json b/test/test_files/copilots/expect_remove_copilot.json new file mode 100755 index 000000000..7939ccf6e --- /dev/null +++ b/test/test_files/copilots/expect_remove_copilot.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "Copilot dok_tester has been successfully removed" +} \ No newline at end of file diff --git a/test/test_files/copilots/expect_remove_copilot_with_empty_body.json b/test/test_files/copilots/expect_remove_copilot_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/copilots/expect_remove_copilot_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer.json b/test/test_files/reviewers/expect_create_reviewer.json new file mode 100755 index 000000000..c209e8f5e --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer.json @@ -0,0 +1,4 @@ +{ + "message": "dok_tester1 has been successfully added into Architecture Review Board", + "success": true +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json b/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json b/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json new file mode 100755 index 000000000..1763c9a49 --- /dev/null +++ b/test/test_files/reviewers/expect_create_reviewer_with_missing_categoryId.json @@ -0,0 +1,3 @@ +{ + "error": "Error: categoryId is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_get_reviewers.json b/test/test_files/reviewers/expect_get_reviewers.json new file mode 100755 index 000000000..2e9e8b631 --- /dev/null +++ b/test/test_files/reviewers/expect_get_reviewers.json @@ -0,0 +1,12 @@ +{ + "categoryId":7, + "reviewers": [ + { + "id": 20, + "name": "dok_tester", + "projectCategoryId": 7, + "projectCategoryName": "Architecture", + "immune": true + } + ] +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer.json b/test/test_files/reviewers/expect_remove_reviewer.json new file mode 100755 index 000000000..4eaad6ebc --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer.json @@ -0,0 +1,4 @@ +{ + "success": true, + "message": "dok_tester has been successfully removed from Architecture Review Board" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json b/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json new file mode 100755 index 000000000..cfaebe23c --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer_with_empty_body.json @@ -0,0 +1,3 @@ +{ + "error": "Error: username is a required parameter for this action" +} \ No newline at end of file diff --git a/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json b/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json new file mode 100755 index 000000000..1763c9a49 --- /dev/null +++ b/test/test_files/reviewers/expect_remove_reviewer_with_missing_categoryId.json @@ -0,0 +1,3 @@ +{ + "error": "Error: categoryId is a required parameter for this action" +} \ No newline at end of file From 2236bae7fc5b825f306fe36ee804d566370a3fb0 Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Wed, 28 Sep 2016 14:53:44 -0500 Subject: [PATCH 15/67] fixing bug that allowed limits of more than 100 records to be retrieved by the get top members api --- .gitignore | 1 + actions/.challengeTypes.js.swp | Bin 0 -> 16384 bytes actions/.challenges.js.swp | Bin 0 -> 196608 bytes actions/tops.js | 3 - deploy/.env.sh.swp | Bin 0 -> 12288 bytes initializers/.dataAccess.js.swp | Bin 0 -> 28672 bytes local/docker-compose.yml | 13 ++++- local/env.sh | 100 ++++++++++++++++++++++++++++++++ local/node/Dockerfile | 15 +++++ queries/get_top_members_data | 3 +- 10 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 actions/.challengeTypes.js.swp create mode 100644 actions/.challenges.js.swp create mode 100644 deploy/.env.sh.swp create mode 100644 initializers/.dataAccess.js.swp create mode 100644 local/env.sh create mode 100644 local/node/Dockerfile diff --git a/.gitignore b/.gitignore index fda4d8551..6b610a3e1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test/tmp/design_tmp_submissions/*.zip test/tmp/memberPhoto/* .idea .settings +**/jdk-8u51-linux-x64.gz diff --git a/actions/.challengeTypes.js.swp b/actions/.challengeTypes.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..072ff886b8427e7a6c2f4983e272628b6076a22b GIT binary patch literal 16384 zcmeI2UuYaf9LLAn*7~O^S|MPS>GdI(aJ?i=sD?oT2N`>14P#oVn3At`Z5J~p_e zdbPZLv9n2pUY~GcPX$q*Qy0E3gPI(l31w717G15dy=N7$3f!auYuVuDfqpWwv9N*P zf8SF#Y0nn73RnfK0#*U5fK|XMU=^?mSOu1o0&%#4y?_R<$Qm7FueWr(-kN>J*8=8J z*=sXj_R+pr1*`&A0jq#jz$#!BunJfOtO8a6tAJI&D)2v4K)8$*(XKyeBah$zoBRJi z?_un7@EQ0JyauMh0q{8J0_(tPunOD)&fLw|r=SkjgR^V#9&~|q;FG%;^T81K;!eii z0MCJ4;9;;9e0K-x1#f{_@CdkaJ7X8YJK%NjJU9XdK@TVb4juyQK^OQHUsU`IE`X20 zY48#Vz|-IW=m%Y3B{+8*V;_K1;4pX)oLSA-M?e7yCc!gc0*rtjum)UN#n=zvD{vOP z2VMo!U>Dc`ep|`dm*7pX6%@gb_)_Ita2Z?x=fOGfGI$YGzz*;LSPg!`mo(pi^Wc4O z8k_=8f*wF)`1|#LA;am?Nx--9Y7&?;8N7(|871zhAdn`Z!~;1U_wZRpnOzZ!iil){ zA`{)Q6HPiu@l~z+Xvt)VwFsj0cn7j{Ybo6B67TEdaovk}b_4I|#wHc`Gp5v()DaGZ zP>HF{-Kiy >BNJKZDu_KuGp8lOCfe_81)GqqWT zbl61r$T@M;R7a^=o=7|`U6Yzx_5xH$Je~ed^<~13|UQpw$6EZxeBQhm^vhG0+qb6jm zB|^LCL}iBW8&2Xn>+9`hY;L>h=3J|@?RV65jBvRweF#~ui@^2eUNzDh?qKUb)~<-`Qd8kw#lq+`6ptm3;>7io zIWDvjo?9sKZdX>4T6a&opjYL^rE6=t@^bV0im#mG(rqheM98`CKko8{oY;l*5y?Eg z<4()L+k<2LdignGRlv8e(-ZUaf%Qd9PXDei=H>46#*TX}f6S-f;7mJzCdKT+Xg;EA z{$xt4V{W!d=d#>nL?v>Qk(N=2laCUu(K2q&S;lZjv8B=|-EA*U+qf6YMmd+lC`ac- zk8YNg-lorUXQtM2LzByxo8@n%PSPSY>ABY^JR)LQQ+j3*0|*z)twoJMk#d+s_2Mkc zHw>C3eq6?~I4Ycau^p;Vznjx}v%r+lq%8EEI67b6)>^V#X)NR}vXNaZAqBI96ms$} zy2xG6P%l_pN3~XZ;c6(g&Q(V%7Nz7?)mCepD_`1L>RX}%=`LtC6`=5J*l}bOm1VkM z9!R8~DHgCkPtG632nM1P{(hn13^C%b5?`<^x0OcYN||2GM$Rit4;K%$I)luU!ORs#^Fxhjwp z;5F^-Qp%Sim}~cVlQCNLMGd7z45>qmw+s-D!Z%PyB!zbBS@_c#6_)eH_f=>R<~m2y zRL~6Hb2Aceh|olwF``cdwHlS8`V)za;>FCi=dx6XR^Z5$srW{0VQ7pIdd*6k-b1n^ zf}H5LFy0JNB(M3Zf+*e#s#-*d7#%zaBrCOYoOT!ZqkJvpjV)|B5)94^d5O8*dJNKt zx|~k*1yl%iwdceIOdIL=54&ztadJ`A5WobgN%t|S?68Le>=CbO)!+l=0cuV|>KfXA z(u?bSk8D)n6bEI^gMC4J5sLCD+s=-%{U%W$j>isQ3TDNWR!$uONkbkNf%k0ENKBUI zekrYvl15ZGqrF-^=Ead7zPW#3kdLcyM7gMXEO5#tKJ5GaAmxbAmPqO;=~CYr?f>7# zUi}iF{Xe$C=DLji{AsWq^n#yo|9kLtmiHp%x0hAGDqt0`3RnfK0#*U5fK|XMU=^?m YSOxw|1(w|}V1M_AhwO*QoA~|VFLnG9u>b%7 literal 0 HcmV?d00001 diff --git a/actions/.challenges.js.swp b/actions/.challenges.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..66275bc8f4dfa0aaac9cd1e94b8ec779e0fa94a3 GIT binary patch literal 196608 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 Kof&8JQ1~yqE^jpe literal 0 HcmV?d00001 diff --git a/initializers/.dataAccess.js.swp b/initializers/.dataAccess.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..542d73f7eba4b7e4272e91f9d0283b665d384cbe GIT binary patch literal 28672 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 literal 0 HcmV?d00001 diff --git a/local/docker-compose.yml b/local/docker-compose.yml index 986dbdedb..d37d536f0 100644 --- a/local/docker-compose.yml +++ b/local/docker-compose.yml @@ -1,6 +1,15 @@ version: '2' services: + informix: + image: appiriodevops/informix:1.2 + ports: + - 2021:2021 tc-api: - image: "node" + build: ./node + depends_on: + - informix ports: - - "7777:7777" + - "8080:8080" + volumes: + - ../:/tc-api + command: /bin/bash -c "cd /tc-api && npm install && source local/env.sh && npm start && tail -f log/forever.log" diff --git a/local/env.sh b/local/env.sh new file mode 100644 index 000000000..ac4e6ab9c --- /dev/null +++ b/local/env.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +# +# Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved. +# +# Version: 1.3 +# Author: vangavroche, isv, TCASSEMBLER +# changes in 1.1: +# - add JIRA_USERNAME and JIRA_PASSWORD +# changes in 1.2: +# - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable +# - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable +# - added REDIS_HOST environment variable +# - added REDIS_PORT environment variable +# changes in 1.3 +# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable +# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable +# - added HIGHLIGHT_STYLE_LINK environment variable +# + +# tests rely on caching being off. But set this to a real value (or remove) while coding. +export CACHE_EXPIRY=-1 + +VM_IP=informix +if [ -n "$TC_VM_IP" ] +then +VM_IP=$TC_VM_IP +fi + +export TC_DB_NAME=informixoltp_tcp +export TC_DB_HOST=$VM_IP +export TC_DB_PORT=2021 +export TC_DB_USER=informix +export TC_DB_PASSWORD=1nf0rm1x + +export TC_DW_NAME=informixoltp_tcp +export TC_DW_HOST=$VM_IP +export TC_DW_PORT=2021 +export TC_DW_USER=informix +export TC_DW_PASSWORD=1nf0rm1x + +# oauth provider +export TC_API_HOST=api.topcoder.com + +# LDAP settings +export TC_LDAP_HOST=$VM_IP +export TC_LDAP_PORT=636 +export TC_LDAP_PASSWORD=secret +export TC_LDAP_MEMBER_BASE_DN="ou=members, dc=topcoder, dc=com" +export TC_BIND_DN="cn=Manager,dc=topcoder,dc=com" + +# Mail settings +export TC_EMAIL_HOST=smtp.gmail.com +export TC_EMAIL_HOST_PORT=465 +export TC_EMAIL_SECURED=true +export TC_EMAIL_ACCOUNT=tc.ldap.test.1@gmail.com +export TC_EMAIL_PASSWORD=tc_public_email +export TC_EMAIL_FROM=tc.ldap.test.1@gmail.com +export TC_EMAIL_TEMPLATE_DIR=mail_templates + +export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_SOFTWARE_SERVER_NAME="https://software.topcoder.com" +export TC_FORUMS_SERVER_NAME="http://apps.topcoder.com/forums" + +export PASSWORD_HASH_KEY="ciTHHTSMg6ixffIuPbB30A==" +## JDBC connection pool environment variables - set for all databases +export MINPOOL=1 +export MAXPOOL=20 +export MAXSIZE=0 +export IDLETIMEOUT=3600 +export TIMEOUT=30000 + +# Used in Jira soap service (Bugs API) +export JIRA_USERNAME=api_test +export JIRA_PASSWORD=8CDDp6BHLtUeUdD + +# Forum settings +export STUDIO_FORUMS_SERVER_NAME="http://studio.topcoder.com/forums" +export GRANT_FORUM_ACCESS=false +export DEV_FORUM_JNDI=jnp://env.topcoder.com:1199 + +## The period for expiring the generated tokens for password resetting +export RESET_PASSWORD_TOKEN_EMAIL_SUBJECT=TopCoder Account Password Reset +# Set this to 180000 which is 3 mins. This will help saving time for test. +export RESET_PASSWORD_TOKEN_CACHE_EXPIRY=180000 + +export REDIS_HOST=localhost +export REDIS_PORT=6379 + +export DEVELOP_SUBMISSION_MAX_SIZE=6144 + +export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png + +export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage +export WKHTMLTOIMAGE_IMAGE_WIDTH=1024 +export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css + +export JWT_TOKEN_COOKIE_KEY="tcjwt_vm" + +export ADMIN_API_KEY=1234567 diff --git a/local/node/Dockerfile b/local/node/Dockerfile new file mode 100644 index 000000000..f94975902 --- /dev/null +++ b/local/node/Dockerfile @@ -0,0 +1,15 @@ +FROM node:0.10 + +COPY jdk-8u51-linux-x64.gz /opt + +RUN cd /opt && \ + tar -xvf jdk-8u51-linux-x64.gz && \ + cd /usr/bin && \ + ln -s /opt/jdk1.8.0_51/bin/java java && \ + ln -s /opt/jdk1.8.0_51/bin/javac javac + +ENV JAVA_HOME=/opt/jdk1.8.0_51 + +RUN npm install -g java + +RUN apt-get install -y net-tools psmisc diff --git a/queries/get_top_members_data b/queries/get_top_members_data index 7b7ae710c..bf65f0a23 100644 --- a/queries/get_top_members_data +++ b/queries/get_top_members_data @@ -1,5 +1,6 @@ SELECT -FIRST @pageSize@ + SKIP @firstRowIndex@ + FIRST @pageSize@ c.coder_id AS user_id , handle , rating From d798cc51293e4d97e7a9b57a99bd433302dfeb26 Mon Sep 17 00:00:00 2001 From: Mauricio Desiderio Date: Wed, 28 Sep 2016 14:55:03 -0500 Subject: [PATCH 16/67] removing vi temp files --- .gitignore | 1 + actions/.challengeTypes.js.swp | Bin 16384 -> 0 bytes actions/.challenges.js.swp | Bin 196608 -> 0 bytes 3 files changed, 1 insertion(+) delete mode 100644 actions/.challengeTypes.js.swp delete mode 100644 actions/.challenges.js.swp diff --git a/.gitignore b/.gitignore index 6b610a3e1..d5f8cf869 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ test/tmp/memberPhoto/* .idea .settings **/jdk-8u51-linux-x64.gz +*.swp diff --git a/actions/.challengeTypes.js.swp b/actions/.challengeTypes.js.swp deleted file mode 100644 index 072ff886b8427e7a6c2f4983e272628b6076a22b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI2UuYaf9LLAn*7~O^S|MPS>GdI(aJ?i=sD?oT2N`>14P#oVn3At`Z5J~p_e zdbPZLv9n2pUY~GcPX$q*Qy0E3gPI(l31w717G15dy=N7$3f!auYuVuDfqpWwv9N*P zf8SF#Y0nn73RnfK0#*U5fK|XMU=^?mSOu1o0&%#4y?_R<$Qm7FueWr(-kN>J*8=8J z*=sXj_R+pr1*`&A0jq#jz$#!BunJfOtO8a6tAJI&D)2v4K)8$*(XKyeBah$zoBRJi z?_un7@EQ0JyauMh0q{8J0_(tPunOD)&fLw|r=SkjgR^V#9&~|q;FG%;^T81K;!eii z0MCJ4;9;;9e0K-x1#f{_@CdkaJ7X8YJK%NjJU9XdK@TVb4juyQK^OQHUsU`IE`X20 zY48#Vz|-IW=m%Y3B{+8*V;_K1;4pX)oLSA-M?e7yCc!gc0*rtjum)UN#n=zvD{vOP z2VMo!U>Dc`ep|`dm*7pX6%@gb_)_Ita2Z?x=fOGfGI$YGzz*;LSPg!`mo(pi^Wc4O z8k_=8f*wF)`1|#LA;am?Nx--9Y7&?;8N7(|871zhAdn`Z!~;1U_wZRpnOzZ!iil){ zA`{)Q6HPiu@l~z+Xvt)VwFsj0cn7j{Ybo6B67TEdaovk}b_4I|#wHc`Gp5v()DaGZ zP>HF{-Kiy >BNJKZDu_KuGp8lOCfe_81)GqqWT zbl61r$T@M;R7a^=o=7|`U6Yzx_5xH$Je~ed^<~13|UQpw$6EZxeBQhm^vhG0+qb6jm zB|^LCL}iBW8&2Xn>+9`hY;L>h=3J|@?RV65jBvRweF#~ui@^2eUNzDh?qKUb)~<-`Qd8kw#lq+`6ptm3;>7io zIWDvjo?9sKZdX>4T6a&opjYL^rE6=t@^bV0im#mG(rqheM98`CKko8{oY;l*5y?Eg z<4()L+k<2LdignGRlv8e(-ZUaf%Qd9PXDei=H>46#*TX}f6S-f;7mJzCdKT+Xg;EA z{$xt4V{W!d=d#>nL?v>Qk(N=2laCUu(K2q&S;lZjv8B=|-EA*U+qf6YMmd+lC`ac- zk8YNg-lorUXQtM2LzByxo8@n%PSPSY>ABY^JR)LQQ+j3*0|*z)twoJMk#d+s_2Mkc zHw>C3eq6?~I4Ycau^p;Vznjx}v%r+lq%8EEI67b6)>^V#X)NR}vXNaZAqBI96ms$} zy2xG6P%l_pN3~XZ;c6(g&Q(V%7Nz7?)mCepD_`1L>RX}%=`LtC6`=5J*l}bOm1VkM z9!R8~DHgCkPtG632nM1P{(hn13^C%b5?`<^x0OcYN||2GM$Rit4;K%$I)luU!ORs#^Fxhjwp z;5F^-Qp%Sim}~cVlQCNLMGd7z45>qmw+s-D!Z%PyB!zbBS@_c#6_)eH_f=>R<~m2y zRL~6Hb2Aceh|olwF``cdwHlS8`V)za;>FCi=dx6XR^Z5$srW{0VQ7pIdd*6k-b1n^ zf}H5LFy0JNB(M3Zf+*e#s#-*d7#%zaBrCOYoOT!ZqkJvpjV)|B5)94^d5O8*dJNKt zx|~k*1yl%iwdceIOdIL=54&ztadJ`A5WobgN%t|S?68Le>=CbO)!+l=0cuV|>KfXA z(u?bSk8D)n6bEI^gMC4J5sLCD+s=-%{U%W$j>isQ3TDNWR!$uONkbkNf%k0ENKBUI zekrYvl15ZGqrF-^=Ead7zPW#3kdLcyM7gMXEO5#tKJ5GaAmxbAmPqO;=~CYr?f>7# zUi}iF{Xe$C=DLji{AsWq^n#yo|9kLtmiHp%x0hAGDqt0`3RnfK0#*U5fK|XMU=^?m YSOxw|1(w|}V1M_AhwO*QoA~|VFLnG9u>b%7 diff --git a/actions/.challenges.js.swp b/actions/.challenges.js.swp deleted file mode 100644 index 66275bc8f4dfa0aaac9cd1e94b8ec779e0fa94a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196608 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 Date: Fri, 14 Oct 2016 16:14:07 -0700 Subject: [PATCH 17/67] fix cache for get endpoints --- actions/admins.js | 1 + actions/copilots.js | 1 + actions/reviewers.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actions/admins.js b/actions/admins.js index 842ebeae9..3348a70cd 100755 --- a/actions/admins.js +++ b/actions/admins.js @@ -73,6 +73,7 @@ exports.admins = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { diff --git a/actions/copilots.js b/actions/copilots.js index a0fa9a4cf..2dc0b68dd 100755 --- a/actions/copilots.js +++ b/actions/copilots.js @@ -61,6 +61,7 @@ exports.copilots = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { diff --git a/actions/reviewers.js b/actions/reviewers.js index 510ab4ea0..2f2891f88 100755 --- a/actions/reviewers.js +++ b/actions/reviewers.js @@ -140,6 +140,7 @@ exports.reviewers = { blockedConnectionTypes: [], outputExample: {}, version: 'v2', + cacheEnabled: false, transaction: 'read', // this action is read-only databases: ['tcs_catalog'], run: function (api, connection, next) { @@ -326,4 +327,4 @@ exports.removeReviewer = { api.helper.handleNoConnection(api, connection, next); } } -}; \ No newline at end of file +}; From c8b046c02dba34e9de29b4c2fb05eeb44959bf40 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 6 Dec 2016 20:28:31 -0500 Subject: [PATCH 18/67] modified active member count and active challenge count --- queries/tc_direct_facts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/queries/tc_direct_facts b/queries/tc_direct_facts index 945edf6bb..9b933c262 100644 --- a/queries/tc_direct_facts +++ b/queries/tc_direct_facts @@ -1,14 +1,7 @@ SELECT (SELECT count(unique p.project_id) FROM project p - ,project_phase submpp - ,project_phase regpp -WHERE regpp.project_id = p.project_id -AND regpp.phase_type_id = 1 -AND submpp.project_id = p.project_id -AND submpp.phase_type_id = 2 -and p.project_status_id = 1 -AND (regpp.phase_status_id = 2) +WHERE p.project_status_id = 1 AND NOT EXISTS (SELECT 'has_eligibility_constraints' FROM contest_eligibility ce WHERE ce.contest_id = p.project_id) ) + @@ -33,13 +26,13 @@ WHERE ce.contest_id = p.project_id) (select count (unique rur.user_id) from resource rur - , resource_info ri + , resource_info ri , project p where p.project_id = rur.project_id and rur.resource_id = ri.resource_id - and rur.resource_role_id = 1 + and rur.resource_role_id in (1,2,3,4,5,6,7,8,9,14,18,19,20,21) and ri.resource_info_type_id = 1 - and p.project_status_id = 1) AS active_members_count, + and p.project_status_id = 1) AS active_members_count, (SELECT COUNT(*) FROM user u From 86528900ace6f2348811f4482b88ed67a21cc9f9 Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 7 Dec 2016 12:32:09 -0500 Subject: [PATCH 19/67] updated prize_purse to include all active challenges --- queries/tc_direct_facts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/queries/tc_direct_facts b/queries/tc_direct_facts index 9b933c262..88adbf30e 100644 --- a/queries/tc_direct_facts +++ b/queries/tc_direct_facts @@ -46,20 +46,11 @@ where exists (select project_id from project where tc_direct_project_id = dp.pro where project_status_id = 4) AS completed_projects_count, nvl((select sum(pr.prize_amount) - - FROM project p - ,project_phase submpp - ,project_phase regpp , project_category_lu pcl , prize pr -WHERE regpp.project_id = p.project_id -AND regpp.phase_type_id = 1 -AND submpp.project_id = p.project_id -AND submpp.phase_type_id = 2 -and pr.project_id = p.project_id +WHERE pr.project_id = p.project_id and p.project_status_id = 1 -AND (regpp.phase_status_id = 2) and pcl.project_category_id = p.project_category_id AND NOT EXISTS (SELECT 'has_eligibility_constraints' FROM contest_eligibility ce WHERE ce.contest_id = p.project_id)), 0) AS prize_purse From 1f3ff759cbf3c4f2b5e52455bd28bf48a3bc5622 Mon Sep 17 00:00:00 2001 From: TonyJ Date: Mon, 6 Mar 2017 17:58:52 -0500 Subject: [PATCH 20/67] updated queries to exclude tasks that are assigned --- queries/get_active_challenges | 8 ++++++-- queries/get_open_challenges | 6 ++++++ queries/get_open_challenges_count | 4 ++++ queries/get_past_challenges | 8 ++++++++ queries/get_past_challenges_count | 4 ++++ queries/get_upcoming_challenges | 5 ++++- 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/queries/get_active_challenges b/queries/get_active_challenges index c172d49cc..94e2c25b5 100644 --- a/queries/get_active_challenges +++ b/queries/get_active_challenges @@ -44,6 +44,7 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id +, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -70,5 +71,8 @@ AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'co AND pp1.phase_status_id IN (2, 3) AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id - -ORDER BY @sort_column@ @sort_order@ +AND pi82.project_id = p.project_id +AND pi82.project_info_type_id = 82 +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) ORDER BY @sort_column@ @sort_order@ diff --git a/queries/get_open_challenges b/queries/get_open_challenges index 47ff4ee5f..26a94609a 100644 --- a/queries/get_open_challenges +++ b/queries/get_open_challenges @@ -70,6 +70,7 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id +, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -102,5 +103,10 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) +AND pi82.project_id = p.project_id +AND pi82.project_info_type_id = 82 +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_open_challenges_count b/queries/get_open_challenges_count index 16798c55a..5e26f7d47 100644 --- a/queries/get_open_challenges_count +++ b/queries/get_open_challenges_count @@ -5,6 +5,7 @@ FROM project p , project_info pn , project_category_lu pcl , project_info pi1 +, OUTER project_info pi82 WHERE 1=1 AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -32,3 +33,6 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) diff --git a/queries/get_past_challenges b/queries/get_past_challenges index 45c1668c2..333454aea 100644 --- a/queries/get_past_challenges +++ b/queries/get_past_challenges @@ -59,6 +59,11 @@ INNER JOIN project_info pn ON pn.project_id = p.project_id AND pn.project_info_t INNER JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 LEFT JOIN project_phase pp15 ON pp15.project_id = p.project_id AND pp15.phase_type_id = 15 LEFT JOIN project_info pidr ON pidr.project_id = p.project_id AND pidr.project_info_type_id = 26 +LEFT OUTER JOIN + project_info pi82 +ON + pi82.project_id = p.project_id +AND pi82.project_info_type_id = 82 WHERE p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) AND pcl.project_category_id NOT IN (27, 37) AND pcl.project_type_id IN (@track@) @@ -71,5 +76,8 @@ AND LOWER(pn.value) LIKE('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index c3f343bc5..d278a1002 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -5,6 +5,7 @@ FROM project p , project_info pn , project_info pi1 , project_category_lu pcl +, OUTER project_info pi82 WHERE p.project_id = pn.project_id AND pn.project_info_type_id = 6 AND pp1.project_id = p.project_id @@ -32,3 +33,6 @@ AND (not exists (select contest_id from contest_eligibility where contest_id = p or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) diff --git a/queries/get_upcoming_challenges b/queries/get_upcoming_challenges index 30f5bcaf2..1e3fd47c6 100644 --- a/queries/get_upcoming_challenges +++ b/queries/get_upcoming_challenges @@ -36,6 +36,7 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id +, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -67,5 +68,7 @@ AND pp1.scheduled_start_time > CURRENT AND pp1.scheduled_start_time < CURRENT + 90 UNITS DAY AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id - +AND NOT ( NVL(pi82.value,0) = 1 + AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 + ) ORDER BY @sort_column@ @sort_order@ From 3e136ac438c1875a8f9719df761f5401b216b73a Mon Sep 17 00:00:00 2001 From: TonyJ Date: Mon, 6 Mar 2017 18:22:54 -0500 Subject: [PATCH 21/67] updated queries to exclude tasks that are assigned --- queries/get_active_challenges | 8 ++------ queries/get_open_challenges | 7 +------ queries/get_past_challenges | 9 +-------- queries/get_upcoming_challenges | 5 +---- 4 files changed, 5 insertions(+), 24 deletions(-) diff --git a/queries/get_active_challenges b/queries/get_active_challenges index 94e2c25b5..81779a980 100644 --- a/queries/get_active_challenges +++ b/queries/get_active_challenges @@ -44,7 +44,6 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id -, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -71,8 +70,5 @@ AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'co AND pp1.phase_status_id IN (2, 3) AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id -AND pi82.project_id = p.project_id -AND pi82.project_info_type_id = 82 -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) ORDER BY @sort_column@ @sort_order@ +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +ORDER BY @sort_column@ @sort_order@ diff --git a/queries/get_open_challenges b/queries/get_open_challenges index 26a94609a..b585f222a 100644 --- a/queries/get_open_challenges +++ b/queries/get_open_challenges @@ -70,7 +70,6 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id -, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -103,10 +102,6 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND pi82.project_id = p.project_id -AND pi82.project_info_type_id = 82 -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_past_challenges b/queries/get_past_challenges index 333454aea..9cdacad71 100644 --- a/queries/get_past_challenges +++ b/queries/get_past_challenges @@ -59,11 +59,6 @@ INNER JOIN project_info pn ON pn.project_id = p.project_id AND pn.project_info_t INNER JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 LEFT JOIN project_phase pp15 ON pp15.project_id = p.project_id AND pp15.phase_type_id = 15 LEFT JOIN project_info pidr ON pidr.project_id = p.project_id AND pidr.project_info_type_id = 26 -LEFT OUTER JOIN - project_info pi82 -ON - pi82.project_id = p.project_id -AND pi82.project_info_type_id = 82 WHERE p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) AND pcl.project_category_id NOT IN (27, 37) AND pcl.project_type_id IN (@track@) @@ -76,8 +71,6 @@ AND LOWER(pn.value) LIKE('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_upcoming_challenges b/queries/get_upcoming_challenges index 1e3fd47c6..0f026ebbb 100644 --- a/queries/get_upcoming_challenges +++ b/queries/get_upcoming_challenges @@ -36,7 +36,6 @@ FROM project p , outer project_info pi4 --forum id , outer project_info pi79 , project_info pi1 -- external id -, OUTER project_info pi82 WHERE p.project_status_id = pstatus.project_status_id AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -68,7 +67,5 @@ AND pp1.scheduled_start_time > CURRENT AND pp1.scheduled_start_time < CURRENT + 90 UNITS DAY AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ From 873749141f54539fd5ccbd350ce408f55210d1f7 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 7 Mar 2017 16:35:11 -0500 Subject: [PATCH 22/67] added task check --- queries/get_past_challenges_count | 102 ++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index d278a1002..9cdacad71 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -1,38 +1,76 @@ -SELECT COUNT(*) AS total +SELECT is_studio +, challenge_type +, challenge_name +, challenge_id +, (SELECT value FROM project_info WHERE project_id = challenge_id AND project_info_type_id = 4) AS forum_id +, num_submissions +, num_registrants +, number_of_checkpoints_prizes +, first_place_prize +, total_prize +, checkpoint_total_prizes +, registration_start_date +, registration_end_date +, checkpoint_submission_end_date +, submission_end_date +, status +, digital_run_points +, nvl((SELECT MAX(event_id) FROM contest_project_xref x, contest c WHERE project_id = challenge_id AND c.contest_id = x.contest_id), 0) AS event_id +, (SELECT event_short_desc FROM event e WHERE e.event_id = nvl((SELECT MAX(event_id) FROM contest_project_xref x, contest c WHERE project_id = challenge_id AND c.contest_id = x.contest_id), 0)) AS event_name +, technology_list(value_for_technology) AS technologies +, platform_list(id_for_platform) AS platforms +, registration_open +, NVL((SELECT CAST('t' AS boolean) FROM contest_eligibility WHERE contest_id = challenge_id), CAST('f' AS boolean)) AS is_private +, (SELECT value FROM project_info pi53 WHERE project_id = challenge_id AND project_info_type_id = 53) AS submissions_viewable +FROM ( +SELECT +SKIP @first_row_index@ +FIRST @page_size@ + CASE WHEN (p.project_studio_spec_id IS NOT NULL) THEN 1 ELSE 0 END AS is_studio +, pcl.description AS challenge_type +, pn.value AS challenge_name +, p.project_id AS challenge_id +, (SELECT COUNT(*) FROM submission s1 INNER JOIN upload u1 ON s1.upload_id = u1.upload_id + WHERE u1.project_id = p.project_id + AND s1.submission_type_id = 1 + AND s1.submission_status_id <> 5) AS num_submissions +, (SELECT COUNT(*) FROM resource r WHERE r.project_id = p.project_id AND r.resource_role_id = 1) AS num_registrants +, NVL((SELECT SUM(pr.number_of_submissions) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 14), 0) AS number_of_checkpoints_prizes +, (SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1) AS first_place_prize +, (SELECT SUM(prize_amount) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15) AS total_prize +, (SELECT SUM(prize_amount * number_of_submissions) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 14) AS checkpoint_total_prizes +, NVL(pp1.actual_start_time, pp1.scheduled_start_time) AS registration_start_date +, NVL(pp1.actual_end_time, pp1.scheduled_end_time) AS registration_end_date +, NVL(pp15.actual_end_time, pp15.scheduled_end_time) AS checkpoint_submission_end_date +, NVL(pp2.actual_end_time, pp2.scheduled_end_time) AS submission_end_date +, pstatus.name AS status +, CASE WHEN pidr.value = 'On' THEN + NVL((SELECT value::decimal FROM project_info pi_dr WHERE pi_dr.project_info_type_id = 30 AND pi_dr.project_id = p.project_id), (SELECT round(NVL(pi16.value::decimal, 0)) FROM project_info pi16 WHERE pi16.project_info_type_id = 16 AND pi16.project_id = p.project_id)) + ELSE NULL END AS digital_run_points +, pi1.value AS value_for_technology +, p.project_id AS id_for_platform +, CASE WHEN (pp1.phase_status_id = 2) THEN 'Yes' ELSE 'No' END AS registration_open FROM project p -, project_phase pp1 --registration phase -, project_phase pp2 --submission phase -, project_info pn -, project_info pi1 -, project_category_lu pcl -, OUTER project_info pi82 -WHERE p.project_id = pn.project_id -AND pn.project_info_type_id = 6 -AND pp1.project_id = p.project_id -AND pp1.phase_type_id = 1 --registration phase -AND pp2.project_id = p.project_id -AND pp2.phase_type_id = 2 --submission phase -AND p.project_category_id = pcl.project_category_id -AND p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) -AND pcl.project_type_id IN (@track@) -AND pp1.phase_status_id in (2,3) -AND pi1.project_id = p.project_id -AND pi1.project_info_type_id = 1 +INNER JOIN project_status_lu pstatus ON pstatus.project_status_id = p.project_status_id +INNER JOIN project_category_lu pcl ON pcl.project_category_id = p.project_category_id +INNER JOIN project_phase pp1 ON pp1.project_id = p.project_id AND pp1.phase_type_id = 1 +INNER JOIN project_phase pp2 ON pp2.project_id = p.project_id AND pp2.phase_type_id = 2 +INNER JOIN project_info pn ON pn.project_id = p.project_id AND pn.project_info_type_id = 6 +INNER JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 +LEFT JOIN project_phase pp15 ON pp15.project_id = p.project_id AND pp15.phase_type_id = 15 +LEFT JOIN project_info pidr ON pidr.project_id = p.project_id AND pidr.project_info_type_id = 26 +WHERE +p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) +AND pcl.project_category_id NOT IN (27, 37) AND pcl.project_type_id IN (@track@) and NVL(pp2.actual_end_time, pp2.scheduled_end_time) > '2012-01-01 00:00:00' -AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. --- start of parameters - AND NVL(pp2.actual_end_time, pp2.scheduled_end_time) BETWEEN TO_DATE('@submission_end_from@ 00:00:00', '%Y-%m-%d %H:%M:%S') AND TO_DATE('@submission_end_to@ 23:59:59', '%Y-%m-%d %H:%M:%S') - -AND LOWER(pn.value) LIKE ('@challenge_name@') +-- Filter out the challenge that user is not belong to. +AND pp1.phase_status_id in (2,3) +AND LOWER(pn.value) LIKE('@challenge_name@') +-- start of parameters AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) --- Filter out the challenge that user is not belong to. -AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) - or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id - AND ce.contest_id = p.project_id)) -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +ORDER BY @sort_column@ @sort_order@ +) From a807107e3adf29dbcfb54e0fa0da616d75571e24 Mon Sep 17 00:00:00 2001 From: TonyJ Date: Sat, 11 Mar 2017 09:25:31 -0500 Subject: [PATCH 23/67] fixed bug in task exclusion --- queries/get_active_challenges | 2 +- queries/get_open_challenges | 2 +- queries/get_past_challenges | 2 +- queries/get_upcoming_challenges | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/queries/get_active_challenges b/queries/get_active_challenges index 81779a980..cd17225db 100644 --- a/queries/get_active_challenges +++ b/queries/get_active_challenges @@ -70,5 +70,5 @@ AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'co AND pp1.phase_status_id IN (2, 3) AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ diff --git a/queries/get_open_challenges b/queries/get_open_challenges index b585f222a..7a581d5f9 100644 --- a/queries/get_open_challenges +++ b/queries/get_open_challenges @@ -102,6 +102,6 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_past_challenges b/queries/get_past_challenges index 9cdacad71..49ff02fcf 100644 --- a/queries/get_past_challenges +++ b/queries/get_past_challenges @@ -71,6 +71,6 @@ AND LOWER(pn.value) LIKE('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ ) diff --git a/queries/get_upcoming_challenges b/queries/get_upcoming_challenges index 0f026ebbb..585acf7a7 100644 --- a/queries/get_upcoming_challenges +++ b/queries/get_upcoming_challenges @@ -67,5 +67,5 @@ AND pp1.scheduled_start_time > CURRENT AND pp1.scheduled_start_time < CURRENT + 90 UNITS DAY AND pi1.project_info_type_id = 1 -- external reference id AND pi1.project_id = p.project_id -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks ORDER BY @sort_column@ @sort_order@ From 1d3c3649f2a9eccf80eb247340acdf7ab0d5772b Mon Sep 17 00:00:00 2001 From: TonyJ Date: Sat, 11 Mar 2017 09:34:15 -0500 Subject: [PATCH 24/67] fixed bug in task exclusion --- queries/get_past_challenges_count | 99 +++++++++---------------------- 1 file changed, 29 insertions(+), 70 deletions(-) diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index 9cdacad71..12b383a68 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -1,76 +1,35 @@ -SELECT is_studio -, challenge_type -, challenge_name -, challenge_id -, (SELECT value FROM project_info WHERE project_id = challenge_id AND project_info_type_id = 4) AS forum_id -, num_submissions -, num_registrants -, number_of_checkpoints_prizes -, first_place_prize -, total_prize -, checkpoint_total_prizes -, registration_start_date -, registration_end_date -, checkpoint_submission_end_date -, submission_end_date -, status -, digital_run_points -, nvl((SELECT MAX(event_id) FROM contest_project_xref x, contest c WHERE project_id = challenge_id AND c.contest_id = x.contest_id), 0) AS event_id -, (SELECT event_short_desc FROM event e WHERE e.event_id = nvl((SELECT MAX(event_id) FROM contest_project_xref x, contest c WHERE project_id = challenge_id AND c.contest_id = x.contest_id), 0)) AS event_name -, technology_list(value_for_technology) AS technologies -, platform_list(id_for_platform) AS platforms -, registration_open -, NVL((SELECT CAST('t' AS boolean) FROM contest_eligibility WHERE contest_id = challenge_id), CAST('f' AS boolean)) AS is_private -, (SELECT value FROM project_info pi53 WHERE project_id = challenge_id AND project_info_type_id = 53) AS submissions_viewable -FROM ( -SELECT -SKIP @first_row_index@ -FIRST @page_size@ - CASE WHEN (p.project_studio_spec_id IS NOT NULL) THEN 1 ELSE 0 END AS is_studio -, pcl.description AS challenge_type -, pn.value AS challenge_name -, p.project_id AS challenge_id -, (SELECT COUNT(*) FROM submission s1 INNER JOIN upload u1 ON s1.upload_id = u1.upload_id - WHERE u1.project_id = p.project_id - AND s1.submission_type_id = 1 - AND s1.submission_status_id <> 5) AS num_submissions -, (SELECT COUNT(*) FROM resource r WHERE r.project_id = p.project_id AND r.resource_role_id = 1) AS num_registrants -, NVL((SELECT SUM(pr.number_of_submissions) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 14), 0) AS number_of_checkpoints_prizes -, (SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1) AS first_place_prize -, (SELECT SUM(prize_amount) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15) AS total_prize -, (SELECT SUM(prize_amount * number_of_submissions) FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 14) AS checkpoint_total_prizes -, NVL(pp1.actual_start_time, pp1.scheduled_start_time) AS registration_start_date -, NVL(pp1.actual_end_time, pp1.scheduled_end_time) AS registration_end_date -, NVL(pp15.actual_end_time, pp15.scheduled_end_time) AS checkpoint_submission_end_date -, NVL(pp2.actual_end_time, pp2.scheduled_end_time) AS submission_end_date -, pstatus.name AS status -, CASE WHEN pidr.value = 'On' THEN - NVL((SELECT value::decimal FROM project_info pi_dr WHERE pi_dr.project_info_type_id = 30 AND pi_dr.project_id = p.project_id), (SELECT round(NVL(pi16.value::decimal, 0)) FROM project_info pi16 WHERE pi16.project_info_type_id = 16 AND pi16.project_id = p.project_id)) - ELSE NULL END AS digital_run_points -, pi1.value AS value_for_technology -, p.project_id AS id_for_platform -, CASE WHEN (pp1.phase_status_id = 2) THEN 'Yes' ELSE 'No' END AS registration_open +SELECT COUNT(*) AS total FROM project p -INNER JOIN project_status_lu pstatus ON pstatus.project_status_id = p.project_status_id -INNER JOIN project_category_lu pcl ON pcl.project_category_id = p.project_category_id -INNER JOIN project_phase pp1 ON pp1.project_id = p.project_id AND pp1.phase_type_id = 1 -INNER JOIN project_phase pp2 ON pp2.project_id = p.project_id AND pp2.phase_type_id = 2 -INNER JOIN project_info pn ON pn.project_id = p.project_id AND pn.project_info_type_id = 6 -INNER JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 -LEFT JOIN project_phase pp15 ON pp15.project_id = p.project_id AND pp15.phase_type_id = 15 -LEFT JOIN project_info pidr ON pidr.project_id = p.project_id AND pidr.project_info_type_id = 26 -WHERE -p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) -AND pcl.project_category_id NOT IN (27, 37) AND pcl.project_type_id IN (@track@) -and NVL(pp2.actual_end_time, pp2.scheduled_end_time) > '2012-01-01 00:00:00' -AND NVL(pp2.actual_end_time, pp2.scheduled_end_time) BETWEEN TO_DATE('@submission_end_from@ 00:00:00', '%Y-%m-%d %H:%M:%S') AND TO_DATE('@submission_end_to@ 23:59:59', '%Y-%m-%d %H:%M:%S') --- Filter out the challenge that user is not belong to. +, project_phase pp1 --registration phase +, project_phase pp2 --submission phase +, project_info pn +, project_info pi1 +, project_category_lu pcl +WHERE p.project_id = pn.project_id +AND pn.project_info_type_id = 6 +AND pp1.project_id = p.project_id +AND pp1.phase_type_id = 1 --registration phase +AND pp2.project_id = p.project_id +AND pp2.phase_type_id = 2 --submission phase +AND p.project_category_id = pcl.project_category_id +AND p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) +AND pcl.project_type_id IN (@track@) AND pp1.phase_status_id in (2,3) -AND LOWER(pn.value) LIKE('@challenge_name@') +AND pi1.project_id = p.project_id +AND pi1.project_info_type_id = 1 +and NVL(pp2.actual_end_time, pp2.scheduled_end_time) > '2012-01-01 00:00:00' +AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters + +AND NVL(pp2.actual_end_time, pp2.scheduled_end_time) BETWEEN TO_DATE('@submission_end_from@ 00:00:00', '%Y-%m-%d %H:%M:%S') AND TO_DATE('@submission_end_to@ 23:59:59', '%Y-%m-%d %H:%M:%S') + +AND LOWER(pn.value) LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82) -- exclude assigned tasks -ORDER BY @sort_column@ @sort_order@ -) +-- Filter out the challenge that user is not belong to. +AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) + or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x + where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + AND ce.contest_id = p.project_id)) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks \ No newline at end of file From 582855083377ec4dc065d40490369dc3458f3a15 Mon Sep 17 00:00:00 2001 From: TonyJ Date: Sat, 11 Mar 2017 09:35:09 -0500 Subject: [PATCH 25/67] fixed bug in task exclusion --- queries/get_open_challenges_count | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/queries/get_open_challenges_count b/queries/get_open_challenges_count index 5e26f7d47..52cc31b2d 100644 --- a/queries/get_open_challenges_count +++ b/queries/get_open_challenges_count @@ -5,7 +5,6 @@ FROM project p , project_info pn , project_category_lu pcl , project_info pi1 -, OUTER project_info pi82 WHERE 1=1 AND p.project_id = pn.project_id AND pn.project_info_type_id = 6 @@ -33,6 +32,4 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND NOT ( NVL(pi82.value,0) = 1 - AND (select count(*) from resource r where r.project_id = p.project_id and resource_role_id = 1 ) > 0 - ) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks From ea66e851110ee0c1ec6818bce2c7b2d2cb5233fe Mon Sep 17 00:00:00 2001 From: TonyJ Date: Mon, 13 Mar 2017 15:09:04 -0400 Subject: [PATCH 26/67] fixed bug in task exclusion --- queries/get_past_challenges_count | 2 -- 1 file changed, 2 deletions(-) diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index 12b383a68..88748347b 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -20,9 +20,7 @@ AND pi1.project_info_type_id = 1 and NVL(pp2.actual_end_time, pp2.scheduled_end_time) > '2012-01-01 00:00:00' AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters - AND NVL(pp2.actual_end_time, pp2.scheduled_end_time) BETWEEN TO_DATE('@submission_end_from@ 00:00:00', '%Y-%m-%d %H:%M:%S') AND TO_DATE('@submission_end_to@ 23:59:59', '%Y-%m-%d %H:%M:%S') - AND LOWER(pn.value) LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ From 80da5c86587faf331a362302eebdc12a184adbf3 Mon Sep 17 00:00:00 2001 From: cache Date: Tue, 18 Apr 2017 09:01:48 -0400 Subject: [PATCH 27/67] obtain user-id from sso provider info --- initializers/helper.js | 35 ++++++++++++++++++++++++++- initializers/middleware.js | 39 ++++++++++++++++++++++++++++-- queries/get_user_by_sso_login | 5 ++++ queries/get_user_by_sso_login.json | 5 ++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 queries/get_user_by_sso_login create mode 100644 queries/get_user_by_sso_login.json diff --git a/initializers/helper.js b/initializers/helper.js index 7170bd95d..e9961b888 100755 --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1214,9 +1214,23 @@ helper.socialProviders = { "twitter": 3, "github": 4, "salesforce": 5, - "ad": 50 + "dribbble": 10, + "behance": 11, + "stackoverflow": 12, + "linkedin": 13, + "bitbucket": 14, + "ad": 50, + "samlp": 102 }; +helper.isTCADProvider = function (providerId) { + return providerId === helper.socialProviders.ad; +} + +helper.isSSOProvider = function (providerId) { + return providerId === helper.socialProviders.samlp; +} + /** * Retrieve provider information from the provider name. * @@ -1240,15 +1254,34 @@ helper.getProviderId = function (provider, callback) { if (provider.startsWith("salesforce")) { providerId = helper.socialProviders.salesforce; } + if (provider.startsWith("dribbble")) { + providerId = helper.socialProviders.dribbble; + } + if (provider.startsWith("behance")) { + providerId = helper.socialProviders.behance; + } + if (provider.startsWith("stackoverflow")) { + providerId = helper.socialProviders.stackoverflow; + } + if (provider.startsWith("linkedin")) { + providerId = helper.socialProviders.linkedin; + } + if (provider.startsWith("bitbucket")) { + providerId = helper.socialProviders.bitbucket; + } if (provider.startsWith("ad") || provider.startsWith("auth0")) { providerId = helper.socialProviders.ad; } + if (provider.startsWith("samlp")) { + providerId = helper.socialProviders.samlp; + } if (providerId) { callback(null, providerId); } else { callback(new Error('Social provider: ' + provider + ' is not defined in config')); } }; + /* Encrypt the password using the specified key. After being * encrypted with a Blowfish key, the encrypted byte array is * then encoded with a base 64 encoding, resulting in the String diff --git a/initializers/middleware.js b/initializers/middleware.js index e41ad3b63..cf370e589 100644 --- a/initializers/middleware.js +++ b/initializers/middleware.js @@ -76,12 +76,14 @@ exports.middleware = function (api, next) { var authHeader = connection.rawConnection.req.headers.authorization, connectionMap = { "common_oltp": api.dataAccess.createConnection("common_oltp") }, isTopcoderAD, + isSSO, cachePrefix = "authorizationPreProcessor::", decoded, isCachedReturned, cacheKey, socialUserId, socialProvider, + authConnection, cookieToken = api.utils.parseCookies(connection.rawConnection.req)[process.env.JWT_TOKEN_COOKIE_KEY]; if (_.isUndefined(authHeader) && _.isUndefined(cookieToken)) { @@ -113,8 +115,10 @@ exports.middleware = function (api, next) { cb(new IllegalArgumentError('Malformed Auth header. No sub in token!')); return; } + // connection name + authConnection = getAuth0Connection(decoded); var split = decoded.sub.split("|"); - if (split.length === 1) { + if (split.length === 1) { // token.sub should contain "|" cb(new IllegalArgumentError('Malformed Auth header. token.sub is in bad format!')); return; @@ -136,7 +140,8 @@ exports.middleware = function (api, next) { } api.helper.getProviderId(socialProvider, cb); }, function (providerId, cb) { - isTopcoderAD = providerId === api.helper.socialProviders.ad; + isTopcoderAD = api.helper.isTCADProvider(providerId); + isSSO = api.helper.isSSOProvider(providerId); cacheKey = cachePrefix + decoded.sub; api.cache.load(cacheKey, function (err, value) { var userId; @@ -155,6 +160,16 @@ exports.middleware = function (api, next) { cbx(api.helper.checkPositiveInteger(userId, "userId")); return; } + if (isSSO) { + api.dataAccess.executeQuery("get_user_by_sso_login", + { + sso_user_id: socialUserId, + auth_connection: authConnection, + }, + connectionMap, + cbx); + return; + } api.dataAccess.executeQuery("get_user_by_social_login", { social_user_id: socialUserId, @@ -251,6 +266,26 @@ exports.middleware = function (api, next) { } /*jslint */ + /** + * Extract Auth0 connection name from JWT token. + * JWT Example: + * .... + * "identities": [ + * { + * "connection": "sfdc-aspdev", + * "isSocial": false, + * "provider": "samlp", + * "user_id": "user1@asp.appirio.com.dev" + * } + * ] + * sfdc-aspdev is returned. + * + * @param {Object} decoded JWT token issued by Auth0 (v2 token) + */ + function getAuth0Connection(jwt) { + return (jwt && jwt.identities && Array.isArray(jwt.identities) && jwt.identities.length>0 ) ? jwt.identities[0].connection : null; + } + /** * The pre-processor that checks if user is slamming. * diff --git a/queries/get_user_by_sso_login b/queries/get_user_by_sso_login new file mode 100644 index 000000000..1f2257a3e --- /dev/null +++ b/queries/get_user_by_sso_login @@ -0,0 +1,5 @@ +SELECT s.user_id +FROM user_sso_login s +JOIN sso_login_provider p ON s.provider_id = p.sso_login_provider_id +WHERE p.name = '@auth_connection@' +AND s.sso_user_id = '@sso_user_id@' diff --git a/queries/get_user_by_sso_login.json b/queries/get_user_by_sso_login.json new file mode 100644 index 000000000..c73c00aec --- /dev/null +++ b/queries/get_user_by_sso_login.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_by_sso_login", + "db" : "common_oltp", + "sqlfile" : "get_user_by_sso_login" +} From d2386314a85aeaed67a6afb45b09b9b059034f07 Mon Sep 17 00:00:00 2001 From: ajefts Date: Fri, 28 Apr 2017 11:25:13 -0400 Subject: [PATCH 28/67] removed comment to fix syntax error --- queries/get_past_challenges_count | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index 88748347b..0adb68d0f 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -30,4 +30,4 @@ AND (not exists (select contest_id from contest_eligibility where contest_id = p or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks \ No newline at end of file +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) From 71d7a21142a02a81f3b2f85e30bbe4a54710d712 Mon Sep 17 00:00:00 2001 From: ajefts Date: Fri, 28 Apr 2017 11:25:48 -0400 Subject: [PATCH 29/67] removed comment to fix syntax error --- queries/get_open_challenges_count | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/queries/get_open_challenges_count b/queries/get_open_challenges_count index 52cc31b2d..863788407 100644 --- a/queries/get_open_challenges_count +++ b/queries/get_open_challenges_count @@ -32,4 +32,5 @@ AND pn.value LIKE ('@challenge_name@') AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) >= @prize_lower_bound@ AND NVL((SELECT pr.prize_amount FROM prize pr WHERE pr.project_id = p.project_id AND pr.prize_type_id = 15 AND pr.place = 1), 0) <= @prize_upper_bound@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @project_id@) -AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) + From 0ab234a61c41677af5b8a972b230ac75a7e9824a Mon Sep 17 00:00:00 2001 From: Vyacheslav V Sokolov Date: Mon, 19 Jun 2017 21:52:23 +0700 Subject: [PATCH 30/67] Improve challenge visibility control (#501) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. --- actions/challengeRegistration.js | 39 +- actions/challenges.js | 38 +- db_scripts/test_eligibility.delete.sql | 39 ++ db_scripts/test_eligibility.insert.sql | 219 ++++++++++ ...e-Improve Challenge Visibility Control.doc | Bin 0 -> 52736 bytes initializers/challengeHelper.js | 77 +++- initializers/middleware.js | 7 +- initializers/v3client.js | 143 +++++++ package.json | 2 + queries/challenge_registration_validations | 10 - .../get_challenge_accessibility_and_groups | 21 + ...et_challenge_accessibility_and_groups.json | 5 + ...Visibility_Control.postman_collection.json | 386 ++++++++++++++++++ ...isibility_Control.postman_environment.json | 34 ++ test/scripts/mock_v3.js | 73 ++++ 15 files changed, 1037 insertions(+), 56 deletions(-) create mode 100644 db_scripts/test_eligibility.delete.sql create mode 100644 db_scripts/test_eligibility.insert.sql create mode 100644 docs/Verification_Guide-Improve Challenge Visibility Control.doc create mode 100644 initializers/v3client.js create mode 100644 queries/get_challenge_accessibility_and_groups create mode 100644 queries/get_challenge_accessibility_and_groups.json create mode 100644 test/postman/New_Challenge_Visibility_Control.postman_collection.json create mode 100644 test/postman/New_Challenge_Visibility_Control.postman_environment.json create mode 100644 test/scripts/mock_v3.js diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index f50077b66..9424951ef 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -3,8 +3,8 @@ * * The APIs to register a challenge (studio category or software category) for the current logged-in user. * - * @version 1.7 - * @author ecnu_haozi, xjtufreeman, bugbuka, flytoj2ee, muzehyun + * @version 1.8 + * @author ecnu_haozi, xjtufreeman, bugbuka, flytoj2ee, muzehyun, GFalcon * * changes in 1.1: * Combine Challenge Registration API(BUGR-11058) @@ -27,6 +27,9 @@ * * changes in 1.7: * Avoid reliability info set if there is none for new user. + * + * changes in 1.8: + * Added the verification of the challenge's eligibility */ "use strict"; @@ -880,19 +883,31 @@ exports.registerChallenge = { } else { api.helper.checkUserActivated(connection.caller.handle, api, connection.dbConnectionMap, function (err, inactive) { var fail = err || inactive; - if (fail) cb(fail); - else api.dataAccess.executeQuery('check_challenge_exists', {challengeId: challengeId}, connection.dbConnectionMap, cb); + if (fail) { + cb(fail); + } else { + api.dataAccess.executeQuery('check_challenge_exists', {challengeId: challengeId}, connection.dbConnectionMap, cb); + } }, "You must activate your account in order to participate. Please check your e-mail in order to complete the activation process, or contact support@topcoder.com if you did not receive an e-mail."); } - }, function (result, cb) { - if (result.length > 0) { - if (result[0].is_studio) { - registerStudioChallengeAction(api, connection, next); - } else { - registerSoftwareChallengeAction(api, connection, next); - } - } else { + }, function(result, cb) { + // If the challenge is not found in the tcs_catalog:project table, + if (result.length === 0) { + // Do nothing, do not register cb(); + return; + } + var isStudio = result[0].isStudio !== 0; + api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, function (err) { + cb(err, isStudio); + }); + }, function (isStudio, cb) { + if (_.isUndefined(isStudio)) { + cb(); + } else if (isStudio) { + registerStudioChallengeAction(api, connection, next); + } else { + registerSoftwareChallengeAction(api, connection, next); } } ], function (err) { diff --git a/actions/challenges.js b/actions/challenges.js index 0c40bbf2b..53266e7a8 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -1,9 +1,9 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.31 + * @version 1.32 * @author Sky_, mekanizumu, TCSASSEMBLER, freegod, Ghost_141, kurtrips, xjtufreeman, ecnu_haozi, hesibo, LazyChild, - * @author isv, muzehyun, bugbuka + * @author isv, muzehyun, bugbuka, GFalcon * @changes from 1.0 * merged with Member Registration API * changes in 1.1: @@ -79,9 +79,12 @@ * - Update challenge type filter. * Changes in 1.31: * - Remove screeningScorecardId and reviewScorecardId from search challenges api. + * Changes in 1.32: + * - validateChallenge function now checks if an user belongs to a group via + * user_group_xref for old challenges and by calling V3 API for new ones. */ "use strict"; -/*jslint stupid: true, unparam: true, continue: true */ +/*jslint stupid: true, unparam: true, continue: true, nomen: true */ require('datejs'); var fs = require('fs'); @@ -851,7 +854,7 @@ var addFilter = function (sql, filter, isMyChallenges, helper, caller) { * @since 1.10 */ function validateChallenge(api, connection, dbConnectionMap, challengeId, isStudio, callback) { - var error, sqlParams, helper = api.helper; + var error, sqlParams, helper = api.helper, userId = (connection.caller.userId || 0); async.waterfall([ function (cb) { error = helper.checkPositiveInteger(challengeId, 'challengeId') || @@ -862,31 +865,18 @@ function validateChallenge(api, connection, dbConnectionMap, challengeId, isStud } sqlParams = { challengeId: challengeId, - user_id: connection.caller.userId || 0 + user_id: userId }; - async.parallel({ - accessibility: function (cbx) { - api.dataAccess.executeQuery('check_user_challenge_accessibility', sqlParams, dbConnectionMap, cbx); - }, - exists: function (cbx) { - api.dataAccess.executeQuery('check_challenge_exists', sqlParams, dbConnectionMap, cbx); - } - }, cb); + api.dataAccess.executeQuery('check_challenge_exists', sqlParams, dbConnectionMap, cb); }, function (res, cb) { - if (res.exists.length === 0 || Boolean(res.exists[0].is_studio) !== isStudio) { + // If the record with this callengeId doesn't exist in 'project' table + // or there's a studio/software mismatch + if (res.length === 0 || Boolean(res[0].is_studio) !== isStudio) { cb(new NotFoundError("Challenge not found.")); return; } - var access = res.accessibility[0]; - if (access.is_private && !access.has_access && connection.caller.accessLevel !== "admin") { - if (connection.caller.accessLevel === "anon") { - cb(new UnauthorizedError()); - } else { - cb(new ForbiddenError()); - } - return; - } - cb(); + // Check the eligibility + api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cb); } ], callback); } diff --git a/db_scripts/test_eligibility.delete.sql b/db_scripts/test_eligibility.delete.sql new file mode 100644 index 000000000..5f77f6c44 --- /dev/null +++ b/db_scripts/test_eligibility.delete.sql @@ -0,0 +1,39 @@ +DATABASE common_oltp; + +DELETE FROM user_group_xref WHERE group_id > 3330000 AND group_id < 3330100; +DELETE FROM security_groups WHERE group_id > 3330000 AND group_id < 3330100; +DELETE FROM group_contest_eligibility WHERE contest_eligibility_id > 1110000 AND contest_eligibility_id < 1110100; +DELETE FROM contest_eligibility WHERE contest_eligibility_id > 1110000 AND contest_eligibility_id < 1110100; + +DATABASE informixoltp; + +-- UPDATE coder SET comp_country_code = NULL WHERE user_id = 132458; + +DATABASE tcs_catalog; + +DELETE FROM notification WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project_result WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project_user_audit WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM component_inquiry WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE project_id > 1110000 AND project_id < 1110100); +DELETE FROM resource WHERE project_id > 1110000 AND project_id < 1110100; + +DELETE FROM project_info WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM comp_versions WHERE component_id = 3330333; +DELETE FROM comp_catalog WHERE component_id = 3330333; +DELETE FROM project_phase WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project WHERE project_id > 1110000 AND project_id < 1110100; + +DELETE FROM review_item_comment WHERE review_item_comment_id > 7770000 AND review_item_id < 7770100; +DELETE FROM review_item WHERE review_item_id > 5550000 AND review_item_id < 5550100; +DELETE FROM review WHERE review_id > 4440000 AND review_id < 4440100; +DELETE FROM scorecard_question WHERE scorecard_question_id = 3330333; +DELETE FROM scorecard_section WHERE scorecard_section_id = 3330333; +DELETE FROM scorecard_group WHERE scorecard_group_id = 3330333; +DELETE FROM scorecard WHERE scorecard_id = 3330333; +DELETE FROM submission WHERE submission_id > 2220000 AND submission_id < 2220100; +DELETE FROM prize WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM upload WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM resource WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM project_phase WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM project WHERE project_id > 2220000 AND project_id < 2220100; diff --git a/db_scripts/test_eligibility.insert.sql b/db_scripts/test_eligibility.insert.sql new file mode 100644 index 000000000..8bb746502 --- /dev/null +++ b/db_scripts/test_eligibility.insert.sql @@ -0,0 +1,219 @@ +DATABASE tcs_catalog; + +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220001, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220002, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220003, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220004, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220005, 1, 14, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770001, 2220001, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770002, 2220002, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770003, 2220003, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770004, 2220004, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770005, 2220005, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880001, 20, 2220001, 7770001, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880002, 20, 2220002, 7770002, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880003, 20, 2220003, 7770003, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880004, 20, 2220004, 7770004, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880005, 20, 2220005, 7770005, 132456, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990001, 2220001, 8880001, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990002, 2220002, 8880002, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990003, 2220003, 8880003, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990004, 2220004, 8880004, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990005, 2220005, 8880005, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 2220001, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 2220002, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 2220003, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 2220004, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 2220005, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220001, 9990001, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110001); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220002, 9990002, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110002); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220003, 9990003, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110003); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220004, 9990004, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110004); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220005, 9990005, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110005); + +INSERT INTO scorecard (scorecard_id, scorecard_status_id, scorecard_type_id, project_category_id, name, version, min_score, max_score, create_user, create_date, modify_user, modify_date, version_number) + VALUES (3330333, 1, 7, 14, "---", "---", 0, 100, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_group (scorecard_group_id, scorecard_id, name, weight, sort, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 3330333, "---", 100, 1, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_section (scorecard_section_id, scorecard_group_id, name, weight, sort, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 3330333, "---", 100, 1, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_question (scorecard_question_id, scorecard_question_type_id, scorecard_section_id, description, weight, sort, upload_document, upload_document_required, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 1, 3330333, '---', 100, 1, 0, 0, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440001, 8880001, 2220001, 7770001, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440002, 8880002, 2220002, 7770002, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440003, 8880003, 2220003, 7770003, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440004, 8880004, 2220004, 7770004, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440005, 8880005, 2220005, 7770005, 3330333, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550001, 4440001, 3330333, 9990001, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550002, 4440002, 3330333, 9990002, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550003, 4440003, 3330333, 9990003, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550004, 4440004, 3330333, 9990004, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550005, 4440005, 3330333, 9990005, "---", 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770001, 8880001, 5550001, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770002, 8880002, 5550002, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770003, 8880003, 5550003, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770004, 8880004, 5550004, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770005, 8880005, 5550005, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 1, 14, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220001, 1110001, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220002, 1110002, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220003, 1110003, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220004, 1110004, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220005, 1110005, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO comp_catalog (component_id, current_version, component_name, status_id, modify_date, public_ind) + VALUES (3330333, 1, "---", 1, CURRENT, 0); + +INSERT INTO comp_versions (comp_vers_id, component_id, version, version_text, phase_id, phase_time, price, modify_date) + VALUES (4440444, 3330333, 1, "1", 113, CURRENT, 1000, CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 2, "3330333", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 6, 3330333, "Not private", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 6, 3330333, "Old logic - access allowed", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 6, 3330333, "Old logic - access denied", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 6, 3330333, "New logic - access allowed", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 6, 3330333, "New logic - access denied", CURRENT, "132456", CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 79, "---", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330001, 1110001, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330002, 1110002, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330003, 1110003, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330004, 1110004, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330005, 1110005, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); + +DATABASE informixoltp; + +UPDATE coder SET comp_country_code = ( + SELECT MIN(country_code) FROM country WHERE country_name = "United States" +) WHERE coder_id = 132458; + +DATABASE common_oltp; + +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110002, 2220002, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110003, 2220003, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110004, 2220004, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110005, 2220005, 0); + +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330001, "Eligibility - Old logic - with user", 0); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330002, "Eligibility - Old logic - no users", 0); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330003, "Eligibility - New logic - with user", 1); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330004, "Eligibility - New logic - no users", 1); + +INSERT INTO user_group_xref (user_group_id, login_id, group_id) VALUES (5550001, 132458, 3330001); + +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110002, 3330001); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110003, 3330002); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110004, 3330003); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110005, 3330004); + +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110012, 1110002, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110013, 1110003, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110014, 1110004, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110015, 1110005, 0); + +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110012, 3330001); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110013, 3330002); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110014, 3330003); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110015, 3330004); diff --git a/docs/Verification_Guide-Improve Challenge Visibility Control.doc b/docs/Verification_Guide-Improve Challenge Visibility Control.doc new file mode 100644 index 0000000000000000000000000000000000000000..1c2913aaec56e9f7acc508b92ae08a944ff0a3e0 GIT binary patch literal 52736 zcmeHw2|!iF_y0V2z+)8<5e@Z$gh(!+xF-SbxZ#4Dnu>sm3%KA~DH>UtnVNgK=2B*A zrj};qQd*k(8k)P7nu%pLE9OpE5GV(f2PyzYAP5Ks zn0^TMPXeJpB_Ir_41@zu0abvi0K*ayIF1Br05yRqpcYUYr~}jm>H+nE2EfxmLm(P> z28aP-fjFQM&=_a}GzFRg&4Cs`JkSzo1+)g*0BwN;pdHX2=m2yC5`j)YXP^u4EYKC` z26P9W19|{GfnGoo&>LWRQm{`o9pm)hvb7f(_{&6HG!g084;7=4gW_-gkQi-7ixZP& zEl#-NhWRIbGY5*pGcEshh`@6~k7HZypZD1Qx&LST-{OD{wdx23&r&(NIw<0^P5Bi~ zslQvNB@fLQmeD8E*g0cpO4Y_01BMSuP0v*Ngw`E+t#L-O(i8Bw8cvk^)#{4d_0|uO zgo^rw{K-U%3RJ|sJZKV~4HA^=b1yIY<@dJzyx*U05KmS5=sWfo-Eo6`81D}TnD{8b z5qKA18`uW09UKMN7v2WgA2JgAL_h4=FB-9D-&h5EwvW2lvyW_yJ<~|Qp7gIikbNHe zwAjIe2c{(_Wu|4M8{4N28J;#YmHZrL>@YYrIjxWBmJH86_UVM78U0g}GmUMM2Bo6o zV&BQMJ7x@Sl#!A;6o<++_PHHUO(T_ zt`jk#!Yx#|H`S>-&s|PJw@xQ|WUtdbmyJ#|J0Y*+vQffs+0()~MCYYspq4?$w7MH~ zU4(Nm5pJ3gs+4C}-(q z&a?g~pzKT@fjM1-%R_Qb8?ZWBZcM1NTd1?7uj~~1-cF$J?Ih{v-Y=lfoEsjJ^STOU z11P(=g}O+}%1)uYn3NZrDEBf`R&zd3xGMD7o^01Ct=8&j=P{uMw@`zmuj~~15ze3= z;Vg50@b`k8Q-XY@1VQ|Lr39%B9~47HUWH%sZz=Y*D=gH`u#nq`;b_~DVvL}61k2H6 zt101hI*tb{!__c_^FVWVMsr6-02$uLM);aVgb%V4o)$gn8$ciO4@mlvHo`|0K|j_` zczg8Q*$7|D27OnI7s$sXT^w>+1|x{xVi>ezCc;+5I3N?p>Cg^I(3&Z@lMF34RHTVM z%AKM3aGDeQ6j zLp$NAiCOcmsCie^yemd*EcJFBM{fQYpQVC}Ns!oq;Gz-ZFY-7^^i_TbgU?2c-7=JM z40G2*iG4^%4dew5rXp+#Y9bvm*uxkx>g%V_;25%Pyc^PWLmM&{bYSR5S~k}S%YMuS z>eMWUlEN(=N05a}P}3rXYMKbKK5Oj_X;_W{y=!H*=#fVSkg3N^-E>57=wnL+pev+`6R>9B(5$`we^Hr`rh6 ze!yP%B{ss>L3s9ew)(LT?S!{i-tBh6+sn@(8{z8}k)QK+!rRNwEj!^0=O;puNSz}& z;bvtIMA*!cjNxN9M&Ufk7(sSp*vnO<%^0ekVp(r8jaa)e?B%MR-5B!Q3u9n!1VJ}x7+Kpi^SKDpI zD5}*SvKzx*uFl(yVJ}y=Y{t;G+CuHasc`$KfjJoab?##~hP`qG*^ObZ9FaC-*zWgD z?Za*id%0?7H-^1jCE1Kolr9-!H-^1jjkg=aR<0_0|B^pliQy&6!T0vGzLq`){w4$k(I7P!|D1p(|aB)_oD>F!`*pI;&D@_^k@{A#xhwJEY zqFaGZEzG?oBE=6)@dH3VPD!yLe&h5Q)9Pc9S_Ak1Bao&yJc|y4;U&x$(cwB0X(c)# z{&uHUpzbd!Ajh$Y*+SnAlo}#VTjaea^8AcKwJp_PO&53_+$LJfrz=bgw^AI$lsViamFe>(Uu41a@hbvSs@ z7oj-A$bRmYQ)3+aV*EZF6lfWvRa31dnG?U&h0_T6TXY#@MUBo=b6Y)s@+k8 zc|=lp)DU3ujs!W=JK|n2>Lz!zmbB^-SXmnD_7VFH}SRZgc19C}U z7r9FBL2Hcs2V3MR1ibPDr#&&s^)47En)&4k5%v}d3f*?j383q%_#2ovwGBK@EwoZ7 zCOK24A@Mfq)6$nvH1Fg%bBHs?^`&i6Yk}OR6!76uXiJ`sH}NzM92|&0)=oM&X-oc! z$5ZL&neI$KkCzBUJC1<{peAam=%Q!P3S$d&Lfd#R)RGt4gBKKw7v|^lD(J*|XL__; zZOkT6w)+d|CLP5|0<27E8(#u6N+|w}1k`GecpubmAmlX;zS;GrwpAXfHdJOM_*)No zp|+}zt8vf5+2C8FLE3}A8(KNl8RdaqkoR0b>f1v z9H&=Q_*4fpnAZmSR-od9`eAJ9@REua;_ORfCt8`E68Nblpe{w(T&PRk?dVdLVn{(Ld_{~B zC)gzhaa`cZV{4}FqAZecL+z#NtENh?iB8mTp`XVM?b_O#$lL^?RE{BzNgS47O~<_OC)co)L~b0gXKu=4o~;4$Qp2K;qa{&Z zj1bD?!n6>F9@$u@f~l!q*cVJLfZu)!rI;cpkz@X1q=phF=_Dogex(!&jfcU>i2kow|zV<_DjIp9YTL`+sg|?6jDAF&@elt@UOO_MQ3uWrk z0nyYsOLe|4ArEn+1n)3Our@yVSW3Jg<8y{holTmX2Mre&?n;@R+MtR}+mp(Lw&n|=Db6~bNRStt zb(rB)LbNl7fExFM7B2-YUP|e&dtlH0xsAdT2SXdsc2jzJdi>ctsdf!zG|NJ_m0WgC z#|dO=S0i;Cz5XUIKl>)=CIkB>TUvsx$6t&uphsO%a+%Z*Y=4X$Y+(Dd6`S%NEGAgQ z?vZ`pGw{LM^4b!e42JoW-=K6G+WVWJC12n5L+40rQ2W z9~zVM#a`lN#59`pkc=7d(3qSL_7W2j^GA!AWgZ%nGreBo6~tU;nlP4WmVIbU^1w^H zikPDcc+PS@@z9u@qxBM#5HrR!70WbbeJ@pN$sIbOux-w^?Ik87o~jI}#|p+dQ2Q9_ zK+9~az z!kp6fZAQhv?~y5_CprnFpcRyYnx^GZVsO7cefl6-S~R{~_p0a`SGJ=Prrsdc)F)8`lAoOK zZ)ojfnEqSQEOun${o3D3tP{KcBY-p<=Ws`vbRKIO7TPA zfld#+3lD+3(3;^wA?iR@7FQK3eQ52T(;*1zWLO{u6s56V$w!|FNRLGzDd$!A(7Cs3 z6{PQhwZ2KcWE%4QqvChzNZo>=M

Z?I$8{HTO?5!~Uabcy2{RN)@UfGN8nByc!7{Cne6Ur_ zLz>){0;S3bF&n^*KD@@&fqGG2b3C%(nD>j?LUEIL9(WNL_ZS$DI9z)7HI~(lDlNn( z6|uUmx)5t1>fu=0T^_e907f7P2nG;DiE{<<{IO)NJP-=3gE`ZwB+QQhghRyaFj!)s zH4RA#!7I4Shnc5x+X{2T8K7Aq8?}B=;&}MEP6Z^1cCCHQ>78E`~2UEEvo^h#TI* zkP8|zGN_agj=7)UyoAu_l3_aGno9!erpwCDCme*E5|1f6;U>dpVIQZH*Rf8JHj~&X z+;f}bu(5JsoK6Q~Umz#mVw-OE?Ac;Fj?vBO($dnzUL8gZ;yBvB5GR!1e3Z{1oVe;~ zsQaGKQd}uxsUvy41}vMN=qGC7SqaX|)CHpOtVGeVA`!MH!f{_$iCGWNRn&Y)teT+P z6Did}dUX(s=|tf8VQED@Mp{gXxnpUVN2dDlv}&RhkDf!O^61=P`E7Cj30$>bUy&%u zQ7t8oSpjm-eon4$^L{|nd&#Y-axX;i#m* zRPsw$|Cu$N^__gEO6G{Lbx)y(d!~&AP&Pdj6h+ap;f2(43Iv#7J7*tYXB# z*zo-r zsV7v4(GsvNE9SK!tZ7P1QL(LMMH^enT2ZmBWknm?@u6v3%ZfI({-Lq0Wknm?>7lW$ zWknmCE7FVRnzgKGV{`p`(Xp*%MH`!|)!AQ23QE-}Kh`qOFxrhHEb}Jsx>&5qtAXB7 zyJo+p$d4ffT7}#kptF>X7ZPN@sXT()%%8)Ihi`O{a$q}57~z~rg&W@pLo!2ggrp;S zEzq$phApZc;A|fRM|1`p4R|m&w0Nm&-5+U4N#|pWw&gXNFmm!rI#T2`WC!G3F1Qzp z)3>NHHDm5*X*^`p=8k6dK*gOw=S?yk0(jimxp<}IG}S;Q*UX{hX>lsYlBXS5qiS^* zk@JVusQEq~h>j|52{Pd1WA5d2yp$9?U=0owM>;wqCYM8~3-O1Md&QJca*vWF9c74D zOxd#LdQa9CZOMmhZ84?D*r5HsZAiT*=Lw+*@Ad6>gOrK^&Wm$)kU^ z$Kil)#N)&&I*ygbH?l7C@woFtwnxrMmWE({KIS@wXa}*!NyW0oQiz#>IGM1Ek`$`6j%=qw zivP$q2dOsxizyopesgW~?_2i! zOqn27iI(rVQ!1!Zo&bt+Z`pIe2~3Fm2eW0*d-h+OEgz`HzgmgHRwZ&Aa?Yx97vow# zG=oft_b|L$r#k-nqA9Uk;QG;O5NHk>szcD(IN1c*EUO{}n^iyD$;7^@Dcm63OIPj= z$92AIK+b{=Q^Lt^zB=e7AvL)Mv^ts;S97x23`Hm!l$?L&TMF0}3Gof^Z(~T8zl|Ye zSQ-j662Aig%yaxZ3)03$LCAj^Y1Ne4Ov9cAD{G>fQd@(ST9hizT=OH~JLY_t5D#E* z(trH9cQ32H0yDd=&+4u?{%AcER^n-YINw6`xL$92VraY)c!Ki#lJc9a{OV)TE(BK( zVR)nbj@My~EhZ?(FDbuYz#{g>tcx5kdn@xT2%gnmxa2lO-kqE-pLl>-M!Xp+IV-4A zP;4`-0>E`8GB0CW+_YS-53X5<%+EiWe@=MEH*L{W=wK%I!k@@Lg-*fE#Ra}OH-o|8 z?&jw172xgV>FHIjl)q0v#R^Y`1Xlxx0ILdIfrW2Q~-`4r}npTmClTV}Of)siQ7fIQZxseRTO>avD|VgxoU2 zrcLMIsCRO9F}S+9Bfv&);h=MLbkI9GIqCHX{UYuQy^oV`P-Lug$#zLD!9)CNjGw;5 zP%-Yq?WNiu$_=U6d*}pLH~-QBWy(GoS}Ck@cvP+0b?Vlu->7kurp=nSi0{xbu~X+R z&vs2tN$u0OUt0fRnZrkn95s5(OW7|^eC5?ilV`j+bJpxRbLY)p`p&ZDD^|Yy-m3K< zZP>W!<4-nk*|GD>uXgSJ`kOt6j~qRA{KU!ca?W44cgE_zMXmJgFziGES$6KTAkW8f4slUznkrzZ?}`JR_9_w_3=`QnY8i2OmvTeCi&H|*BO z@h^4hHsZrUbw^J)m$e{u*RY$D#qCA+T4a{bIkO;NjBl4OUhn!=!bh7nmF`?Rp;7vf zNh#C918-lt)nnL&?@OG&UEcRt=#j;TzRKykIs2>d(wvPY% z7d>6~#`*n|>dbSvv0zP1^;v&>nY?z)vBO*Iy?3wgdH;7-UK`)|t8d5qeC*^j=g;sy zH~jalyL~MF;7i|a`20@#8z7^q zctpc`t8ePBcWu4$8H$66Qswi2x(r;^A+H5#<^M#RX)~w%jqwMC&KYY?|!O4q@;-4O}a#a5V zaV;{lKkD{I!`O(g-3M(yFmAcmhRxkKIL&E4Jh{o{=azr4;aJbjKhCLhXZG`Bj}7;D z;`5vHjNS8w`;Gbht*5eIOV~J~p~JV!79IbjZQ!ey=j@C-H}UmXZq`|7_`T-*L&t6} za*SPmI=V``?35)FGZu}gyUpdz`70B@Up~m=oe3*{y86xLI#2zycF@2P9ZokIRA%<* z_F=m_j}RrkJl6BZzDju$Ml8DfVBD^)cW-o_)6=zD^vV5ygm#=X_m>W1F5W*9RW3TG z-%mOBcgECS{m#B=2R02%e(#A77wuSis`lZQOV-{QSkX0pXin?2%gKko*ihy3eRtMw z+^IiyOYm#WPv-|YQ5_YO4)oc{TaF7x-)?l9uF)bE?c z9Nw5Rsru@}?F?V`x-_W#$@__Q|15R>>$x?qwo4f^W84RR3Fn_Uv%TzZ$>kG6=e)Ok z*z%L#pM1jg#`F(ft*~X6OW>W=U)+n`8a3`p=?V{SpS{?`?{x6pxR=MQ>y$me$MUao z&rF-&?doeOi&93MIpo{?z}l;wPPE)-Zs2|rsgncdy^cUHIKWsM2>3vet3ToH>5&i9LFx+wRyqI3g4V+h0lR>R?opaJ9Eq z_qWR3E&Y}LGxX3!Z?vxZAc(bf^$}L5#NqmQC3^9#tD^0y-mt6Uzl2rhT=vD!u99@G zUMWFy6?b+ht``^`yp>))w>+*{hs>Xp(59KETUibsTQ^e8c_EBrYe=4Qt;lPORA<$V83F&D+CSH3sXtva*G^k4srU3NbuFh-bc8ln(Da)D(TFZuG>+{!D(9yM zBd3+?Jm#GH!Z&{&ld>{I7d!uM6vrgS|#U|;hkqkTS#e1GA* zlk@8MB*WU;5&~jf{$JFUKu;I(hJ|Q7?RYDrQMS z*v#FYyVqBVHB`*@4mJdJU2(bZ>a90C7pHa|`)BF|@0HiLeCRsw^A$L4y64mxf%}6h z56hlgr+t{8+vV-s?(R8WX}Ruh^KlcCyn_tMORvvOG6W@5-gUBjQ-e=juZlt0NdfO< z84kH7MT8oH;v#m{zwum{-<~6{t+{TjJtw;4gfh-gRH(D}xg}e2?#%ilc}nMol@AS1 z$hc4{ao)0Lf|kcSMR%>@|6ccrC5HvB{JKW>Cw4~6Ully1-qa3>vo6-1dNF?ajBUSe zHTu2QWMb0lP6OWS-lxpO)MpbDtNQP~e_`Bv1ICXiIpOCU$(^1_`KV#lUH2BoFBx^S z>hm7o*X_}%&XK9pJa&R)iy56}ePx_* zuxsaLhS*oPcd7Jc{}1cFy}x_y_tWMFy<-S^`apvP`)AFXvp2P!-@a3^Yq}1qJv*sm z>yk};s=n4KYtW!UUm4Q2q<{2X-p+CNN}SER^KrxS2R=A|e%#%R{2O2VIVZoyqP<^T zyp@0Niw7BJAN)M>okKsqmGSxS3(n@te=wyfECd&j-^YTtg#XX(4)&n}!F(|6R!@Rz*fnpbLkVuQ<= zvL5ZG)($;#KD}Zqljiw<-SpA~-jmp$Ld{P_N=RnX%ky`CUY>Snas{`F%dTxXa^9eS>Bx~I z4I4JxvS#Ixv?)WP7o9zFqH`7hlD9rtaeZl6TwlYl#`nMKy|L?&c^V+w9y;f$$%9YJlckI)*@92e{6E}YF*@D#X`h_@rfn{ z$7d&Y3qaHH*cp@G*r2-{aCG(c`jHPN_j~;Z=Zeev%|Cmyv!7FF;_{*Mk^)+vNV*qe zNGj9x#PHX>pF5J%p#J)KtwP^QeRjmIdXYI?2^@Cvxb&wRnjLYaCg1H zslDG@7Up)~*Ou{DHokv2s;Zyc=&ffqZ?4y3M!=NVDOof7cIlLu7~phmOB=(jQJ+m- zI&*T9ZzEpyo|rnP$2S{qdk3KHg}F5^(fWvY(CQXFC(M4UoWZ$Nlj$MfB&=(>H0-Sr z9TF4U&5ViY;?rbmYMF`CQ|Dc*yY|Fe@yllq2tIhW-+X7^ii-|(`=C>!5+%9@1=MwZ zDK^&o@YM|=Evl_Na&_qM5AsGmJ++K;wTXk$(qau)=l9vz^@}?j*ZRZ_tljOsjx(OT zx;FpX+OZGnjJ^2a?%8pdXLU|_HgD0mTcvmI+_~tFKYuvzc67tq(RrH>yjZRL8=txmqFN{Vy=I)b=kl&yO%oHlb%WMvIdbI3E3Tg(e6oV!=!p{pR^^;IQ>{h& z7oU5sQSv)0R+WEaWtS>za-K_`QmP4-l};~}I(BSy_s%EIoLTekDx)D{^~vu}?X5B8 zzz@Iv@nB!os`FQ^4?J=5Xv)W@o}GQ;#*NfxYtL$R=cm(3_l7>=zj@2Z9HgwCR{LG9 zq^3hAvYrqz*k@g+x#B1I+j%UOjqDxayhEoPPkZY^r?(F%$Cdqj2loH*uu6gFb*{jF7gU(*=ZoUE>O$F%@}i4$V>%HOJ^fC7(v*u?2;?s@l;Dv#=xZXy7H=WOZ{=x`-=@U9Zj3MnlnqiV71T3 zf=bF4*IV#71bZy8RF1K;*j`o6ykMYlQ}^=B_AkRk*}i3QH4fqW;mRsg_%qnQpb2rd zrVvA&v5vlyfIC)*J=lk7?pq)3sf=d`AaZT0U;-02*JICoYy}-k;-hZu@R)CJMEnTG z=TC^d2Xsn3DiKZicagtf&H3XV{sKO1daO47xGDZU@vfjgI)PM2@G<7^A^Df6%tSu4 z^7J{*J^m8K$BXPBzI(ig|HS%-1OIT~e}n^`ctb!7%$xE(MH$MXM)fT{@?8d8s>g+G zoiH6a4CoDTxkaY(#vv|m;Bq3y9SBMcr!KyF0=b7XJ*gUpe= z8ZPxOP1O}P6-(y%h#ZkI%&P^A;2Yo2F&tdwy#(-rS{~#RD>}q3nm!H zh}x8Go~?rvlR+^Jb)Bii3&Sz5bFmnoh^MCD8B^nW61g;)*ZDp*`DQEgRZ=$Rjrrjl z#YmspuqQMn^&lmSd6fC4bQu-8=9qGQj9mB96EXNa{$PZ%E^QShCes`QsbB+yVEm<$0`8(TyHEk)od^4SV0~#Nepd7HydZV^jYqEy;&Ur3&SGBa6 zb6m5e#+H&-U#SzmD}w!GHN3Tqk|f`pz}}aA7i&$epQ1xD2aKPNcv6aF{~_sUYldxy zt9mG_gCSe&^M_fKg&}y?vbLFP_ONn6BjrnK&5@us6z$2VWQ7NeE!!FGGinRg3v(?wIv9I?eZq)m zcKPHt-}|TXhUrLc##oHg0bjOjg*_M1N{ddd0ky2u*1f?IrlrazbtzL*^%E&20lZHe zQ))x4R+gdY;MR453O17bHu2MV5t+qfm~SidlDa#UyQOYJM(D zRxQbDpvqp8)XhDmlqJD{e>>r6#2wZM_oB(yv%@0+E?6Jdsf7ImbWchba*eh^U!FFc0$l3Eqi;kROj;HoetpWC_ zw8doGQrm|0m{a!etVf2EXH>b0b_%khkAw`%Pfwiq+Fm^Ll07T8NGOFlKiHbL2va>O+F$RwY9;A6hB=2M{O zd*aw^D^Uj3{Psj#QsC1tT0dzu3trOZ8iH_^HkVA#n(JmBt8IjQf4n@9L*zYkYtDnL zH_8iJ0`-r|-9ZSYwf59flA8=a&@>WaO{)F6Rw^h9(ymvv8`~~pazxA;VC_+wRE`%d znH&??(wXv_T0dHuR7+ycmD!I}SaM|Qjll)=$)d_i-FH;-DE&wDLa~>UHmZ0N=or_pyslu)cJj~!l|occA1tr7pmk)UBP^DL}KQFwZ<}gG^kr;pK0z*&1Isb zaE8Fz-lT;@eo{7No95R6wOWxhC~emIPnGKGkVeb!a(ruRtfuw|e1=W+3sFmSM7^;0 zV=8KD?E9!q%{i=(vo;7b5}KqDVp!S;w6IyTmKua1<+z$9r%tlWZBNKVVZO=UOpg9l zsrdU-wp}OYw8m*kWcm*2d*r1f`5pVn3d_HPE_I+l#fV z@k^t$v}7xwH9~o0tB|_k@8Fd!Nux{$80lMJ-$%QXy|%fOtUGId471`$dxT(YS~ASF zxg|5UWh-G^%2iW@XZfVx)zS~Z^jj%8QcF+mEk_aLH$7_7_oC`Q))4t8eO{!>Jg~)4 zRxQhC$zgNrPe(}We6a1yp4F1N)K+Htsyx}}mK@2*T4hPnkrrDKdsBHPWtO}&m%?2C zg=M$6X>o)hM~o^Be)CrLgQ|Da+UKLy_~bZyIjJAzn9)|fN^PMX-Kw=?RHT4qp%tt3 zmr1?BenF0d)f^V4PpMSzvM#9O<+o(jxgOG|4QKW(QX)Cpdmf;tjDNw3HnNsza$Ti3 z3}M(Rx@lsmv9*-cYwA8&p6tAp+6Pr^P&ZQ|w`mpA>Y+^|DFmDPDQZ;C zUXc&7J_>W%%qz2Z(iXR+EmD}@!8Y>uqUPFj79|Kq`)6J#Bb2tLII@-hV2fDQ&>K?f zNw)(OVfMAlQ80Cg*}j!IHcQdpCmGB=+w@Qqziriau%dm~dek~;s>I-^fwM*A zf@LYqwMyTbe9yX!8wGEEO$&~g!;+GAv?6DI)HFMqI7JCCYYS>iwgUQZ)&7HZPTvx_ zWS-rkek4~Jn&W)tT53>g0`}AFx24yH63BT9+RthVX6uofg)=aWQO$12nHxE?Fc4JK zQWWN#5ulnG*76x( zY_7R0?M2E~Z-i9GU{cpx)*3A^+8fkba)!u$?Q%>i?HX0~%vOtapZ|E8ov(-aA;)4A%NR(oR_N&AxtDIWPNn>-Bi_VKE=-UGj)6mRgv1==Wt$z_F+t zze`QdGq!VSIdEL^s2bZIM~bJnLyfYpr4va;VkdwsbLTK}x2%<}LMY;kLs# zWx7|DNHCG7tOD>_TsSMm;d-xvHYth?12IN1(3x|d@B zbDNaoWXqNz*L2A8Fud9>%)IzlwF35*tS9!?(z19I`Hy-m`R{0|H}ZBxOVOQK^)rHjxr%OWHYqC4CPpJGHpf3o4%ijP1b{%Q3vP&bWHOmWPIN zO`ApS5lFp>#aa}$k$=}H-aH2QE2H?rJYbEncPp$vWS_?Ss;@>J$5RVgkLp!D zXWH5WL0w8D{Y`S-hT~ZI(no6b;*4o&3zDK-|E1d3d~Kv6tU&&n_Fap6q4saSc1H6DoB*y|1fcp5dOBWy$m<;f%VB%MC z`B!lcUo+-kjyRliAP2kM*yV^X@cWTigYy+Ri*n|#d1K8h;}kA<$&V@H^LYXI&@P@c z=C|r=8*uKYoco#1YZYATj!*_(Rla8L5fR@F-`_Kxm2m9;l@5K7 z+-c>kA$jpFa2&|O3mLWm-vWo9M126qfiOJz9}ZLjssXiu-arbF35)nWz~{g= zUG4}d`^8~`^U z9H;_B0ri2WfoLEGhy&gQRspL4er5Czaa;S3^PA%4P3y}th`jm34=_ZIo<^O1Bmc$G zqhdR9VYQibhpGKTBbsWlKzFG9g|^)?Q~)g%C6r>kXnGYz51Fh3lLcZ&s#>sYn}9vQ zx4>yYUk7ag@CPD*8bBP72jIaW;SDqg;(_)+C!h<^3+My%1NsB}nrb=_QV(xZ0hR)} zKuCSa1rP>=166=@U@$Njm=Aml><4my)4&4e)IMP67Tv0KhM{-uc6N2whJ3cym@(7Fw_o)rgJ)b!LRkNuw^bfJTwQq_t`i6I1P5 z$cVLM+0YL)_?{mJfMWpK|094=z!=~KU@VXYOaQWhiNLGCWPoD?jt{m1rvZLx_jfH5 z?`lu3YELd{PcCRr&S_81XivV=o}AF09Mhg0(ViUAo*dAg?9-m?)t>Cqo@^`Li8j3ZQ2BVb^%y^*BF2h(F)sFhE8AxpU`a^g>629~K~r zq;!txdU)XAI8Yww)hh;Eaf}juqD1xM?!rOGboBUD&S3Iu&QRwCXAX|qkPeR8ka&N) zMM&1QLR0AZW$XZ;ED#9rE7||le}RmsvcdXSHdFoM7;l_4{aPHE>fe5V|E~IHeH(#b zAOr{n{;B`}f9jw09R`F0Re)+h1W*H@kB`2*b#7KNk*bKk=_grxlB7Mc=sKR$-sz@2 zfgNSuEP5F8W%Wom0{tQ}%QpU%`d`Vz|5*EXW!=*EdI7iubCJ+X*$QhUd~*=J$TeW{J} zh4$n#?TJO4PqlY8X-_t2Pu6QsERNYT6X=B^z%gw>PsSaNcKj0vSl`AKpiBvIH=v>wrp{8T?|fS>fY*1(?rR@K+4`dPL9 z!TF`(3jlrwasm1a=_9nL_dm28uox9s2*FRC+yC=J~ceZLzwrEd2 z(Vl#uJt@{PT9R){KNyc%#w)c6S->~a?9`Zhf&Fe_xtk@$d{kJjEbbK{PijnG)|=|L zrLUHLTGdBO|15p869D>Vec_w+15`ikZ7mzFYELZ2Z5AzayEd4`iABSDUmNUg?a6fQ z$wci5#M``$Gam{p4Y#$C2gTr*?V2i~R&Hd72oKTq$gX2*c0$C4C||#`QTH|_!dImv zRl{GV$1m>{H*MiXe|lG=^rcV4{#C$O5%wUk37~&i^#jufO#d(azV!Ffze~R^{kinx z-UmYqwtEFfIJlubxvo9AqCGjUJ+Tm*{n|Sg4f{*&9SenS(Uv!AgRRq^ScuvR?VTms z6AK~44YTC@8;-mRdIl*Z7X5>*^dZ|tw0?68U)CG_?)10Q*G@k>eeCqD)2B{fI(_K$ zozrJdUpf8c^G$y8^3@LxAublG-$FBO(8jUo@-3vQAQZKXJ(%m^KXv5C`l9cjK7ac9>EowwpMHJ% z^i_X8efjj`(|=FDJ^l6c)6+jszdU{N^vA2d_-Rqawg>JO@CpYO;_|EZ&Moc9P3?(& z?Q&Hc><8`1W$npD?a6uV$yx2m_u7+F+LQ0JCl)e#OnYay_Qck4k?q``6#bR!{;Vg? zm#A|koG0NN3Fk&QC&Kv<&Vz8Cfb#>K7vOvV=K<*dr{ACce){_V&$Ro^THaeU=(XBA ztF$M}wI?=@|5I)FucYg%&e3S+V}{npJS{K+;C##uplpL;*RKV0Ec*9L+FKU({ZZ|m zz1ou<+LQmyV^@un+cCr;Da66@isI|{)C7VjT#hMb=J(15PgD)yigzUM^3-h$a_sH@ zf&a7+H06ibDQ>LSK%hKe1cHGOAQT7#IIlKW%l6sYlbPC+8QPO++LNi;lPTJh$=Z|u znq!rph4y1$7%BB&Im0kBzq@BbnQP$p<8S{F53?|^e)*jbi^_ZaQv7emw525fH%h9j zD=MrzSlzhpT*IJsi=Bdj((4X7RGhZhDFFKr>}w?8Ye3Wb=^_T_pUuy&i8I)NUJ-aM z^|4|ivg>0Gl^=FpjgUUy4cit^y!5vfyvS?5=;D~76Tn;)8CY{uO-YAz`R1U<-Q18m(`9jr zn?CdFXG$;USP%lzJQ zdH3PJWs$xMf+>YBX8AGHF8FSilzKinovFOBicf+)%I1$2q^4cm^5<#t=~SrXk5(Ub iP4chip*VSWL!iv!1T1D4HSNbKe=(CNGT8s+^#32o*MqkJ literal 0 HcmV?d00001 diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index d8d94e75c..2460e3f17 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -1,8 +1,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.4 - * @author ecnu_haozi, bugbuka, Ghost_141, muzehyun + * @version 1.5 + * @author ecnu_haozi, bugbuka, Ghost_141, muzehyun, GFalcon * Refactor common code out from challenge.js. * * changes in 1.1: @@ -13,6 +13,9 @@ * - Avoid undefined if rows[0].copilot_type is null. * Changes in 1.4: * - Add template id to challenge terms of use. + * Changes in 1.5: + * - Add the checkUserChallengeEligibility function + * - Removee the obsolete eligibility check in getChallengeTerms */ "use strict"; @@ -135,11 +138,6 @@ exports.challengeHelper = function (api, next) { return; } - if (!rows[0].no_elgibility_req && !rows[0].user_in_eligible_group) { - cb(new ForbiddenError('You are not part of the groups eligible for this challenge.')); - return; - } - // Update check to use flag. if (requireRegOpen && !rows[0].reg_open) { cb(new ForbiddenError('Registration Phase of this challenge is not open.')); @@ -316,8 +314,71 @@ exports.challengeHelper = function (api, next) { } next(null, result.terms); }); + }, + /** + * Check if the user currently logged in has the right to access the specified challenge + * + * @param {Object} connection The connection object for the current request + * @param {Number} challengeId The challenge id. + * @param {Function} next The callback that will receive an error + * if the user is not eligible + * + * @since 1.5 + */ + checkUserChallengeEligibility: function (connection, challengeId, next) { + // Admins can access any challenge + if (connection.caller.accessLevel === 'admin') { + next(); + return; + } + // Query the accessibility information + var userId = (connection.caller.userId || 0); + api.dataAccess.executeQuery('get_challenge_accessibility_and_groups', { + challengeId: challengeId, + user_id: userId + }, connection.dbConnectionMap, function (err, res) { + if (err) { + next(err); + return; + } + // If there's no corresponding record in group_contest_eligibility + // then the challenge is available to all users + if (res.length === 0 + || _.isNull(res[0].challenge_group_ind) + || _.isUndefined(res[0].challenge_group_ind)) { + next(); + return; + } + var error = false; + // Look at the groups + async.some(res, function (record, cbx) { + // Old challenges: check by looking up in common_oltp:user_group_xref + if (record.challenge_group_ind === 0) { + cbx(!(_.isNull(record.user_group_xref_found) || _.isUndefined(record.user_group_xref_found))); + } else { + // New challenges: query the V3 API + api.v3client.isUserInGroup(connection, userId, record.group_id, function (err, result) { + if (err) { + error = err; + cbx(true); + } else { + cbx(result); + } + }); + } + }, function (eligible) { + if (error) { + next(error); + } else if (eligible) { + next(); + } else if (connection.caller.accessLevel === "anon") { + next(new UnauthorizedError()); + } else { + next(new ForbiddenError()); + } + }); + }); } - }; next(); diff --git a/initializers/middleware.js b/initializers/middleware.js index cf370e589..98fb669ce 100644 --- a/initializers/middleware.js +++ b/initializers/middleware.js @@ -2,8 +2,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.3 - * @author vangavroche, TCSASSEMBLER + * @version 1.4 + * @author vangavroche, TCSASSEMBLER, GFalcon * changes in 1.1: * - add cache support (add preCacheProcessor and postCacheProcessor) * changes in 1.2: @@ -12,6 +12,8 @@ * - add authorizationPreProcessor * changes in 1.3: * - add force refresh check for preCacheProcessor + * changes in 1.4: + * - store the authorization token in connection.authToken */ "use strict"; @@ -105,6 +107,7 @@ exports.middleware = function (api, next) { cb(null, reg.exec(authHeader)[1]); } }, function (token, cb) { + connection.authToken = token; jwt.verify(token, api.config.tcConfig.oauthClientSecret, { audience: api.config.tcConfig.oauthClientId }, diff --git a/initializers/v3client.js b/initializers/v3client.js new file mode 100644 index 000000000..dabb7759d --- /dev/null +++ b/initializers/v3client.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 TopCoder Inc., All Rights Reserved. + * + * V3 API client + * + * @version 1.0 + * @author GFalcon + */ +"use strict"; +/*jslint nomen: true*/ + +var request = require('request'); +var _ = require('underscore'); +var async = require('async'); + +/** + * The URL of the V3 API + */ +var v3url = process.env.TC_API_V3_URL || 'http://localhost:8084/v3/'; + +/** + * Cached V3 API tokens. + * + * This object stores V2 tokens as keys and V3 tokens as values + */ +var tokens = {}; + +/** + * Call the service. It handles both errors and bad response status codes. + * + * @param {Object} params - parameters for a request + * @param {Function} callback - the callback function. + * It will get either an Error object or a response body. + */ +function callService(params, callback) { + params.json = true; + request(params, function (err, response, body) { + if (err) { + callback(err); + return; + } + /*jslint eqeq: true*/ + if (response.statusCode != 200) { + /*jslint eqeq: false*/ + callback(new Error('API V3 returned ' + response.statusCode + ' ' + (response.statusMessage || ''))); + return; + } + callback(null, body); + }); +} + +/** + * Get the V3 API authorization token to use in subsequent calls + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Function} callback - this function receives either an error, + * a V3 token or nothing at all (if the current connection's user is anonymous) + */ +function getToken(connection, callback) { + // Anonymous + if (_.isUndefined(connection.authToken)) { + callback(); + return; + } + // Cached token + if (!_.isUndefined(tokens[connection.authToken])) { + callback(null, tokens[connection.authToken]); + return; + } + // Get the token by calling the API + callService({ + url: v3url + 'authorizations', + method: 'POST', + body: { + param: { + token: connection.authToken + } + } + }, function (err, body) { + if (err) { + callback(err); + } else { + tokens[connection.authToken] = body.result.content.token; + callback(null, body.result.content.token); + } + }); +} + +/** + * Get IDs of users in the specified group + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Number} groupId - the group ID + * @param {Function} callback - the callback. Receives either an error + * or the list of group's users an array of numeric IDs + */ +function getGroupMembers(connection, groupId, callback) { + getToken(connection, function (err, token) { + if (err) { + callback(err); + return; + } + callService({ + url: v3url + 'groups/' + groupId + '/members', + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + token + } + }, function (err, body) { + if (err) { + callback(err); + } else { + callback(null, body.result.content.map(function (item) { + return item.memberId; + })); + } + }); + }); +} + +exports.v3client = function (api, next) { + api.v3client = { + /** + * Check if the user belongs to the group + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Number} userId - the user ID + * @param {Number} groupId - the group ID + * @param {Function} callback - the callback. The second parameter + * is boolean vwhich is true if the user is found in the group. + */ + isUserInGroup: function (connection, userId, groupId, callback) { + getGroupMembers(connection, groupId, function (err, members) { + if (err) { + callback(err); + } else { + callback(null, members.indexOf(userId) >= 0); + } + }); + } + }; + next(); +}; diff --git a/package.json b/package.json index b9daa5614..c3e6dfc2a 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "bcrypt": "0.7.x", "bigdecimal": "0.6.x", "bignum": "0.6.x", + "body-parser": "^1.17.2", "crypto": "0.0.x", "datejs": "0.0.x", "email-templates": "0.1.x", + "express": "^4.15.3", "forums-wrapper": "git://github.com/cloudspokes/forums-wrapper.git#12b57be495c2e10431173522bc9eff60e0575959", "heapdump": "^0.3.6", "highlight.js": ">= 8.3.0", diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 1b20283f4..4bd4a63de 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -4,8 +4,6 @@ select (pp_reg_open.project_id IS NOT NULL) as reg_open, (r.project_id IS NOT NULL) as user_registered, (us.user_id IS NOT NULL) as user_suspended, - (ce.contest_eligibility_id IS NULL) as no_elgibility_req, - (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, (coder2.comp_country_code IS NULL OR coder2.comp_country_code = '') as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, @@ -28,14 +26,6 @@ left join on us.user_id = @userId@ and us.user_status_type_id = 1 and us.user_status_id = 3 --- Check if user meets eligibility requirements -left outer join ( - contest_eligibility ce join ( - group_contest_eligibility gce left outer join user_group_xref ugx - on ugx.group_id = gce.group_id and ugx.login_id = @userId@ - ) - on ce.contest_eligibility_id = gce.contest_eligibility_id -) on p.project_id = ce.contest_id -- Check user's country left outer join ( informixoltp:user_address_xref uax join ( diff --git a/queries/get_challenge_accessibility_and_groups b/queries/get_challenge_accessibility_and_groups new file mode 100644 index 000000000..6ca557db3 --- /dev/null +++ b/queries/get_challenge_accessibility_and_groups @@ -0,0 +1,21 @@ +SELECT + ce.is_studio, + sg.challenge_group_ind, + ugx.group_id AS user_group_xref_found, + sg.group_id AS group_id +FROM + ( + ( + contest_eligibility ce + LEFT JOIN group_contest_eligibility gce + ON ce.contest_eligibility_id = gce.contest_eligibility_id + ) + LEFT JOIN security_groups sg + ON gce.group_id = sg.group_id + ) + LEFT JOIN ( + SELECT group_id FROM user_group_xref WHERE login_id=@user_id@ + ) ugx + ON ugx.group_id = gce.group_id +WHERE ce.contest_id = @challengeId@ + diff --git a/queries/get_challenge_accessibility_and_groups.json b/queries/get_challenge_accessibility_and_groups.json new file mode 100644 index 000000000..218f37428 --- /dev/null +++ b/queries/get_challenge_accessibility_and_groups.json @@ -0,0 +1,5 @@ +{ + "name" : "get_challenge_accessibility_and_groups", + "db" : "tcs_catalog", + "sqlfile" : "get_challenge_accessibility_and_groups" +} \ No newline at end of file diff --git a/test/postman/New_Challenge_Visibility_Control.postman_collection.json b/test/postman/New_Challenge_Visibility_Control.postman_collection.json new file mode 100644 index 000000000..7dadfd3d1 --- /dev/null +++ b/test/postman/New_Challenge_Visibility_Control.postman_collection.json @@ -0,0 +1,386 @@ +{ + "id": "ba962be9-0d58-f187-8809-008a39bc2240", + "name": "New Challenge Visibility Control", + "description": "", + "order": [], + "folders": [ + { + "id": "712ffa63-a959-e4a3-6af9-84d4f236b2f3", + "name": "Get checkpoints", + "description": "", + "order": [ + "7c7643c6-89ab-641e-b67a-32b3ac91e09e", + "d830ec36-eb8e-9586-c546-14af77cec152", + "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", + "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "a3ae5124-2077-4ff2-4e02-afae7670bbe5" + ], + "owner": "316251" + }, + { + "id": "cfbf928f-56b8-9813-f8f3-4ac4e342d965", + "name": "Register for challenges", + "description": "", + "order": [ + "4b64d85a-4c08-8ec2-9c3f-50605bd2e09e", + "5224f722-9f4f-07bb-58e7-351512cc66ea", + "60ae89de-4eb1-c0aa-b866-b28b52436e89", + "843d6759-0cc0-a0c6-9fde-60f893f56eac", + "46cf305a-8251-66aa-391c-46def82773a1" + ], + "owner": "316251" + }, + { + "id": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27", + "name": "login", + "description": "", + "order": [ + "6bed8920-6800-0ae0-e63d-b39b05c7f50c", + "fd4cd936-2d4d-a272-f402-d0f7b6cab82f" + ], + "owner": "316251", + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + } + ], + "timestamp": 1474156790593, + "owner": "316251", + "public": false, + "requests": [ + { + "id": "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550652259, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "46cf305a-8251-66aa-391c-46def82773a1", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110005/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813578982, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "4b64d85a-4c08-8ec2-9c3f-50605bd2e09e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110001/register", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "POST", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497813014785, + "version": 2, + "responses": [], + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "cfbf928f-56b8-9813-f8f3-4ac4e342d965" + }, + { + "id": "5224f722-9f4f-07bb-58e7-351512cc66ea", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110002/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813399305, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "60ae89de-4eb1-c0aa-b866-b28b52436e89", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110003/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813480606, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "6bed8920-6800-0ae0-e63d-b39b05c7f50c", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159263289, + "name": "Login as admin user", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", + "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" + }, + { + "id": "7c7643c6-89ab-641e-b67a-32b3ac91e09e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497550504090, + "version": 2, + "responses": [], + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "843d6759-0cc0-a0c6-9fde-60f893f56eac", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110004/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813524683, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "a3ae5124-2077-4ff2-4e02-afae7670bbe5", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550755372, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550612717, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550705028, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "fd4cd936-2d4d-a272-f402-d0f7b6cab82f", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159245944, + "name": "Log in as ordinary user", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "rawModeData": "{\n \"username\": \"user\", \n \"password\": \"password\"\n}", + "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" + } + ] +} \ No newline at end of file diff --git a/test/postman/New_Challenge_Visibility_Control.postman_environment.json b/test/postman/New_Challenge_Visibility_Control.postman_environment.json new file mode 100644 index 000000000..143271c12 --- /dev/null +++ b/test/postman/New_Challenge_Visibility_Control.postman_environment.json @@ -0,0 +1,34 @@ +{ + "id": "d761e292-418f-09b5-8b27-9d93eae42f1e", + "name": "New Challenge Visibility Control", + "values": [ + { + "enabled": true, + "key": "url", + "value": "http://localhost:8080/api/v2", + "type": "text" + }, + { + "enabled": true, + "key": "adminToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU2IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNTkyNjgsImlhdCI6MTQ3NDE1OTI2OH0.KRgW9TxNOEiEu5YdQnXQO1nKFULIuy7JlzDZdq9QFQY", + "type": "text" + }, + { + "enabled": true, + "key": "userToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNzI0MDgsImlhdCI6MTQ3NDE3MjQwOH0.sIG2FoNiCldizzcTMQ9iAFh-PCigNGBAlicxms6uTkk", + "type": "text" + }, + { + "enabled": true, + "key": "authToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAyODI4MDMsImlhdCI6MTQ3NDI4MjgwM30.s6q_FRFryMslkWCkR0wPSWwTopkZhHH8g9R_4GPf9m4", + "type": "text" + } + ], + "timestamp": 1497565761064, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2017-06-15T22:29:38.942Z", + "_postman_exported_using": "Postman/5.0.1" +} \ No newline at end of file diff --git a/test/scripts/mock_v3.js b/test/scripts/mock_v3.js new file mode 100644 index 000000000..8df5e8c02 --- /dev/null +++ b/test/scripts/mock_v3.js @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 TopCoder Inc., All Rights Reserved. + * + * This is the REST server that mocks some services from the V3 API + * + * @author GFalcon + * @version 1.0 + */ +"use strict"; + +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); + +app.use(bodyParser.json()); + +/* + * Log all incoming requests + */ +/*jslint unparam: true*/ +app.use(function (req, res, next) { + console.info('V3 Request: ' + JSON.stringify({ + path: req.path, + method: req.method, + headers: req.headers, + body: req.body + }, null, ' ')); + next(); +}); +/*jslint unparam: false*/ + +/* + * Return a fake 'authorization token' + */ +/*jslint unparam: true*/ +app.post('/v3/authorizations', function (req, res) { + res.json({ + result: { + content: { + token: 'FAKE-TOKEN' + } + } + }); +}); +/*jslint unparam: false*/ + +/* + * Get group members. Makes each group consist of one user + * (the user from the sample database whose handle is 'user') + * except one group (id 3330004) that doesn't have any users at all + */ +app.get('/v3/groups/:groupId/members', function (req, res) { + /*jslint eqeq: true*/ + if (req.params.groupId != 3330004) { + /*jslint eqeq: false*/ + res.json({ + result: { + content: [{ + memberId: 132458 + }] + } + }); + } else { + res.json({ + result: { + content: [] + } + }); + } +}); + +app.listen(8084); From 092c69dc7a1ea669f9a7f864058d3ba32f2367d4 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 20 Jun 2017 08:05:08 +0800 Subject: [PATCH 31/67] improve the query --- queries/check_user_challenge_accessibility | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/check_user_challenge_accessibility b/queries/check_user_challenge_accessibility index 4ab12d78a..e7d7e9b14 100644 --- a/queries/check_user_challenge_accessibility +++ b/queries/check_user_challenge_accessibility @@ -3,9 +3,9 @@ SELECT 1 FROM contest_eligibility ce INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id - INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id + LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ugx.login_id = @user_id@) AS has_access + AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access , (SELECT 1 FROM contest_eligibility ce From a0b6b3b4a81be58a07e93fbeb1e425a0557a5baa Mon Sep 17 00:00:00 2001 From: skyhit Date: Tue, 20 Jun 2017 08:16:46 +0800 Subject: [PATCH 32/67] update query for groups (#502) --- queries/check_is_related_with_challenge | 2 +- queries/get_open_challenges_count | 2 +- queries/get_past_challenges_count | 2 +- queries/search_past_software_studio_challenges | 2 +- queries/search_past_software_studio_challenges_count | 2 +- queries/search_software_studio_challenges | 2 +- queries/search_software_studio_challenges_count | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/queries/check_is_related_with_challenge b/queries/check_is_related_with_challenge index 5c18c5bb6..ab18cd096 100644 --- a/queries/check_is_related_with_challenge +++ b/queries/check_is_related_with_challenge @@ -5,7 +5,7 @@ SELECT INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ugx.login_id = @user_id@) AS has_access + AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access , (SELECT 1 FROM contest_eligibility ce diff --git a/queries/get_open_challenges_count b/queries/get_open_challenges_count index 863788407..0b65313d6 100644 --- a/queries/get_open_challenges_count +++ b/queries/get_open_challenges_count @@ -16,7 +16,7 @@ AND p.project_category_id = pcl.project_category_id -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index 0adb68d0f..23b07d5eb 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -28,6 +28,6 @@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @pr -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) diff --git a/queries/search_past_software_studio_challenges b/queries/search_past_software_studio_challenges index 660133285..53ba42220 100644 --- a/queries/search_past_software_studio_challenges +++ b/queries/search_past_software_studio_challenges @@ -105,7 +105,7 @@ AND NVL((cmc_task_id.value), '') = DECODE('@cmc@', '', NVL((cmc_task_id.value), -- Filter out the challenge that user is not belong to. AND (not exists (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id) OR exists(SELECT contest_id FROM contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - WHERE x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + WHERE ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) and pp.actual_end_time > '2012-01-01 00:00:00' diff --git a/queries/search_past_software_studio_challenges_count b/queries/search_past_software_studio_challenges_count index 95151dbe4..81bd29d3b 100644 --- a/queries/search_past_software_studio_challenges_count +++ b/queries/search_past_software_studio_challenges_count @@ -11,7 +11,7 @@ INNER JOIN project_category_lu pcl on pcl.project_category_id = p.project_catego LEFT JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 WHERE (not exists (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id) OR exists(SELECT contest_id FROM contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - WHERE x.login_id = 22655028 AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + WHERE ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. AND p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) diff --git a/queries/search_software_studio_challenges b/queries/search_software_studio_challenges index 0f148ac57..18c7c1370 100644 --- a/queries/search_software_studio_challenges +++ b/queries/search_software_studio_challenges @@ -104,7 +104,7 @@ FIRST @pageSize@ -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters diff --git a/queries/search_software_studio_challenges_count b/queries/search_software_studio_challenges_count index 50a44e649..ca300b02b 100644 --- a/queries/search_software_studio_challenges_count +++ b/queries/search_software_studio_challenges_count @@ -51,7 +51,7 @@ SELECT count(*) AS total -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. AND pstatus.project_status_id IN (@project_status_id@) From 29cc5e729459cf107766eb9ac0fc1f3428afbe08 Mon Sep 17 00:00:00 2001 From: skyhit Date: Tue, 20 Jun 2017 09:53:16 +0800 Subject: [PATCH 33/67] Update queries (#503) improve logging for v3 api call --- initializers/v3client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index dabb7759d..7b7ac3c33 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -42,7 +42,7 @@ function callService(params, callback) { /*jslint eqeq: true*/ if (response.statusCode != 200) { /*jslint eqeq: false*/ - callback(new Error('API V3 returned ' + response.statusCode + ' ' + (response.statusMessage || ''))); + callback(new Error('API ' + params.url + ' returned ' + response.statusCode + ' ' + (response.statusMessage || ''))); return; } callback(null, body); From 591e2d9fe19357976c3307dae7113101efa71a1a Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 20 Jun 2017 10:10:32 +0800 Subject: [PATCH 34/67] should use externalToken field name --- initializers/v3client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 7b7ac3c33..df8312a37 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -73,7 +73,7 @@ function getToken(connection, callback) { method: 'POST', body: { param: { - token: connection.authToken + externalToken: connection.authToken } } }, function (err, body) { From a81d5d7b418fb2ed257f35e801ea89273528b5e3 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 20 Jun 2017 09:30:16 -0400 Subject: [PATCH 35/67] Challenge eligibility updates for v3 groups (#505) * Improve challenge visibility control (#501) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * improve the query * update query for groups (#502) * Update queries (#503) improve logging for v3 api call * should use externalToken field name --- actions/challengeRegistration.js | 39 +- actions/challenges.js | 38 +- db_scripts/test_eligibility.delete.sql | 39 ++ db_scripts/test_eligibility.insert.sql | 219 ++++++++++ ...e-Improve Challenge Visibility Control.doc | Bin 0 -> 52736 bytes initializers/challengeHelper.js | 77 +++- initializers/middleware.js | 7 +- initializers/v3client.js | 143 +++++++ package.json | 2 + queries/challenge_registration_validations | 10 - queries/check_is_related_with_challenge | 2 +- queries/check_user_challenge_accessibility | 4 +- .../get_challenge_accessibility_and_groups | 21 + ...et_challenge_accessibility_and_groups.json | 5 + queries/get_open_challenges_count | 2 +- queries/get_past_challenges_count | 2 +- .../search_past_software_studio_challenges | 2 +- ...arch_past_software_studio_challenges_count | 2 +- queries/search_software_studio_challenges | 2 +- .../search_software_studio_challenges_count | 2 +- ...Visibility_Control.postman_collection.json | 386 ++++++++++++++++++ ...isibility_Control.postman_environment.json | 34 ++ test/scripts/mock_v3.js | 73 ++++ 23 files changed, 1046 insertions(+), 65 deletions(-) create mode 100644 db_scripts/test_eligibility.delete.sql create mode 100644 db_scripts/test_eligibility.insert.sql create mode 100644 docs/Verification_Guide-Improve Challenge Visibility Control.doc create mode 100644 initializers/v3client.js create mode 100644 queries/get_challenge_accessibility_and_groups create mode 100644 queries/get_challenge_accessibility_and_groups.json create mode 100644 test/postman/New_Challenge_Visibility_Control.postman_collection.json create mode 100644 test/postman/New_Challenge_Visibility_Control.postman_environment.json create mode 100644 test/scripts/mock_v3.js diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index f50077b66..9424951ef 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -3,8 +3,8 @@ * * The APIs to register a challenge (studio category or software category) for the current logged-in user. * - * @version 1.7 - * @author ecnu_haozi, xjtufreeman, bugbuka, flytoj2ee, muzehyun + * @version 1.8 + * @author ecnu_haozi, xjtufreeman, bugbuka, flytoj2ee, muzehyun, GFalcon * * changes in 1.1: * Combine Challenge Registration API(BUGR-11058) @@ -27,6 +27,9 @@ * * changes in 1.7: * Avoid reliability info set if there is none for new user. + * + * changes in 1.8: + * Added the verification of the challenge's eligibility */ "use strict"; @@ -880,19 +883,31 @@ exports.registerChallenge = { } else { api.helper.checkUserActivated(connection.caller.handle, api, connection.dbConnectionMap, function (err, inactive) { var fail = err || inactive; - if (fail) cb(fail); - else api.dataAccess.executeQuery('check_challenge_exists', {challengeId: challengeId}, connection.dbConnectionMap, cb); + if (fail) { + cb(fail); + } else { + api.dataAccess.executeQuery('check_challenge_exists', {challengeId: challengeId}, connection.dbConnectionMap, cb); + } }, "You must activate your account in order to participate. Please check your e-mail in order to complete the activation process, or contact support@topcoder.com if you did not receive an e-mail."); } - }, function (result, cb) { - if (result.length > 0) { - if (result[0].is_studio) { - registerStudioChallengeAction(api, connection, next); - } else { - registerSoftwareChallengeAction(api, connection, next); - } - } else { + }, function(result, cb) { + // If the challenge is not found in the tcs_catalog:project table, + if (result.length === 0) { + // Do nothing, do not register cb(); + return; + } + var isStudio = result[0].isStudio !== 0; + api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, function (err) { + cb(err, isStudio); + }); + }, function (isStudio, cb) { + if (_.isUndefined(isStudio)) { + cb(); + } else if (isStudio) { + registerStudioChallengeAction(api, connection, next); + } else { + registerSoftwareChallengeAction(api, connection, next); } } ], function (err) { diff --git a/actions/challenges.js b/actions/challenges.js index 0c40bbf2b..53266e7a8 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -1,9 +1,9 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.31 + * @version 1.32 * @author Sky_, mekanizumu, TCSASSEMBLER, freegod, Ghost_141, kurtrips, xjtufreeman, ecnu_haozi, hesibo, LazyChild, - * @author isv, muzehyun, bugbuka + * @author isv, muzehyun, bugbuka, GFalcon * @changes from 1.0 * merged with Member Registration API * changes in 1.1: @@ -79,9 +79,12 @@ * - Update challenge type filter. * Changes in 1.31: * - Remove screeningScorecardId and reviewScorecardId from search challenges api. + * Changes in 1.32: + * - validateChallenge function now checks if an user belongs to a group via + * user_group_xref for old challenges and by calling V3 API for new ones. */ "use strict"; -/*jslint stupid: true, unparam: true, continue: true */ +/*jslint stupid: true, unparam: true, continue: true, nomen: true */ require('datejs'); var fs = require('fs'); @@ -851,7 +854,7 @@ var addFilter = function (sql, filter, isMyChallenges, helper, caller) { * @since 1.10 */ function validateChallenge(api, connection, dbConnectionMap, challengeId, isStudio, callback) { - var error, sqlParams, helper = api.helper; + var error, sqlParams, helper = api.helper, userId = (connection.caller.userId || 0); async.waterfall([ function (cb) { error = helper.checkPositiveInteger(challengeId, 'challengeId') || @@ -862,31 +865,18 @@ function validateChallenge(api, connection, dbConnectionMap, challengeId, isStud } sqlParams = { challengeId: challengeId, - user_id: connection.caller.userId || 0 + user_id: userId }; - async.parallel({ - accessibility: function (cbx) { - api.dataAccess.executeQuery('check_user_challenge_accessibility', sqlParams, dbConnectionMap, cbx); - }, - exists: function (cbx) { - api.dataAccess.executeQuery('check_challenge_exists', sqlParams, dbConnectionMap, cbx); - } - }, cb); + api.dataAccess.executeQuery('check_challenge_exists', sqlParams, dbConnectionMap, cb); }, function (res, cb) { - if (res.exists.length === 0 || Boolean(res.exists[0].is_studio) !== isStudio) { + // If the record with this callengeId doesn't exist in 'project' table + // or there's a studio/software mismatch + if (res.length === 0 || Boolean(res[0].is_studio) !== isStudio) { cb(new NotFoundError("Challenge not found.")); return; } - var access = res.accessibility[0]; - if (access.is_private && !access.has_access && connection.caller.accessLevel !== "admin") { - if (connection.caller.accessLevel === "anon") { - cb(new UnauthorizedError()); - } else { - cb(new ForbiddenError()); - } - return; - } - cb(); + // Check the eligibility + api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cb); } ], callback); } diff --git a/db_scripts/test_eligibility.delete.sql b/db_scripts/test_eligibility.delete.sql new file mode 100644 index 000000000..5f77f6c44 --- /dev/null +++ b/db_scripts/test_eligibility.delete.sql @@ -0,0 +1,39 @@ +DATABASE common_oltp; + +DELETE FROM user_group_xref WHERE group_id > 3330000 AND group_id < 3330100; +DELETE FROM security_groups WHERE group_id > 3330000 AND group_id < 3330100; +DELETE FROM group_contest_eligibility WHERE contest_eligibility_id > 1110000 AND contest_eligibility_id < 1110100; +DELETE FROM contest_eligibility WHERE contest_eligibility_id > 1110000 AND contest_eligibility_id < 1110100; + +DATABASE informixoltp; + +-- UPDATE coder SET comp_country_code = NULL WHERE user_id = 132458; + +DATABASE tcs_catalog; + +DELETE FROM notification WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project_result WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project_user_audit WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM component_inquiry WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE project_id > 1110000 AND project_id < 1110100); +DELETE FROM resource WHERE project_id > 1110000 AND project_id < 1110100; + +DELETE FROM project_info WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM comp_versions WHERE component_id = 3330333; +DELETE FROM comp_catalog WHERE component_id = 3330333; +DELETE FROM project_phase WHERE project_id > 1110000 AND project_id < 1110100; +DELETE FROM project WHERE project_id > 1110000 AND project_id < 1110100; + +DELETE FROM review_item_comment WHERE review_item_comment_id > 7770000 AND review_item_id < 7770100; +DELETE FROM review_item WHERE review_item_id > 5550000 AND review_item_id < 5550100; +DELETE FROM review WHERE review_id > 4440000 AND review_id < 4440100; +DELETE FROM scorecard_question WHERE scorecard_question_id = 3330333; +DELETE FROM scorecard_section WHERE scorecard_section_id = 3330333; +DELETE FROM scorecard_group WHERE scorecard_group_id = 3330333; +DELETE FROM scorecard WHERE scorecard_id = 3330333; +DELETE FROM submission WHERE submission_id > 2220000 AND submission_id < 2220100; +DELETE FROM prize WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM upload WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM resource WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM project_phase WHERE project_id > 2220000 AND project_id < 2220100; +DELETE FROM project WHERE project_id > 2220000 AND project_id < 2220100; diff --git a/db_scripts/test_eligibility.insert.sql b/db_scripts/test_eligibility.insert.sql new file mode 100644 index 000000000..8bb746502 --- /dev/null +++ b/db_scripts/test_eligibility.insert.sql @@ -0,0 +1,219 @@ +DATABASE tcs_catalog; + +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220001, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220002, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220003, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220004, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (2220005, 1, 14, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770001, 2220001, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770002, 2220002, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770003, 2220003, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770004, 2220004, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (7770005, 2220005, 17, 3, CURRENT, CURRENT, 0, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880001, 20, 2220001, 7770001, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880002, 20, 2220002, 7770002, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880003, 20, 2220003, 7770003, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880004, 20, 2220004, 7770004, 132456, "132456", CURRENT, "132456", CURRENT); +INSERT INTO resource (resource_id, resource_role_id, project_id, project_phase_id, user_id, create_user, create_date, modify_user, modify_date) + VALUES (8880005, 20, 2220005, 7770005, 132456, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990001, 2220001, 8880001, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990002, 2220002, 8880002, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990003, 2220003, 8880003, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990004, 2220004, 8880004, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO upload (upload_id, project_id, resource_id, upload_type_id, upload_status_id, parameter, create_user, create_date, modify_user, modify_date) + VALUES (9990005, 2220005, 8880005, 1, 1, "---", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 2220001, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 2220002, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 2220003, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 2220004, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO prize (prize_id, project_id, place, prize_amount, prize_type_id, number_of_submissions, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 2220005, 1, 1000, 14, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220001, 9990001, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110001); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220002, 9990002, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110002); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220003, 9990003, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110003); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220004, 9990004, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110004); +INSERT INTO submission (submission_id, upload_id, submission_status_id, submission_type_id, create_user, create_date, modify_user, modify_date, prize_id) + VALUES (2220005, 9990005, 1, 3, "132456", CURRENT, "132456", CURRENT, 1110005); + +INSERT INTO scorecard (scorecard_id, scorecard_status_id, scorecard_type_id, project_category_id, name, version, min_score, max_score, create_user, create_date, modify_user, modify_date, version_number) + VALUES (3330333, 1, 7, 14, "---", "---", 0, 100, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_group (scorecard_group_id, scorecard_id, name, weight, sort, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 3330333, "---", 100, 1, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_section (scorecard_section_id, scorecard_group_id, name, weight, sort, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 3330333, "---", 100, 1, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO scorecard_question (scorecard_question_id, scorecard_question_type_id, scorecard_section_id, description, weight, sort, upload_document, upload_document_required, create_user, create_date, modify_user, modify_date, version) + VALUES (3330333, 1, 3330333, '---', 100, 1, 0, 0, "132456", CURRENT, "132456", CURRENT, 1); + +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440001, 8880001, 2220001, 7770001, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440002, 8880002, 2220002, 7770002, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440003, 8880003, 2220003, 7770003, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440004, 8880004, 2220004, 7770004, 3330333, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, create_user, create_date, modify_user, modify_date) + VALUES (4440005, 8880005, 2220005, 7770005, 3330333, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550001, 4440001, 3330333, 9990001, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550002, 4440002, 3330333, 9990002, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550003, 4440003, 3330333, 9990003, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550004, 4440004, 3330333, 9990004, "---", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) + VALUES (5550005, 4440005, 3330333, 9990005, "---", 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770001, 8880001, 5550001, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770002, 8880002, 5550002, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770003, 8880003, 5550003, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770004, 8880004, 5550004, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO review_item_comment (review_item_comment_id, resource_id, review_item_id, comment_type_id, content, sort, create_user, create_date, modify_user, modify_date) + VALUES (7770005, 8880005, 5550005, 1, "The current user has the right to view this challenge", 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 1, 14, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project (project_id, project_status_id, project_category_id, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 1, 14, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220001, 1110001, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220002, 1110002, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220003, 1110003, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220004, 1110004, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (2220005, 1110005, 1, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); + +INSERT INTO comp_catalog (component_id, current_version, component_name, status_id, modify_date, public_ind) + VALUES (3330333, 1, "---", 1, CURRENT, 0); + +INSERT INTO comp_versions (comp_vers_id, component_id, version, version_text, phase_id, phase_time, price, modify_date) + VALUES (4440444, 3330333, 1, "1", 113, CURRENT, 1000, CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 2, "3330333", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 6, 3330333, "Not private", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 6, 3330333, "Old logic - access allowed", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 6, 3330333, "Old logic - access denied", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 6, 3330333, "New logic - access allowed", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 6, 3330333, "New logic - access denied", CURRENT, "132456", CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 79, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 79, "---", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330001, 1110001, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330002, 1110002, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330003, 1110003, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330004, 1110004, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) + VALUES (3330005, 1110005, 2, 2, CURRENT, CURRENT, 1, "132456", CURRENT, "132456", CURRENT); + +DATABASE informixoltp; + +UPDATE coder SET comp_country_code = ( + SELECT MIN(country_code) FROM country WHERE country_name = "United States" +) WHERE coder_id = 132458; + +DATABASE common_oltp; + +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110002, 2220002, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110003, 2220003, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110004, 2220004, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110005, 2220005, 0); + +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330001, "Eligibility - Old logic - with user", 0); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330002, "Eligibility - Old logic - no users", 0); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330003, "Eligibility - New logic - with user", 1); +INSERT INTO security_groups (group_id, description, challenge_group_ind) VALUES (3330004, "Eligibility - New logic - no users", 1); + +INSERT INTO user_group_xref (user_group_id, login_id, group_id) VALUES (5550001, 132458, 3330001); + +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110002, 3330001); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110003, 3330002); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110004, 3330003); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110005, 3330004); + +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110012, 1110002, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110013, 1110003, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110014, 1110004, 0); +INSERT INTO contest_eligibility (contest_eligibility_id, contest_id, is_studio) VALUES (1110015, 1110005, 0); + +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110012, 3330001); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110013, 3330002); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110014, 3330003); +INSERT INTO group_contest_eligibility (contest_eligibility_id, group_id) VALUES (1110015, 3330004); diff --git a/docs/Verification_Guide-Improve Challenge Visibility Control.doc b/docs/Verification_Guide-Improve Challenge Visibility Control.doc new file mode 100644 index 0000000000000000000000000000000000000000..1c2913aaec56e9f7acc508b92ae08a944ff0a3e0 GIT binary patch literal 52736 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 literal 0 HcmV?d00001 diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index d8d94e75c..2460e3f17 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -1,8 +1,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.4 - * @author ecnu_haozi, bugbuka, Ghost_141, muzehyun + * @version 1.5 + * @author ecnu_haozi, bugbuka, Ghost_141, muzehyun, GFalcon * Refactor common code out from challenge.js. * * changes in 1.1: @@ -13,6 +13,9 @@ * - Avoid undefined if rows[0].copilot_type is null. * Changes in 1.4: * - Add template id to challenge terms of use. + * Changes in 1.5: + * - Add the checkUserChallengeEligibility function + * - Removee the obsolete eligibility check in getChallengeTerms */ "use strict"; @@ -135,11 +138,6 @@ exports.challengeHelper = function (api, next) { return; } - if (!rows[0].no_elgibility_req && !rows[0].user_in_eligible_group) { - cb(new ForbiddenError('You are not part of the groups eligible for this challenge.')); - return; - } - // Update check to use flag. if (requireRegOpen && !rows[0].reg_open) { cb(new ForbiddenError('Registration Phase of this challenge is not open.')); @@ -316,8 +314,71 @@ exports.challengeHelper = function (api, next) { } next(null, result.terms); }); + }, + /** + * Check if the user currently logged in has the right to access the specified challenge + * + * @param {Object} connection The connection object for the current request + * @param {Number} challengeId The challenge id. + * @param {Function} next The callback that will receive an error + * if the user is not eligible + * + * @since 1.5 + */ + checkUserChallengeEligibility: function (connection, challengeId, next) { + // Admins can access any challenge + if (connection.caller.accessLevel === 'admin') { + next(); + return; + } + // Query the accessibility information + var userId = (connection.caller.userId || 0); + api.dataAccess.executeQuery('get_challenge_accessibility_and_groups', { + challengeId: challengeId, + user_id: userId + }, connection.dbConnectionMap, function (err, res) { + if (err) { + next(err); + return; + } + // If there's no corresponding record in group_contest_eligibility + // then the challenge is available to all users + if (res.length === 0 + || _.isNull(res[0].challenge_group_ind) + || _.isUndefined(res[0].challenge_group_ind)) { + next(); + return; + } + var error = false; + // Look at the groups + async.some(res, function (record, cbx) { + // Old challenges: check by looking up in common_oltp:user_group_xref + if (record.challenge_group_ind === 0) { + cbx(!(_.isNull(record.user_group_xref_found) || _.isUndefined(record.user_group_xref_found))); + } else { + // New challenges: query the V3 API + api.v3client.isUserInGroup(connection, userId, record.group_id, function (err, result) { + if (err) { + error = err; + cbx(true); + } else { + cbx(result); + } + }); + } + }, function (eligible) { + if (error) { + next(error); + } else if (eligible) { + next(); + } else if (connection.caller.accessLevel === "anon") { + next(new UnauthorizedError()); + } else { + next(new ForbiddenError()); + } + }); + }); } - }; next(); diff --git a/initializers/middleware.js b/initializers/middleware.js index cf370e589..98fb669ce 100644 --- a/initializers/middleware.js +++ b/initializers/middleware.js @@ -2,8 +2,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.3 - * @author vangavroche, TCSASSEMBLER + * @version 1.4 + * @author vangavroche, TCSASSEMBLER, GFalcon * changes in 1.1: * - add cache support (add preCacheProcessor and postCacheProcessor) * changes in 1.2: @@ -12,6 +12,8 @@ * - add authorizationPreProcessor * changes in 1.3: * - add force refresh check for preCacheProcessor + * changes in 1.4: + * - store the authorization token in connection.authToken */ "use strict"; @@ -105,6 +107,7 @@ exports.middleware = function (api, next) { cb(null, reg.exec(authHeader)[1]); } }, function (token, cb) { + connection.authToken = token; jwt.verify(token, api.config.tcConfig.oauthClientSecret, { audience: api.config.tcConfig.oauthClientId }, diff --git a/initializers/v3client.js b/initializers/v3client.js new file mode 100644 index 000000000..df8312a37 --- /dev/null +++ b/initializers/v3client.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 TopCoder Inc., All Rights Reserved. + * + * V3 API client + * + * @version 1.0 + * @author GFalcon + */ +"use strict"; +/*jslint nomen: true*/ + +var request = require('request'); +var _ = require('underscore'); +var async = require('async'); + +/** + * The URL of the V3 API + */ +var v3url = process.env.TC_API_V3_URL || 'http://localhost:8084/v3/'; + +/** + * Cached V3 API tokens. + * + * This object stores V2 tokens as keys and V3 tokens as values + */ +var tokens = {}; + +/** + * Call the service. It handles both errors and bad response status codes. + * + * @param {Object} params - parameters for a request + * @param {Function} callback - the callback function. + * It will get either an Error object or a response body. + */ +function callService(params, callback) { + params.json = true; + request(params, function (err, response, body) { + if (err) { + callback(err); + return; + } + /*jslint eqeq: true*/ + if (response.statusCode != 200) { + /*jslint eqeq: false*/ + callback(new Error('API ' + params.url + ' returned ' + response.statusCode + ' ' + (response.statusMessage || ''))); + return; + } + callback(null, body); + }); +} + +/** + * Get the V3 API authorization token to use in subsequent calls + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Function} callback - this function receives either an error, + * a V3 token or nothing at all (if the current connection's user is anonymous) + */ +function getToken(connection, callback) { + // Anonymous + if (_.isUndefined(connection.authToken)) { + callback(); + return; + } + // Cached token + if (!_.isUndefined(tokens[connection.authToken])) { + callback(null, tokens[connection.authToken]); + return; + } + // Get the token by calling the API + callService({ + url: v3url + 'authorizations', + method: 'POST', + body: { + param: { + externalToken: connection.authToken + } + } + }, function (err, body) { + if (err) { + callback(err); + } else { + tokens[connection.authToken] = body.result.content.token; + callback(null, body.result.content.token); + } + }); +} + +/** + * Get IDs of users in the specified group + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Number} groupId - the group ID + * @param {Function} callback - the callback. Receives either an error + * or the list of group's users an array of numeric IDs + */ +function getGroupMembers(connection, groupId, callback) { + getToken(connection, function (err, token) { + if (err) { + callback(err); + return; + } + callService({ + url: v3url + 'groups/' + groupId + '/members', + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + token + } + }, function (err, body) { + if (err) { + callback(err); + } else { + callback(null, body.result.content.map(function (item) { + return item.memberId; + })); + } + }); + }); +} + +exports.v3client = function (api, next) { + api.v3client = { + /** + * Check if the user belongs to the group + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Number} userId - the user ID + * @param {Number} groupId - the group ID + * @param {Function} callback - the callback. The second parameter + * is boolean vwhich is true if the user is found in the group. + */ + isUserInGroup: function (connection, userId, groupId, callback) { + getGroupMembers(connection, groupId, function (err, members) { + if (err) { + callback(err); + } else { + callback(null, members.indexOf(userId) >= 0); + } + }); + } + }; + next(); +}; diff --git a/package.json b/package.json index b9daa5614..c3e6dfc2a 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "bcrypt": "0.7.x", "bigdecimal": "0.6.x", "bignum": "0.6.x", + "body-parser": "^1.17.2", "crypto": "0.0.x", "datejs": "0.0.x", "email-templates": "0.1.x", + "express": "^4.15.3", "forums-wrapper": "git://github.com/cloudspokes/forums-wrapper.git#12b57be495c2e10431173522bc9eff60e0575959", "heapdump": "^0.3.6", "highlight.js": ">= 8.3.0", diff --git a/queries/challenge_registration_validations b/queries/challenge_registration_validations index 1b20283f4..4bd4a63de 100644 --- a/queries/challenge_registration_validations +++ b/queries/challenge_registration_validations @@ -4,8 +4,6 @@ select (pp_reg_open.project_id IS NOT NULL) as reg_open, (r.project_id IS NOT NULL) as user_registered, (us.user_id IS NOT NULL) as user_suspended, - (ce.contest_eligibility_id IS NULL) as no_elgibility_req, - (ugx.login_id IS NOT NULL) as user_in_eligible_group, (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as user_country_banned, (coder2.comp_country_code IS NULL OR coder2.comp_country_code = '') as comp_country_is_null, (cop.copilot_profile_id IS NOT NULL) as user_is_copilot, @@ -28,14 +26,6 @@ left join on us.user_id = @userId@ and us.user_status_type_id = 1 and us.user_status_id = 3 --- Check if user meets eligibility requirements -left outer join ( - contest_eligibility ce join ( - group_contest_eligibility gce left outer join user_group_xref ugx - on ugx.group_id = gce.group_id and ugx.login_id = @userId@ - ) - on ce.contest_eligibility_id = gce.contest_eligibility_id -) on p.project_id = ce.contest_id -- Check user's country left outer join ( informixoltp:user_address_xref uax join ( diff --git a/queries/check_is_related_with_challenge b/queries/check_is_related_with_challenge index 5c18c5bb6..ab18cd096 100644 --- a/queries/check_is_related_with_challenge +++ b/queries/check_is_related_with_challenge @@ -5,7 +5,7 @@ SELECT INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ugx.login_id = @user_id@) AS has_access + AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access , (SELECT 1 FROM contest_eligibility ce diff --git a/queries/check_user_challenge_accessibility b/queries/check_user_challenge_accessibility index 4ab12d78a..e7d7e9b14 100644 --- a/queries/check_user_challenge_accessibility +++ b/queries/check_user_challenge_accessibility @@ -3,9 +3,9 @@ SELECT 1 FROM contest_eligibility ce INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id - INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id + LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ugx.login_id = @user_id@) AS has_access + AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access , (SELECT 1 FROM contest_eligibility ce diff --git a/queries/get_challenge_accessibility_and_groups b/queries/get_challenge_accessibility_and_groups new file mode 100644 index 000000000..6ca557db3 --- /dev/null +++ b/queries/get_challenge_accessibility_and_groups @@ -0,0 +1,21 @@ +SELECT + ce.is_studio, + sg.challenge_group_ind, + ugx.group_id AS user_group_xref_found, + sg.group_id AS group_id +FROM + ( + ( + contest_eligibility ce + LEFT JOIN group_contest_eligibility gce + ON ce.contest_eligibility_id = gce.contest_eligibility_id + ) + LEFT JOIN security_groups sg + ON gce.group_id = sg.group_id + ) + LEFT JOIN ( + SELECT group_id FROM user_group_xref WHERE login_id=@user_id@ + ) ugx + ON ugx.group_id = gce.group_id +WHERE ce.contest_id = @challengeId@ + diff --git a/queries/get_challenge_accessibility_and_groups.json b/queries/get_challenge_accessibility_and_groups.json new file mode 100644 index 000000000..218f37428 --- /dev/null +++ b/queries/get_challenge_accessibility_and_groups.json @@ -0,0 +1,5 @@ +{ + "name" : "get_challenge_accessibility_and_groups", + "db" : "tcs_catalog", + "sqlfile" : "get_challenge_accessibility_and_groups" +} \ No newline at end of file diff --git a/queries/get_open_challenges_count b/queries/get_open_challenges_count index 863788407..0b65313d6 100644 --- a/queries/get_open_challenges_count +++ b/queries/get_open_challenges_count @@ -16,7 +16,7 @@ AND p.project_category_id = pcl.project_category_id -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters diff --git a/queries/get_past_challenges_count b/queries/get_past_challenges_count index 0adb68d0f..23b07d5eb 100644 --- a/queries/get_past_challenges_count +++ b/queries/get_past_challenges_count @@ -28,6 +28,6 @@ AND p.tc_direct_project_id = DECODE(@project_id@, 0, p.tc_direct_project_id, @pr -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @user_id@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) diff --git a/queries/search_past_software_studio_challenges b/queries/search_past_software_studio_challenges index 660133285..53ba42220 100644 --- a/queries/search_past_software_studio_challenges +++ b/queries/search_past_software_studio_challenges @@ -105,7 +105,7 @@ AND NVL((cmc_task_id.value), '') = DECODE('@cmc@', '', NVL((cmc_task_id.value), -- Filter out the challenge that user is not belong to. AND (not exists (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id) OR exists(SELECT contest_id FROM contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - WHERE x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + WHERE ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = @user_id@) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) and pp.actual_end_time > '2012-01-01 00:00:00' diff --git a/queries/search_past_software_studio_challenges_count b/queries/search_past_software_studio_challenges_count index 95151dbe4..81bd29d3b 100644 --- a/queries/search_past_software_studio_challenges_count +++ b/queries/search_past_software_studio_challenges_count @@ -11,7 +11,7 @@ INNER JOIN project_category_lu pcl on pcl.project_category_id = p.project_catego LEFT JOIN project_info pi1 ON pi1.project_id = p.project_id AND pi1.project_info_type_id = 1 WHERE (not exists (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id) OR exists(SELECT contest_id FROM contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - WHERE x.login_id = 22655028 AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + WHERE ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. AND p.project_status_id IN (4, 5, 6, 7, 8, 9, 10, 11) diff --git a/queries/search_software_studio_challenges b/queries/search_software_studio_challenges index 0f148ac57..18c7c1370 100644 --- a/queries/search_software_studio_challenges +++ b/queries/search_software_studio_challenges @@ -104,7 +104,7 @@ FIRST @pageSize@ -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. -- start of parameters diff --git a/queries/search_software_studio_challenges_count b/queries/search_software_studio_challenges_count index 50a44e649..ca300b02b 100644 --- a/queries/search_software_studio_challenges_count +++ b/queries/search_software_studio_challenges_count @@ -51,7 +51,7 @@ SELECT count(*) AS total -- Filter out the challenge that user is not belong to. AND (not exists (select contest_id from contest_eligibility where contest_id = p.project_id) or exists(select contest_id from contest_eligibility ce, group_contest_eligibility gce, user_group_xref x - where x.login_id = @userId@ AND x.group_id = gce.group_id AND gce.contest_eligibility_id = ce.contest_eligibility_id + where ((gce.group_id < 2000000 AND x.group_id = gce.group_id AND x.login_id = 22655028) OR gce.group_id >= 2000000) AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)) AND pcl.project_category_id NOT IN (27, 37) --exclude when spec review was a 'contest.' Also exclude MM, which is in there as a 'software' contest. AND pstatus.project_status_id IN (@project_status_id@) diff --git a/test/postman/New_Challenge_Visibility_Control.postman_collection.json b/test/postman/New_Challenge_Visibility_Control.postman_collection.json new file mode 100644 index 000000000..7dadfd3d1 --- /dev/null +++ b/test/postman/New_Challenge_Visibility_Control.postman_collection.json @@ -0,0 +1,386 @@ +{ + "id": "ba962be9-0d58-f187-8809-008a39bc2240", + "name": "New Challenge Visibility Control", + "description": "", + "order": [], + "folders": [ + { + "id": "712ffa63-a959-e4a3-6af9-84d4f236b2f3", + "name": "Get checkpoints", + "description": "", + "order": [ + "7c7643c6-89ab-641e-b67a-32b3ac91e09e", + "d830ec36-eb8e-9586-c546-14af77cec152", + "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", + "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "a3ae5124-2077-4ff2-4e02-afae7670bbe5" + ], + "owner": "316251" + }, + { + "id": "cfbf928f-56b8-9813-f8f3-4ac4e342d965", + "name": "Register for challenges", + "description": "", + "order": [ + "4b64d85a-4c08-8ec2-9c3f-50605bd2e09e", + "5224f722-9f4f-07bb-58e7-351512cc66ea", + "60ae89de-4eb1-c0aa-b866-b28b52436e89", + "843d6759-0cc0-a0c6-9fde-60f893f56eac", + "46cf305a-8251-66aa-391c-46def82773a1" + ], + "owner": "316251" + }, + { + "id": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27", + "name": "login", + "description": "", + "order": [ + "6bed8920-6800-0ae0-e63d-b39b05c7f50c", + "fd4cd936-2d4d-a272-f402-d0f7b6cab82f" + ], + "owner": "316251", + "collectionId": "6369974d-65cc-d819-459b-0026549ddb47" + } + ], + "timestamp": 1474156790593, + "owner": "316251", + "public": false, + "requests": [ + { + "id": "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550652259, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "46cf305a-8251-66aa-391c-46def82773a1", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110005/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813578982, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "4b64d85a-4c08-8ec2-9c3f-50605bd2e09e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110001/register", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "POST", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497813014785, + "version": 2, + "responses": [], + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "cfbf928f-56b8-9813-f8f3-4ac4e342d965" + }, + { + "id": "5224f722-9f4f-07bb-58e7-351512cc66ea", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110002/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813399305, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "60ae89de-4eb1-c0aa-b866-b28b52436e89", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110003/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813480606, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "6bed8920-6800-0ae0-e63d-b39b05c7f50c", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159263289, + "name": "Login as admin user", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", + "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" + }, + { + "id": "7c7643c6-89ab-641e-b67a-32b3ac91e09e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497550504090, + "version": 2, + "responses": [], + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "843d6759-0cc0-a0c6-9fde-60f893f56eac", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110004/register", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "POST", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497813524683, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "a3ae5124-2077-4ff2-4e02-afae7670bbe5", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550755372, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550612717, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550705028, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "fd4cd936-2d4d-a272-f402-d0f7b6cab82f", + "headers": "Content-Type: application/json\n", + "url": "{{url}}/auth", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "var authResponse = JSON.parse(responseBody);\npostman.setEnvironmentVariable(\"authToken\", authResponse.token);\ntests[\"Status code is 200\"] = responseCode.code === 200;\nvar jsonData = JSON.parse(responseBody);\ntests[\"A valid token is returned\"] = !!jsonData.token;", + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1474159245944, + "name": "Log in as ordinary user", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "rawModeData": "{\n \"username\": \"user\", \n \"password\": \"password\"\n}", + "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" + } + ] +} \ No newline at end of file diff --git a/test/postman/New_Challenge_Visibility_Control.postman_environment.json b/test/postman/New_Challenge_Visibility_Control.postman_environment.json new file mode 100644 index 000000000..143271c12 --- /dev/null +++ b/test/postman/New_Challenge_Visibility_Control.postman_environment.json @@ -0,0 +1,34 @@ +{ + "id": "d761e292-418f-09b5-8b27-9d93eae42f1e", + "name": "New Challenge Visibility Control", + "values": [ + { + "enabled": true, + "key": "url", + "value": "http://localhost:8080/api/v2", + "type": "text" + }, + { + "enabled": true, + "key": "adminToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU2IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNTkyNjgsImlhdCI6MTQ3NDE1OTI2OH0.KRgW9TxNOEiEu5YdQnXQO1nKFULIuy7JlzDZdq9QFQY", + "type": "text" + }, + { + "enabled": true, + "key": "userToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAxNzI0MDgsImlhdCI6MTQ3NDE3MjQwOH0.sIG2FoNiCldizzcTMQ9iAFh-PCigNGBAlicxms6uTkk", + "type": "text" + }, + { + "enabled": true, + "key": "authToken", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NtYS5hdXRoMC5jb20vIiwic3ViIjoiYWR8MTMyNDU4IiwiYXVkIjoiQ01hQnV3U25ZMFZ1NjhQTHJXYXR2dnUzaUlpR1BoN3QiLCJleHAiOjE1MTAyODI4MDMsImlhdCI6MTQ3NDI4MjgwM30.s6q_FRFryMslkWCkR0wPSWwTopkZhHH8g9R_4GPf9m4", + "type": "text" + } + ], + "timestamp": 1497565761064, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2017-06-15T22:29:38.942Z", + "_postman_exported_using": "Postman/5.0.1" +} \ No newline at end of file diff --git a/test/scripts/mock_v3.js b/test/scripts/mock_v3.js new file mode 100644 index 000000000..8df5e8c02 --- /dev/null +++ b/test/scripts/mock_v3.js @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 TopCoder Inc., All Rights Reserved. + * + * This is the REST server that mocks some services from the V3 API + * + * @author GFalcon + * @version 1.0 + */ +"use strict"; + +var express = require('express'); +var bodyParser = require('body-parser'); + +var app = express(); + +app.use(bodyParser.json()); + +/* + * Log all incoming requests + */ +/*jslint unparam: true*/ +app.use(function (req, res, next) { + console.info('V3 Request: ' + JSON.stringify({ + path: req.path, + method: req.method, + headers: req.headers, + body: req.body + }, null, ' ')); + next(); +}); +/*jslint unparam: false*/ + +/* + * Return a fake 'authorization token' + */ +/*jslint unparam: true*/ +app.post('/v3/authorizations', function (req, res) { + res.json({ + result: { + content: { + token: 'FAKE-TOKEN' + } + } + }); +}); +/*jslint unparam: false*/ + +/* + * Get group members. Makes each group consist of one user + * (the user from the sample database whose handle is 'user') + * except one group (id 3330004) that doesn't have any users at all + */ +app.get('/v3/groups/:groupId/members', function (req, res) { + /*jslint eqeq: true*/ + if (req.params.groupId != 3330004) { + /*jslint eqeq: false*/ + res.json({ + result: { + content: [{ + memberId: 132458 + }] + } + }); + } else { + res.json({ + result: { + content: [] + } + }); + } +}); + +app.listen(8084); From f875a0963a20547549fd645af87a904aff6b7bc1 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 20 Jun 2017 22:00:42 +0800 Subject: [PATCH 36/67] update queries for group checking --- queries/check_is_related_with_challenge | 4 ++-- queries/check_user_challenge_accessibility | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/queries/check_is_related_with_challenge b/queries/check_is_related_with_challenge index ab18cd096..36014d75c 100644 --- a/queries/check_is_related_with_challenge +++ b/queries/check_is_related_with_challenge @@ -1,9 +1,9 @@ SELECT (SELECT - 1 + max(1) FROM contest_eligibility ce INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id - INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id + LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access , (SELECT diff --git a/queries/check_user_challenge_accessibility b/queries/check_user_challenge_accessibility index e7d7e9b14..309258da0 100644 --- a/queries/check_user_challenge_accessibility +++ b/queries/check_user_challenge_accessibility @@ -1,6 +1,6 @@ SELECT (SELECT - 1 + max(1) FROM contest_eligibility ce INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id From df292bf5881a9bbc59bc688293a0e7f2e8b030ee Mon Sep 17 00:00:00 2001 From: Vyacheslav V Sokolov Date: Tue, 20 Jun 2017 21:09:45 +0700 Subject: [PATCH 37/67] Improve challenge visibility control: getChallenge and getRegistrants (#504) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * Improve challenge visibility control: getChallenge and getRegistrants --- actions/challenges.js | 95 ++- db_scripts/test_eligibility.insert.sql | 23 + ...e-Improve Challenge Visibility Control.doc | Bin 52736 -> 56832 bytes initializers/challengeHelper.js | 2 +- queries/check_is_related_with_challenge | 26 +- ...Visibility_Control.postman_collection.json | 640 +++++++++++++++++- 6 files changed, 714 insertions(+), 72 deletions(-) diff --git a/actions/challenges.js b/actions/challenges.js index 53266e7a8..bb4d6ceb9 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -80,8 +80,8 @@ * Changes in 1.31: * - Remove screeningScorecardId and reviewScorecardId from search challenges api. * Changes in 1.32: - * - validateChallenge function now checks if an user belongs to a group via - * user_group_xref for old challenges and by calling V3 API for new ones. + * - validateChallenge, getRegistrants, getChallenge, getSubmissions and getPhases functions now check + * if an user belongs to a group via user_group_xref for old challenges and by calling V3 API for new ones. */ "use strict"; /*jslint stupid: true, unparam: true, continue: true, nomen: true */ @@ -1081,19 +1081,20 @@ var getChallenge = function (api, connection, dbConnectionMap, isStudio, next) { }; // Do the private check. + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - if (result[0].is_manager) { isManager = true; } // If the user has the access to the challenge or is a resource for the challenge then he is related with this challenge. - if (result[0].has_access || result[0].is_related || isManager || helper.isAdmin(caller)) { + if (result[0].is_private || result[0].is_related || isManager || helper.isAdmin(caller)) { isRelated = true; } @@ -3342,33 +3343,32 @@ var getRegistrants = function (api, connection, dbConnectionMap, isStudio, next) }; // Do the private check. - api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); - }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { api.dataAccess.executeQuery('challenge_registrants', sqlParams, dbConnectionMap, cb); }, function (results, cb) { var mapRegistrants = function (results) { - if (!_.isDefined(results)) { - return []; + if (!_.isDefined(results)) { + return []; + } + return _.map(results, function (item) { + var registrant = { + handle: item.handle, + reliability: !_.isDefined(item.reliability) ? "n/a" : item.reliability + "%", + registrationDate: formatDate(item.inquiry_date), + submissionDate: formatDate(item.submission_date) + }; + if (!isStudio) { + registrant.rating = item.rating; + registrant.colorStyle = helper.getColorStyle(item.rating); } - return _.map(results, function (item) { - var registrant = { - handle: item.handle, - reliability: !_.isDefined(item.reliability) ? "n/a" : item.reliability + "%", - registrationDate: formatDate(item.inquiry_date), - submissionDate: formatDate(item.submission_date) - }; - if (!isStudio) { - registrant.rating = item.rating; - registrant.colorStyle = helper.getColorStyle(item.rating); - } - return registrant; - }); - }; + return registrant; + }); + }; registrants = mapRegistrants(results); cb(); } @@ -3440,18 +3440,16 @@ var getSubmissions = function (api, connection, dbConnectionMap, isStudio, next) submission_type: [helper.SUBMISSION_TYPE.challenge.id, helper.SUBMISSION_TYPE.checkpoint.id] }; - async.parallel({ - privateCheck: execQuery("check_is_related_with_challenge"), - challengeStatus: execQuery("get_challenge_status") - }, cb); - }, function (result, cb) { - if (result.privateCheck[0].is_private && !result.privateCheck[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, + execQuery("get_challenge_status"), + function (result, cb) { // If the caller is not admin and challenge status is still active. - if (!helper.isAdmin(caller) && result.challengeStatus[0].challenge_status_id === 1) { + if (!helper.isAdmin(caller) && result[0].challenge_status_id === 1) { cb(new BadRequestError("The challenge is not finished.")); return; } @@ -3567,13 +3565,12 @@ var getPhases = function (api, connection, dbConnectionMap, isStudio, next) { }; // Do the private check. - api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); - }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { var execQuery = function (name) { return function (cbx) { api.dataAccess.executeQuery(name, sqlParams, dbConnectionMap, cbx); diff --git a/db_scripts/test_eligibility.insert.sql b/db_scripts/test_eligibility.insert.sql index 8bb746502..994746f08 100644 --- a/db_scripts/test_eligibility.insert.sql +++ b/db_scripts/test_eligibility.insert.sql @@ -150,6 +150,28 @@ INSERT INTO project_info (project_id, project_info_type_id, value, create_user, INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) VALUES (1110005, 2, "3330333", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 6, "Not private", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 6, "Old logic - access allowed", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 6, "Old logic - access denied", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 6, "New logic - access allowed", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 6, "New logic - access denied", "132456", CURRENT, "132456", CURRENT); + +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 26, "---", "132456", CURRENT, "132456", CURRENT); + INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) VALUES (1110001, 6, 3330333, "Not private", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) @@ -162,6 +184,7 @@ INSERT INTO project_info (project_id, project_info_type_id, value, create_user, VALUES (1110005, 6, 3330333, "New logic - access denied", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) +>>>>>>> upstream/dev VALUES (1110001, 79, "---", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) VALUES (1110002, 79, "---", "132456", CURRENT, "132456", CURRENT); diff --git a/docs/Verification_Guide-Improve Challenge Visibility Control.doc b/docs/Verification_Guide-Improve Challenge Visibility Control.doc index 1c2913aaec56e9f7acc508b92ae08a944ff0a3e0..fd53c55cca92514a1e3731a719129a08120a1bcc 100644 GIT binary patch delta 5309 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 Osw|eZj`bgG@%cA;d-3%E delta 3478 zcmZwK3s6+o83*w1?k*G&1QC!$+%6AcK|*-03QE){1=}KaI_N~DyrqUBMT%(*?D`ld z?WBr+hM|NhY2%~}LZe)}r9gcU5(U9pN^MFgW~?QWI?806jwP-1|LwYQ(d^yd-h0kH z_uR+f{CBbTYOT%M`go=2Y{FLUyhpRMv&BSI!e_OckLMdZb%C)F0eYea&=HklAEC{x zj#6E=_my&L{A*=gDmIO;#~Zj+{-*ny#@wjv+a(!_y}DRSv~@9&JN*!49UZEd z_1s#ikyn(Kj2yovQ1va2Q`B`C7Zk4CncJBe=9c=;zy1$TjA&z#H8d)8-8YEzIIZIp zQcv`D#?tA9sC&Aly5rluz6%mo;Yp&E^;Ximcl|gr&+xTqf4}nGcScy*n4((qniUP6 zsv$qz+dgR0qy6r)^TQU(Ai2ZrtPe$C?ZXH1YojqHG!aSk8rII&%_O2KG<_+Gs1lWf zSerOhwfW*$aF2sxw770X_v(MAh_t+Cvqz zm!9Wewk(>zm*QwQN=EYL&9#e_8s1W-x0Fy7mOUVQs;P{mrUcuC_$;CV`Vsa>ISY!> zXEFNJ8>{WD_h4@kj&UAoL}?FNe06(iS%9N=VSgHq`2iL9(#!V7d(n~ow7jJ@AgcnM z7*X$fX>vK$v=5&p=)y?lxReTP%Vn>m6f$Eiofl)hiW0CylefC+$KmsM+7if>Ey45U z03&7NpnbfsEN!{o=(yf;bSC%ed}66+6=W>OMeMSlL~o4cJ>N{j7Wh-xB(x5{}Q4=u)-+#h7iRqC3+P);Sorr7{CaUA=`}jbA%mm7$jzb zAQdv91}0$|3d8Wo42Y;ONQ84Bk@b)sJfNw+PtgcIMr5I3`Iw>$G&N;k^^k4YW*a8k zX?n-LJ&f`&_4MGBb6&jRbdP0?^g0ALz=}!L4MQ-p47Ub1z=S{e7>I>9Fhd~}Lp9XE zD^L$-;5=M}J{W_1%!op$f@-LNIyh*uII{H(6uO`r`rrx-!Vp}8^_Y7bAOkWX2a2H- zYTy^}Hk<*O;6pGDjWI+`&<3~QHr$12_#9>|_@L#;PSAlnG-6UWLr5G34?+<91~5Vx z#6UX87lM3+JhZ>CqG_6Z{2qI<`M7QxWA{wug-!89-+~>`1kG>?TA>ZjfepH#8}bq`E>H+zi9`_) z2_}e#4T%_=3~b~=9ay0r+MpeJp&aQ}0d;T@`alx%8eE4l7>5b?1mcmjiI5B%;3%Ac z))e#$?eJfC1Z2S%jRrg+24W!@Qeicu!&+Dmtu%J=F1{imxPO&mKv z{glRObeslg!0{&k(NVfIXU3qwUcWYR>w9bYM8{%FmYm84gD?c+pj&}IBM5|eNQCw9 z2oQ+m3z@J9z5}_i4f3HB%3wFh?~DqFz&BqnqUa7 z!5ECg1bhN_;4|<`Lx@2j1cCgR`S-oAc;BPR$q6hpNrOE$>Y!6CSehGU!IxnYcl|bl zv(+sAo$Ad)XMK5G4ObF7ZU4}C?3%qfe``?mGM?>g!TNPDB3>oTZ6J~B zj8=(NiBySFiBO45iAsq``PDh|@!6%{D^f>ekk3&xaPr^<@ZicOcdM+J0 z#+vt|IN?&1a>nrx2159~fgHA6PTcM<4JDT(k0ggAelL+Gh)1GU_^pS6vHztbFelcmH`KzwjsXE>Gzo z1Pow=Fo=LiFoDd{M%UvTTni^%3nyF)^{$1Z{6)WB`6+&rzRCLs^!(Y#_4(xC_-_V} kPgROT6u)P08CQBmQ95rt)G96NfBt4wlv4H8kB?~n4~Qp6qW}N^ diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 2460e3f17..66edae75f 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -374,7 +374,7 @@ exports.challengeHelper = function (api, next) { } else if (connection.caller.accessLevel === "anon") { next(new UnauthorizedError()); } else { - next(new ForbiddenError()); + next(new ForbiddenError('The user is not allowed to visit the challenge.')); } }); }); diff --git a/queries/check_is_related_with_challenge b/queries/check_is_related_with_challenge index 36014d75c..2e7034109 100644 --- a/queries/check_is_related_with_challenge +++ b/queries/check_is_related_with_challenge @@ -2,21 +2,21 @@ SELECT (SELECT max(1) FROM contest_eligibility ce - INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id - LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access -, (SELECT - 1 - FROM contest_eligibility ce - WHERE ce.contest_id = @challengeId@) AS is_private -, ( - SELECT +) AS is_private +, (SELECT decode(max(ri.value), null, null, 1) FROM resource r - INNER JOIN resource_info ri ON ri.resource_id = r.resource_id AND ri.resource_info_type_id = 1 + INNER JOIN resource_info ri ON ri.resource_id = r.resource_id AND ri.resource_info_type_id = 1 WHERE r.project_id = @challengeId@ - AND ri.value = @user_id@) AS is_related -, (SELECT max(project_metadata_id) FROM direct_project_metadata m, project p - WHERE metadata_value = @user_id@ AND p.tc_direct_project_id = m.tc_direct_project_id and p.project_id = @challengeId@ AND project_metadata_key_id IN (1, 2, 14)) AS is_manager + AND ri.value = @user_id@ +) AS is_related +, (SELECT + max(project_metadata_id) + FROM direct_project_metadata m, project p + WHERE metadata_value = @user_id@ + AND p.tc_direct_project_id = m.tc_direct_project_id + AND p.project_id = @challengeId@ + AND project_metadata_key_id IN (1, 2, 14) +) AS is_manager FROM dual diff --git a/test/postman/New_Challenge_Visibility_Control.postman_collection.json b/test/postman/New_Challenge_Visibility_Control.postman_collection.json index 7dadfd3d1..3c52fb3e4 100644 --- a/test/postman/New_Challenge_Visibility_Control.postman_collection.json +++ b/test/postman/New_Challenge_Visibility_Control.postman_collection.json @@ -4,6 +4,19 @@ "description": "", "order": [], "folders": [ + { + "id": "cada5a0c-766f-dde0-3c9f-d001a67eddd4", + "name": "Get challenge", + "description": "", + "order": [ + "c383cab7-3145-145e-9da9-846001755460", + "42b84596-9d5a-50e7-76be-c1ad23f98468", + "3246a996-e8f9-5e60-79b9-8aeffcd5392f", + "bf83e2d2-549b-361e-f5cf-66a40d816f0c", + "1af5c911-4627-ad92-085c-63e6fc7b6d9e" + ], + "owner": "316251" + }, { "id": "712ffa63-a959-e4a3-6af9-84d4f236b2f3", "name": "Get checkpoints", @@ -17,6 +30,47 @@ ], "owner": "316251" }, + { + "id": "6b9370a1-5974-a6a6-a961-67e73abaa861", + "name": "Get phases", + "description": "", + "order": [ + "c7d11de6-630a-71bd-4095-cd3c8fb8ab77", + "f5da62a7-9231-5f7a-f44a-f2f14c9ae003", + "d7a050dc-6eaa-f62e-24e4-37d111002d4a", + "c305f2ea-dbfd-f95f-c809-583133af5881", + "0461a7de-3ae1-f873-b667-50d04a43b317" + ], + "owner": "316251", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240" + }, + { + "id": "6a038555-23cd-e79f-1d34-0fb860e305a3", + "name": "Get registrants", + "description": "", + "order": [ + "bcc821a7-0e3a-3454-d900-12af0cc94656", + "70b3453b-1d1a-e411-f8e5-527edb0a2530", + "f73f4e00-c286-d440-ce79-89095d7354dd", + "e97dac4e-c786-27b1-5e4b-fff50b6de93a", + "b3cb44e7-3e5f-897e-5d6f-6179afc52653" + ], + "owner": "316251" + }, + { + "id": "2a873809-800c-ee71-51ad-94f10096709b", + "name": "Get submissions", + "description": "", + "order": [ + "f90179ed-98da-be6d-77ae-9e3aa4199b5c", + "f915c206-b3fe-a4be-1094-bc8a448cb467", + "d3e5ca45-334d-fb54-1fd7-46f8e7b82841", + "f8e9d38f-8d8d-6e63-4978-6e3546f20b7c", + "f8720a5a-5a8b-423c-065f-8d3a3469fbca" + ], + "owner": "316251", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240" + }, { "id": "cfbf928f-56b8-9813-f8f3-4ac4e342d965", "name": "Register for challenges", @@ -46,6 +100,62 @@ "owner": "316251", "public": false, "requests": [ + { + "id": "0461a7de-3ae1-f873-b667-50d04a43b317", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959637871, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "1af5c911-4627-ad92-085c-63e6fc7b6d9e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959273575, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", "headers": "Authorization: Bearer {{authToken}}\n", @@ -75,6 +185,62 @@ "responses": [], "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" }, + { + "id": "3246a996-e8f9-5e60-79b9-8aeffcd5392f", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497958076427, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "42b84596-9d5a-50e7-76be-c1ad23f98468", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497957969156, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "46cf305a-8251-66aa-391c-46def82773a1", "headers": "Authorization: Bearer {{authToken}}\n", @@ -214,6 +380,34 @@ "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" }, + { + "id": "70b3453b-1d1a-e411-f8e5-527edb0a2530", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934833132, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "7c7643c6-89ab-641e-b67a-32b3ac91e09e", "headers": "Authorization: Bearer {{authToken}}\n", @@ -304,7 +498,7 @@ "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" }, { - "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "id": "b3cb44e7-3e5f-897e-5d6f-6179afc52653", "headers": "Authorization: Bearer {{authToken}}\n", "headerData": [ { @@ -314,7 +508,7 @@ "enabled": true } ], - "url": "{{url}}/develop/challenges/checkpoint/2220002", + "url": "{{url}}/challenges/registrants/1110005", "queryParams": [], "preRequestScript": null, "pathVariables": {}, @@ -325,15 +519,45 @@ "tests": null, "currentHelper": "normal", "helperAttributes": {}, - "time": 1497550612717, - "name": "Old logic, access allowed", + "time": 1497935002619, + "name": "New logic, access denied", "description": "", "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "bcc821a7-0e3a-3454-d900-12af0cc94656", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497934405019, + "version": 2, "responses": [], - "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "6a038555-23cd-e79f-1d34-0fb860e305a3" }, { - "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "id": "bf83e2d2-549b-361e-f5cf-66a40d816f0c", "headers": "Authorization: Bearer {{authToken}}\n", "headerData": [ { @@ -343,7 +567,7 @@ "enabled": true } ], - "url": "{{url}}/develop/challenges/checkpoint/2220004", + "url": "{{url}}/challenges/1110004", "queryParams": [], "preRequestScript": null, "pathVariables": {}, @@ -354,12 +578,410 @@ "tests": null, "currentHelper": "normal", "helperAttributes": {}, - "time": 1497550705028, + "time": 1497958165136, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "c305f2ea-dbfd-f95f-c809-583133af5881", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959248881, "name": "New logic, access allowed", "description": "", "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "c383cab7-3145-145e-9da9-846001755460", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497957874624, + "version": 2, "responses": [], - "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "cada5a0c-766f-dde0-3c9f-d001a67eddd4" + }, + { + "id": "c7d11de6-630a-71bd-4095-cd3c8fb8ab77", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110001", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959147405, + "name": "No groups (challenge is not private)", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d3e5ca45-334d-fb54-1fd7-46f8e7b82841", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959455425, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d7a050dc-6eaa-f62e-24e4-37d111002d4a", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959220837, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550612717, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "e97dac4e-c786-27b1-5e4b-fff50b6de93a", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934940451, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550705028, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "f5da62a7-9231-5f7a-f44a-f2f14c9ae003", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959161340, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f73f4e00-c286-d440-ce79-89095d7354dd", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934860473, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f8720a5a-5a8b-423c-065f-8d3a3469fbca", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959508749, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f8e9d38f-8d8d-6e63-4978-6e3546f20b7c", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959483268, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f90179ed-98da-be6d-77ae-9e3aa4199b5c", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220001", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959423349, + "name": "No groups (challenge is not private)", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f915c206-b3fe-a4be-1094-bc8a448cb467", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959438513, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] }, { "id": "fd4cd936-2d4d-a272-f402-d0f7b6cab82f", From af2daa1edcc93390999e14bcedf8fa928d48590c Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 20 Jun 2017 22:41:33 +0800 Subject: [PATCH 38/67] revert commit --- tc-api.iml | 9 - tc-api.ipr | 82 --------- tc-api.iws | 495 ----------------------------------------------------- 3 files changed, 586 deletions(-) delete mode 100644 tc-api.iml delete mode 100644 tc-api.ipr delete mode 100644 tc-api.iws diff --git a/tc-api.iml b/tc-api.iml deleted file mode 100644 index 44b943bfa..000000000 --- a/tc-api.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tc-api.ipr b/tc-api.ipr deleted file mode 100644 index 92b3a0143..000000000 --- a/tc-api.ipr +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.8 - - - - - - - - \ No newline at end of file diff --git a/tc-api.iws b/tc-api.iws deleted file mode 100644 index 16b05a466..000000000 --- a/tc-api.iws +++ /dev/null @@ -1,495 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - localhost - 5050 - - - - - - - - - - 1467168622962 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 00bac230b30291822c2240bcb2a4023924150ac9 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 20 Jun 2017 10:42:56 -0400 Subject: [PATCH 39/67] More eligibility and group updates (#506) * Improve challenge visibility control (#501) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * improve the query * update query for groups (#502) * Update queries (#503) improve logging for v3 api call * should use externalToken field name * update queries for group checking * Improve challenge visibility control: getChallenge and getRegistrants (#504) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * Improve challenge visibility control: getChallenge and getRegistrants * revert commit --- actions/challenges.js | 95 ++- db_scripts/test_eligibility.insert.sql | 21 +- ...e-Improve Challenge Visibility Control.doc | Bin 52736 -> 56832 bytes initializers/challengeHelper.js | 2 +- queries/check_is_related_with_challenge | 26 +- queries/check_user_challenge_accessibility | 2 +- ...Visibility_Control.postman_collection.json | 640 +++++++++++++++++- 7 files changed, 708 insertions(+), 78 deletions(-) diff --git a/actions/challenges.js b/actions/challenges.js index 53266e7a8..bb4d6ceb9 100755 --- a/actions/challenges.js +++ b/actions/challenges.js @@ -80,8 +80,8 @@ * Changes in 1.31: * - Remove screeningScorecardId and reviewScorecardId from search challenges api. * Changes in 1.32: - * - validateChallenge function now checks if an user belongs to a group via - * user_group_xref for old challenges and by calling V3 API for new ones. + * - validateChallenge, getRegistrants, getChallenge, getSubmissions and getPhases functions now check + * if an user belongs to a group via user_group_xref for old challenges and by calling V3 API for new ones. */ "use strict"; /*jslint stupid: true, unparam: true, continue: true, nomen: true */ @@ -1081,19 +1081,20 @@ var getChallenge = function (api, connection, dbConnectionMap, isStudio, next) { }; // Do the private check. + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - if (result[0].is_manager) { isManager = true; } // If the user has the access to the challenge or is a resource for the challenge then he is related with this challenge. - if (result[0].has_access || result[0].is_related || isManager || helper.isAdmin(caller)) { + if (result[0].is_private || result[0].is_related || isManager || helper.isAdmin(caller)) { isRelated = true; } @@ -3342,33 +3343,32 @@ var getRegistrants = function (api, connection, dbConnectionMap, isStudio, next) }; // Do the private check. - api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); - }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { api.dataAccess.executeQuery('challenge_registrants', sqlParams, dbConnectionMap, cb); }, function (results, cb) { var mapRegistrants = function (results) { - if (!_.isDefined(results)) { - return []; + if (!_.isDefined(results)) { + return []; + } + return _.map(results, function (item) { + var registrant = { + handle: item.handle, + reliability: !_.isDefined(item.reliability) ? "n/a" : item.reliability + "%", + registrationDate: formatDate(item.inquiry_date), + submissionDate: formatDate(item.submission_date) + }; + if (!isStudio) { + registrant.rating = item.rating; + registrant.colorStyle = helper.getColorStyle(item.rating); } - return _.map(results, function (item) { - var registrant = { - handle: item.handle, - reliability: !_.isDefined(item.reliability) ? "n/a" : item.reliability + "%", - registrationDate: formatDate(item.inquiry_date), - submissionDate: formatDate(item.submission_date) - }; - if (!isStudio) { - registrant.rating = item.rating; - registrant.colorStyle = helper.getColorStyle(item.rating); - } - return registrant; - }); - }; + return registrant; + }); + }; registrants = mapRegistrants(results); cb(); } @@ -3440,18 +3440,16 @@ var getSubmissions = function (api, connection, dbConnectionMap, isStudio, next) submission_type: [helper.SUBMISSION_TYPE.challenge.id, helper.SUBMISSION_TYPE.checkpoint.id] }; - async.parallel({ - privateCheck: execQuery("check_is_related_with_challenge"), - challengeStatus: execQuery("get_challenge_status") - }, cb); - }, function (result, cb) { - if (result.privateCheck[0].is_private && !result.privateCheck[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, + execQuery("get_challenge_status"), + function (result, cb) { // If the caller is not admin and challenge status is still active. - if (!helper.isAdmin(caller) && result.challengeStatus[0].challenge_status_id === 1) { + if (!helper.isAdmin(caller) && result[0].challenge_status_id === 1) { cb(new BadRequestError("The challenge is not finished.")); return; } @@ -3567,13 +3565,12 @@ var getPhases = function (api, connection, dbConnectionMap, isStudio, next) { }; // Do the private check. - api.dataAccess.executeQuery('check_is_related_with_challenge', sqlParams, dbConnectionMap, cb); - }, function (result, cb) { - if (result[0].is_private && !result[0].has_access) { - cb(new UnauthorizedError('The user is not allowed to visit the challenge.')); - return; - } - + api.challengeHelper.checkUserChallengeEligibility( + connection, + connection.params.challengeId, + cb + ); + }, function (cb) { var execQuery = function (name) { return function (cbx) { api.dataAccess.executeQuery(name, sqlParams, dbConnectionMap, cbx); diff --git a/db_scripts/test_eligibility.insert.sql b/db_scripts/test_eligibility.insert.sql index 8bb746502..44a286bf0 100644 --- a/db_scripts/test_eligibility.insert.sql +++ b/db_scripts/test_eligibility.insert.sql @@ -151,16 +151,27 @@ INSERT INTO project_info (project_id, project_info_type_id, value, create_user, VALUES (1110005, 2, "3330333", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) - VALUES (1110001, 6, 3330333, "Not private", CURRENT, "132456", CURRENT); + VALUES (1110001, 6, "Not private", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) - VALUES (1110002, 6, 3330333, "Old logic - access allowed", CURRENT, "132456", CURRENT); + VALUES (1110002, 6, "Old logic - access allowed", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) - VALUES (1110003, 6, 3330333, "Old logic - access denied", CURRENT, "132456", CURRENT); + VALUES (1110003, 6, "Old logic - access denied", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) - VALUES (1110004, 6, 3330333, "New logic - access allowed", CURRENT, "132456", CURRENT); + VALUES (1110004, 6, "New logic - access allowed", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) - VALUES (1110005, 6, 3330333, "New logic - access denied", CURRENT, "132456", CURRENT); + VALUES (1110005, 6, "New logic - access denied", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110001, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110002, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110003, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110004, 26, "---", "132456", CURRENT, "132456", CURRENT); +INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) + VALUES (1110005, 26, "---", "132456", CURRENT, "132456", CURRENT); + INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) VALUES (1110001, 79, "---", "132456", CURRENT, "132456", CURRENT); INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) diff --git a/docs/Verification_Guide-Improve Challenge Visibility Control.doc b/docs/Verification_Guide-Improve Challenge Visibility Control.doc index 1c2913aaec56e9f7acc508b92ae08a944ff0a3e0..fd53c55cca92514a1e3731a719129a08120a1bcc 100644 GIT binary patch delta 5309 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 Osw|eZj`bgG@%cA;d-3%E delta 3478 zcmZwK3s6+o83*w1?k*G&1QC!$+%6AcK|*-03QE){1=}KaI_N~DyrqUBMT%(*?D`ld z?WBr+hM|NhY2%~}LZe)}r9gcU5(U9pN^MFgW~?QWI?806jwP-1|LwYQ(d^yd-h0kH z_uR+f{CBbTYOT%M`go=2Y{FLUyhpRMv&BSI!e_OckLMdZb%C)F0eYea&=HklAEC{x zj#6E=_my&L{A*=gDmIO;#~Zj+{-*ny#@wjv+a(!_y}DRSv~@9&JN*!49UZEd z_1s#ikyn(Kj2yovQ1va2Q`B`C7Zk4CncJBe=9c=;zy1$TjA&z#H8d)8-8YEzIIZIp zQcv`D#?tA9sC&Aly5rluz6%mo;Yp&E^;Ximcl|gr&+xTqf4}nGcScy*n4((qniUP6 zsv$qz+dgR0qy6r)^TQU(Ai2ZrtPe$C?ZXH1YojqHG!aSk8rII&%_O2KG<_+Gs1lWf zSerOhwfW*$aF2sxw770X_v(MAh_t+Cvqz zm!9Wewk(>zm*QwQN=EYL&9#e_8s1W-x0Fy7mOUVQs;P{mrUcuC_$;CV`Vsa>ISY!> zXEFNJ8>{WD_h4@kj&UAoL}?FNe06(iS%9N=VSgHq`2iL9(#!V7d(n~ow7jJ@AgcnM z7*X$fX>vK$v=5&p=)y?lxReTP%Vn>m6f$Eiofl)hiW0CylefC+$KmsM+7if>Ey45U z03&7NpnbfsEN!{o=(yf;bSC%ed}66+6=W>OMeMSlL~o4cJ>N{j7Wh-xB(x5{}Q4=u)-+#h7iRqC3+P);Sorr7{CaUA=`}jbA%mm7$jzb zAQdv91}0$|3d8Wo42Y;ONQ84Bk@b)sJfNw+PtgcIMr5I3`Iw>$G&N;k^^k4YW*a8k zX?n-LJ&f`&_4MGBb6&jRbdP0?^g0ALz=}!L4MQ-p47Ub1z=S{e7>I>9Fhd~}Lp9XE zD^L$-;5=M}J{W_1%!op$f@-LNIyh*uII{H(6uO`r`rrx-!Vp}8^_Y7bAOkWX2a2H- zYTy^}Hk<*O;6pGDjWI+`&<3~QHr$12_#9>|_@L#;PSAlnG-6UWLr5G34?+<91~5Vx z#6UX87lM3+JhZ>CqG_6Z{2qI<`M7QxWA{wug-!89-+~>`1kG>?TA>ZjfepH#8}bq`E>H+zi9`_) z2_}e#4T%_=3~b~=9ay0r+MpeJp&aQ}0d;T@`alx%8eE4l7>5b?1mcmjiI5B%;3%Ac z))e#$?eJfC1Z2S%jRrg+24W!@Qeicu!&+Dmtu%J=F1{imxPO&mKv z{glRObeslg!0{&k(NVfIXU3qwUcWYR>w9bYM8{%FmYm84gD?c+pj&}IBM5|eNQCw9 z2oQ+m3z@J9z5}_i4f3HB%3wFh?~DqFz&BqnqUa7 z!5ECg1bhN_;4|<`Lx@2j1cCgR`S-oAc;BPR$q6hpNrOE$>Y!6CSehGU!IxnYcl|bl zv(+sAo$Ad)XMK5G4ObF7ZU4}C?3%qfe``?mGM?>g!TNPDB3>oTZ6J~B zj8=(NiBySFiBO45iAsq``PDh|@!6%{D^f>ekk3&xaPr^<@ZicOcdM+J0 z#+vt|IN?&1a>nrx2159~fgHA6PTcM<4JDT(k0ggAelL+Gh)1GU_^pS6vHztbFelcmH`KzwjsXE>Gzo z1Pow=Fo=LiFoDd{M%UvTTni^%3nyF)^{$1Z{6)WB`6+&rzRCLs^!(Y#_4(xC_-_V} kPgROT6u)P08CQBmQ95rt)G96NfBt4wlv4H8kB?~n4~Qp6qW}N^ diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 2460e3f17..66edae75f 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -374,7 +374,7 @@ exports.challengeHelper = function (api, next) { } else if (connection.caller.accessLevel === "anon") { next(new UnauthorizedError()); } else { - next(new ForbiddenError()); + next(new ForbiddenError('The user is not allowed to visit the challenge.')); } }); }); diff --git a/queries/check_is_related_with_challenge b/queries/check_is_related_with_challenge index ab18cd096..010cb89f3 100644 --- a/queries/check_is_related_with_challenge +++ b/queries/check_is_related_with_challenge @@ -1,22 +1,22 @@ SELECT (SELECT - 1 + max(1) FROM contest_eligibility ce - INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id - INNER JOIN user_group_xref ugx ON ugx.group_id = gce.group_id WHERE ce.contest_id = @challengeId@ - AND ((ugx.login_id = @user_id@ AND gce.group_id < 2000000) OR gce.group_id >= 2000000)) AS has_access +) AS is_private , (SELECT - 1 - FROM contest_eligibility ce - WHERE ce.contest_id = @challengeId@) AS is_private -, ( - SELECT decode(max(ri.value), null, null, 1) FROM resource r - INNER JOIN resource_info ri ON ri.resource_id = r.resource_id AND ri.resource_info_type_id = 1 + INNER JOIN resource_info ri ON ri.resource_id = r.resource_id AND ri.resource_info_type_id = 1 WHERE r.project_id = @challengeId@ - AND ri.value = @user_id@) AS is_related -, (SELECT max(project_metadata_id) FROM direct_project_metadata m, project p - WHERE metadata_value = @user_id@ AND p.tc_direct_project_id = m.tc_direct_project_id and p.project_id = @challengeId@ AND project_metadata_key_id IN (1, 2, 14)) AS is_manager + AND ri.value = @user_id@ +) AS is_related +, (SELECT + max(project_metadata_id) + FROM direct_project_metadata m, project p + WHERE metadata_value = @user_id@ + AND p.tc_direct_project_id = m.tc_direct_project_id + AND p.project_id = @challengeId@ + AND project_metadata_key_id IN (1, 2, 14) +) AS is_manager FROM dual diff --git a/queries/check_user_challenge_accessibility b/queries/check_user_challenge_accessibility index e7d7e9b14..309258da0 100644 --- a/queries/check_user_challenge_accessibility +++ b/queries/check_user_challenge_accessibility @@ -1,6 +1,6 @@ SELECT (SELECT - 1 + max(1) FROM contest_eligibility ce INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id LEFT JOIN user_group_xref ugx ON ugx.group_id = gce.group_id diff --git a/test/postman/New_Challenge_Visibility_Control.postman_collection.json b/test/postman/New_Challenge_Visibility_Control.postman_collection.json index 7dadfd3d1..3c52fb3e4 100644 --- a/test/postman/New_Challenge_Visibility_Control.postman_collection.json +++ b/test/postman/New_Challenge_Visibility_Control.postman_collection.json @@ -4,6 +4,19 @@ "description": "", "order": [], "folders": [ + { + "id": "cada5a0c-766f-dde0-3c9f-d001a67eddd4", + "name": "Get challenge", + "description": "", + "order": [ + "c383cab7-3145-145e-9da9-846001755460", + "42b84596-9d5a-50e7-76be-c1ad23f98468", + "3246a996-e8f9-5e60-79b9-8aeffcd5392f", + "bf83e2d2-549b-361e-f5cf-66a40d816f0c", + "1af5c911-4627-ad92-085c-63e6fc7b6d9e" + ], + "owner": "316251" + }, { "id": "712ffa63-a959-e4a3-6af9-84d4f236b2f3", "name": "Get checkpoints", @@ -17,6 +30,47 @@ ], "owner": "316251" }, + { + "id": "6b9370a1-5974-a6a6-a961-67e73abaa861", + "name": "Get phases", + "description": "", + "order": [ + "c7d11de6-630a-71bd-4095-cd3c8fb8ab77", + "f5da62a7-9231-5f7a-f44a-f2f14c9ae003", + "d7a050dc-6eaa-f62e-24e4-37d111002d4a", + "c305f2ea-dbfd-f95f-c809-583133af5881", + "0461a7de-3ae1-f873-b667-50d04a43b317" + ], + "owner": "316251", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240" + }, + { + "id": "6a038555-23cd-e79f-1d34-0fb860e305a3", + "name": "Get registrants", + "description": "", + "order": [ + "bcc821a7-0e3a-3454-d900-12af0cc94656", + "70b3453b-1d1a-e411-f8e5-527edb0a2530", + "f73f4e00-c286-d440-ce79-89095d7354dd", + "e97dac4e-c786-27b1-5e4b-fff50b6de93a", + "b3cb44e7-3e5f-897e-5d6f-6179afc52653" + ], + "owner": "316251" + }, + { + "id": "2a873809-800c-ee71-51ad-94f10096709b", + "name": "Get submissions", + "description": "", + "order": [ + "f90179ed-98da-be6d-77ae-9e3aa4199b5c", + "f915c206-b3fe-a4be-1094-bc8a448cb467", + "d3e5ca45-334d-fb54-1fd7-46f8e7b82841", + "f8e9d38f-8d8d-6e63-4978-6e3546f20b7c", + "f8720a5a-5a8b-423c-065f-8d3a3469fbca" + ], + "owner": "316251", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240" + }, { "id": "cfbf928f-56b8-9813-f8f3-4ac4e342d965", "name": "Register for challenges", @@ -46,6 +100,62 @@ "owner": "316251", "public": false, "requests": [ + { + "id": "0461a7de-3ae1-f873-b667-50d04a43b317", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959637871, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "1af5c911-4627-ad92-085c-63e6fc7b6d9e", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959273575, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "2af8f0d9-f3e8-c58a-ca3d-1130e4b07371", "headers": "Authorization: Bearer {{authToken}}\n", @@ -75,6 +185,62 @@ "responses": [], "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" }, + { + "id": "3246a996-e8f9-5e60-79b9-8aeffcd5392f", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497958076427, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "42b84596-9d5a-50e7-76be-c1ad23f98468", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497957969156, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "46cf305a-8251-66aa-391c-46def82773a1", "headers": "Authorization: Bearer {{authToken}}\n", @@ -214,6 +380,34 @@ "rawModeData": "{\n \"username\": \"heffan\", \n \"password\": \"password\"\n}", "folder": "0eeb693c-c6b6-e23b-156d-cff5f21dbb27" }, + { + "id": "70b3453b-1d1a-e411-f8e5-527edb0a2530", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934833132, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, { "id": "7c7643c6-89ab-641e-b67a-32b3ac91e09e", "headers": "Authorization: Bearer {{authToken}}\n", @@ -304,7 +498,7 @@ "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" }, { - "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "id": "b3cb44e7-3e5f-897e-5d6f-6179afc52653", "headers": "Authorization: Bearer {{authToken}}\n", "headerData": [ { @@ -314,7 +508,7 @@ "enabled": true } ], - "url": "{{url}}/develop/challenges/checkpoint/2220002", + "url": "{{url}}/challenges/registrants/1110005", "queryParams": [], "preRequestScript": null, "pathVariables": {}, @@ -325,15 +519,45 @@ "tests": null, "currentHelper": "normal", "helperAttributes": {}, - "time": 1497550612717, - "name": "Old logic, access allowed", + "time": 1497935002619, + "name": "New logic, access denied", "description": "", "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "bcc821a7-0e3a-3454-d900-12af0cc94656", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497934405019, + "version": 2, "responses": [], - "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "6a038555-23cd-e79f-1d34-0fb860e305a3" }, { - "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "id": "bf83e2d2-549b-361e-f5cf-66a40d816f0c", "headers": "Authorization: Bearer {{authToken}}\n", "headerData": [ { @@ -343,7 +567,7 @@ "enabled": true } ], - "url": "{{url}}/develop/challenges/checkpoint/2220004", + "url": "{{url}}/challenges/1110004", "queryParams": [], "preRequestScript": null, "pathVariables": {}, @@ -354,12 +578,410 @@ "tests": null, "currentHelper": "normal", "helperAttributes": {}, - "time": 1497550705028, + "time": 1497958165136, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "c305f2ea-dbfd-f95f-c809-583133af5881", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959248881, "name": "New logic, access allowed", "description": "", "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "c383cab7-3145-145e-9da9-846001755460", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/1110001", + "queryParams": [], + "pathVariables": {}, + "pathVariableData": [], + "preRequestScript": null, + "method": "GET", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "data": null, + "dataMode": "params", + "name": "No groups (challenge is not private)", + "description": "", + "descriptionFormat": "html", + "time": 1497957874624, + "version": 2, "responses": [], - "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "folder": "cada5a0c-766f-dde0-3c9f-d001a67eddd4" + }, + { + "id": "c7d11de6-630a-71bd-4095-cd3c8fb8ab77", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110001", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959147405, + "name": "No groups (challenge is not private)", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d3e5ca45-334d-fb54-1fd7-46f8e7b82841", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959455425, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d7a050dc-6eaa-f62e-24e4-37d111002d4a", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959220837, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "d830ec36-eb8e-9586-c546-14af77cec152", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550612717, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "e97dac4e-c786-27b1-5e4b-fff50b6de93a", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934940451, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f545bbfc-36d7-6567-25a8-b4d6634575e7", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/develop/challenges/checkpoint/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497550705028, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [], + "folder": "712ffa63-a959-e4a3-6af9-84d4f236b2f3" + }, + { + "id": "f5da62a7-9231-5f7a-f44a-f2f14c9ae003", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/phases/1110002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959161340, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f73f4e00-c286-d440-ce79-89095d7354dd", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/registrants/1110003", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497934860473, + "name": "Old logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f8720a5a-5a8b-423c-065f-8d3a3469fbca", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220005", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959508749, + "name": "New logic, access denied", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f8e9d38f-8d8d-6e63-4978-6e3546f20b7c", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220004", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959483268, + "name": "New logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f90179ed-98da-be6d-77ae-9e3aa4199b5c", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220001", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959423349, + "name": "No groups (challenge is not private)", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] + }, + { + "id": "f915c206-b3fe-a4be-1094-bc8a448cb467", + "headers": "Authorization: Bearer {{authToken}}\n", + "headerData": [ + { + "key": "Authorization", + "value": "Bearer {{authToken}}", + "description": "", + "enabled": true + } + ], + "url": "{{url}}/challenges/submissions/2220002", + "queryParams": [], + "preRequestScript": null, + "pathVariables": {}, + "pathVariableData": [], + "method": "GET", + "data": null, + "dataMode": "params", + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1497959438513, + "name": "Old logic, access allowed", + "description": "", + "collectionId": "ba962be9-0d58-f187-8809-008a39bc2240", + "responses": [] }, { "id": "fd4cd936-2d4d-a272-f402-d0f7b6cab82f", From 57a54928729c10028d4ddbdf46e3c2c57eb7e540 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 20 Jun 2017 22:00:42 -0400 Subject: [PATCH 40/67] fixed issue with is_studio check --- actions/challengeRegistration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 9424951ef..149a40d96 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -897,7 +897,7 @@ exports.registerChallenge = { cb(); return; } - var isStudio = result[0].isStudio !== 0; + var isStudio = result[0].is_studio !== 0; api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, function (err) { cb(err, isStudio); }); From d6c922d99d833282f52a35b8ee180d0ebb7e0f17 Mon Sep 17 00:00:00 2001 From: skyhit Date: Wed, 21 Jun 2017 10:36:47 +0800 Subject: [PATCH 41/67] More eligibility and group updates (#506) (#507) * Improve challenge visibility control (#501) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * improve the query * update query for groups (#502) * Update queries (#503) improve logging for v3 api call * should use externalToken field name * update queries for group checking * Improve challenge visibility control: getChallenge and getRegistrants (#504) * IMPROVE CHALLENGE VISIBILITY CONTROL (https://www.topcoder.com/challenge-details/30057891/?type=develop) Verification guide: docs/Verification_Guide-Improve Challenge Visibility Control.doc * Restoring an accidentially modified file * Fixed the case with a challenge that doesn't have eligibility * Shared the eligibility verification with challengeRegistration. The eligibility check routine is now in challengeHelper and can be added anywhere by a couple of simple lines of code. * Improve challenge visibility control: getChallenge and getRegistrants * revert commit From e8a0d71357e3496d784818cfaa1aaf241ae85fb7 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 00:32:50 +0800 Subject: [PATCH 42/67] fix social provider id --- initializers/.dataAccess.js.swp | Bin 28672 -> 0 bytes initializers/helper.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 initializers/.dataAccess.js.swp diff --git a/initializers/.dataAccess.js.swp b/initializers/.dataAccess.js.swp deleted file mode 100644 index 542d73f7eba4b7e4272e91f9d0283b665d384cbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 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 diff --git a/initializers/helper.js b/initializers/helper.js index e9961b888..b4ca38b54 100755 --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1272,7 +1272,7 @@ helper.getProviderId = function (provider, callback) { if (provider.startsWith("ad") || provider.startsWith("auth0")) { providerId = helper.socialProviders.ad; } - if (provider.startsWith("samlp")) { + if (provider.startsWith("samlp") || provider.startsWith("adfs")) { providerId = helper.socialProviders.samlp; } if (providerId) { From 3cdd7669a3588822ccb9e045dfae279a2ce7e083 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 00:39:53 +0800 Subject: [PATCH 43/67] support adfs provider --- initializers/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/helper.js b/initializers/helper.js index e9961b888..b4ca38b54 100755 --- a/initializers/helper.js +++ b/initializers/helper.js @@ -1272,7 +1272,7 @@ helper.getProviderId = function (provider, callback) { if (provider.startsWith("ad") || provider.startsWith("auth0")) { providerId = helper.socialProviders.ad; } - if (provider.startsWith("samlp")) { + if (provider.startsWith("samlp") || provider.startsWith("adfs")) { providerId = helper.socialProviders.samlp; } if (providerId) { From 8c5509ceeaa8fc1d1bf5e05a0d78fdde555dd6e5 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 09:16:48 +0800 Subject: [PATCH 44/67] check v3 token expiration --- initializers/v3client.js | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index df8312a37..b1e620288 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -12,6 +12,7 @@ var request = require('request'); var _ = require('underscore'); var async = require('async'); +var tcAccounts = require('tc-accounts'); /** * The URL of the V3 API @@ -63,7 +64,7 @@ function getToken(connection, callback) { return; } // Cached token - if (!_.isUndefined(tokens[connection.authToken])) { + if (!_.isUndefined(tokens[connection.authToken]) && !tcAccounts.isTokenExpired(tokens[connection.authToken])) { callback(null, tokens[connection.authToken]); return; } diff --git a/package.json b/package.json index c3e6dfc2a..84605d1dd 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "validator": "~3.5.0", "wkhtmltoimage": ">= 0.1.3", "xml2js": "0.2.x", - "xtend": "2.1.x" + "xtend": "2.1.x", + "tc-accounts": "https://github.com/appirio-tech/accounts-app#dev" }, "devDependencies": { "supertest": "0.8.x", From a2820eef03d3f6e2eea56a702d1c6ba285156f90 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 09:38:07 +0800 Subject: [PATCH 45/67] check v3 token expiration --- initializers/v3client.js | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index df8312a37..b1e620288 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -12,6 +12,7 @@ var request = require('request'); var _ = require('underscore'); var async = require('async'); +var tcAccounts = require('tc-accounts'); /** * The URL of the V3 API @@ -63,7 +64,7 @@ function getToken(connection, callback) { return; } // Cached token - if (!_.isUndefined(tokens[connection.authToken])) { + if (!_.isUndefined(tokens[connection.authToken]) && !tcAccounts.isTokenExpired(tokens[connection.authToken])) { callback(null, tokens[connection.authToken]); return; } diff --git a/package.json b/package.json index c3e6dfc2a..84605d1dd 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "validator": "~3.5.0", "wkhtmltoimage": ">= 0.1.3", "xml2js": "0.2.x", - "xtend": "2.1.x" + "xtend": "2.1.x", + "tc-accounts": "https://github.com/appirio-tech/accounts-app#dev" }, "devDependencies": { "supertest": "0.8.x", From 650f8b8dcba19f0f31f80e74ef25533191cf430f Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 09:57:46 +0800 Subject: [PATCH 46/67] copy over token exipiration check logic --- initializers/v3client.js | 65 ++++++++++++++++++++++++++++++++++++++-- package.json | 3 +- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index b1e620288..c9b389aeb 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -12,7 +12,6 @@ var request = require('request'); var _ = require('underscore'); var async = require('async'); -var tcAccounts = require('tc-accounts'); /** * The URL of the V3 API @@ -64,7 +63,7 @@ function getToken(connection, callback) { return; } // Cached token - if (!_.isUndefined(tokens[connection.authToken]) && !tcAccounts.isTokenExpired(tokens[connection.authToken])) { + if (!_.isUndefined(tokens[connection.authToken]) && !isTokenExpired(tokens[connection.authToken])) { callback(null, tokens[connection.authToken]); return; } @@ -87,6 +86,68 @@ function getToken(connection, callback) { }); } + +function urlBase64Decode(str) { + var output = str.replace(/-/g, '+').replace(/_/g, '/'); + + switch (output.length % 4) { + case 0: + break; + + case 2: + output += '=='; + break; + + case 3: + output += '='; + break; + + default: + throw 'Illegal base64url string!' + } + return decodeURIComponent(escape(atob(output)));//polyfill https://github.com/davidchambers/Base64.js +} + +function decodeToken(token) { + var parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('The token is invalid') + } + + var decoded = urlBase64Decode(parts[1]); + + if (!decoded) { + throw new Error('Cannot decode the token') + } + + return JSON.parse(decoded) +} + +function getTokenExpirationDate(token) { + var decoded = decodeToken(token); + + if(typeof decoded.exp === 'undefined') { + return null + } + + var d = new Date(0);// The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + + return d +} + +function isTokenExpired(token) { + var d = getTokenExpirationDate(token); + + if (d === null) { + return false + } + + // Token expired? + return !(d.valueOf() > (new Date().valueOf())) +} + /** * Get IDs of users in the specified group * diff --git a/package.json b/package.json index 84605d1dd..c3e6dfc2a 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,7 @@ "validator": "~3.5.0", "wkhtmltoimage": ">= 0.1.3", "xml2js": "0.2.x", - "xtend": "2.1.x", - "tc-accounts": "https://github.com/appirio-tech/accounts-app#dev" + "xtend": "2.1.x" }, "devDependencies": { "supertest": "0.8.x", From 15c7f74998ddb672f0f8358c16f65213f1691dff Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 10:28:29 +0800 Subject: [PATCH 47/67] add atob --- initializers/v3client.js | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index c9b389aeb..765fc2a0e 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -12,6 +12,7 @@ var request = require('request'); var _ = require('underscore'); var async = require('async'); +var atob = require('atob'); /** * The URL of the V3 API diff --git a/package.json b/package.json index c3e6dfc2a..d5470d6df 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "validator": "~3.5.0", "wkhtmltoimage": ">= 0.1.3", "xml2js": "0.2.x", - "xtend": "2.1.x" + "xtend": "2.1.x", + "atob": "2.0.3" }, "devDependencies": { "supertest": "0.8.x", From c50710c02b82c8147756007b04fd7095879270b8 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Tue, 25 Jul 2017 10:43:23 +0800 Subject: [PATCH 48/67] update check token expiration logic --- initializers/v3client.js | 66 ++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index b1e620288..21db566dc 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -12,7 +12,7 @@ var request = require('request'); var _ = require('underscore'); var async = require('async'); -var tcAccounts = require('tc-accounts'); +var atob = require('atob'); /** * The URL of the V3 API @@ -64,7 +64,7 @@ function getToken(connection, callback) { return; } // Cached token - if (!_.isUndefined(tokens[connection.authToken]) && !tcAccounts.isTokenExpired(tokens[connection.authToken])) { + if (!_.isUndefined(tokens[connection.authToken]) && !isTokenExpired(tokens[connection.authToken])) { callback(null, tokens[connection.authToken]); return; } @@ -87,6 +87,68 @@ function getToken(connection, callback) { }); } +function urlBase64Decode(str) { + var output = str.replace(/-/g, '+').replace(/_/g, '/'); + + switch (output.length % 4) { + case 0: + break; + + case 2: + output += '=='; + break; + + case 3: + output += '='; + break; + + default: + throw 'Illegal base64url string!' + } + return decodeURIComponent(escape(atob(output)));//polyfill https://github.com/davidchambers/Base64.js +} + +function decodeToken(token) { + var parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('The token is invalid') + } + + var decoded = urlBase64Decode(parts[1]); + + if (!decoded) { + throw new Error('Cannot decode the token') + } + + return JSON.parse(decoded) +} + +function getTokenExpirationDate(token) { + var decoded = decodeToken(token); + + if(typeof decoded.exp === 'undefined') { + return null + } + + var d = new Date(0);// The 0 here is the key, which sets the date to the epoch + d.setUTCSeconds(decoded.exp); + + return d +} + +function isTokenExpired(token) { + var d = getTokenExpirationDate(token); + + if (d === null) { + return false + } + + // Token expired? + return !(d.valueOf() > (new Date().valueOf())) +} + + /** * Get IDs of users in the specified group * diff --git a/package.json b/package.json index 84605d1dd..d5470d6df 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "wkhtmltoimage": ">= 0.1.3", "xml2js": "0.2.x", "xtend": "2.1.x", - "tc-accounts": "https://github.com/appirio-tech/accounts-app#dev" + "atob": "2.0.3" }, "devDependencies": { "supertest": "0.8.x", From 329d561267e311815c918c067e0c725051d21211 Mon Sep 17 00:00:00 2001 From: ajefts Date: Tue, 25 Jul 2017 13:55:48 -0400 Subject: [PATCH 49/67] fix is_studio check --- actions/challengeRegistration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 9424951ef..149a40d96 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -897,7 +897,7 @@ exports.registerChallenge = { cb(); return; } - var isStudio = result[0].isStudio !== 0; + var isStudio = result[0].is_studio !== 0; api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, function (err) { cb(err, isStudio); }); From 8d6f97f1d603275b767adafed45622762cee20f0 Mon Sep 17 00:00:00 2001 From: ajefts Date: Mon, 2 Oct 2017 11:22:21 -0400 Subject: [PATCH 50/67] updating activation URL --- actions/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/user.js b/actions/user.js index 88bece40f..b8f7101f9 100644 --- a/actions/user.js +++ b/actions/user.js @@ -233,7 +233,7 @@ function userActivationEmail(api, connection, next) { toAddress : rs[0].address, fromAddress : process.env.TC_EMAIL_ACCOUNT, senderName : activationEmailSenderName, - url : process.env.TC_ACTIVATION_SERVER_NAME + '/reg2/activate.action?code=' + activationCode, + url : process.env.TC_ACTIVATION_SERVER_NAME + '?code=' + activationCode + '&retUrl=https://www.topcoder.com/skill-picker', userHandle : rs[0].handle }, 'default'); api.cache.save(cacheKey, currentResendTimes + 1, api.config.tcConfig.userActivationCacheLifeTime, From 7507828dd087e39b482393127456b1b3d88417af Mon Sep 17 00:00:00 2001 From: ajefts Date: Mon, 2 Oct 2017 11:23:29 -0400 Subject: [PATCH 51/67] updated activation URL --- actions/memberRegistration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/memberRegistration.js b/actions/memberRegistration.js index cb75d89cf..789bcb61f 100644 --- a/actions/memberRegistration.js +++ b/actions/memberRegistration.js @@ -396,7 +396,7 @@ var registerUser = function (user, api, dbConnectionMap, next) { }, function (callback) { var url; - url = process.env.TC_ACTIVATION_SERVER_NAME + '/reg2/activate.action?code=' + activationCode; + url = process.env.TC_ACTIVATION_SERVER_NAME + '?code=' + activationCode + '&retUrl=https://www.topcoder.com/skill-picker'; if (user.regSource && user.regSource.match(/arena/)) { url += '&destination=http%3A%2F%2Farena.topcoder.com'; } From 61b025c87b06c54250e1a37605d1bdb85fff6c30 Mon Sep 17 00:00:00 2001 From: TonyJ Date: Mon, 2 Oct 2017 11:32:43 -0400 Subject: [PATCH 52/67] update activation url --- actions/challengeRegistration.js | 2 +- deploy/ci.sh | 3 ++- deploy/development.sh | 3 ++- deploy/vm.sh | 3 ++- local/env.sh | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actions/challengeRegistration.js b/actions/challengeRegistration.js index 149a40d96..0d558f628 100644 --- a/actions/challengeRegistration.js +++ b/actions/challengeRegistration.js @@ -404,7 +404,7 @@ var sendNotificationEmail = function (api, componentInfo, userId, activeForumCat if (challengeType === CHALLENGE_TYPE.DEVELOP) { forumURL = api.config.tcConfig.developForumsUrlPrefix + activeForumCategoryId; reviewURL = process.env.TC_SOFTWARE_SERVER_NAME + '/review/actions/ViewProjectDetails?pid=' + challengeId; - submitURL = process.env.TC_ACTIVATION_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; + submitURL = process.env.TC_WWW_SERVER_NAME + '/challenge-details/' + challengeId + '/submit/?type=develop'; } else if (challengeType === CHALLENGE_TYPE.DESIGN) { forumURL = api.config.tcConfig.studioForumsUrlPrefix + activeForumCategoryId; //submitURL = process.env.TC_STUDIO_SERVER_NAME + '/?module=ViewContestDetails&ct=' + challengeId; diff --git a/deploy/ci.sh b/deploy/ci.sh index a0143cf31..e3cfb2f49 100644 --- a/deploy/ci.sh +++ b/deploy/ci.sh @@ -58,7 +58,8 @@ export TC_EMAIL_PASSWORD=tc_public_email export TC_EMAIL_FROM=tc.ldap.test@gmail.com export TC_EMAIL_TEMPLATE_DIR=mail_templates -export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_ACTIVATION_SERVER_NAME="https://api.topcoder.com/pub/activation.html" +export TC_WWW_SERVER_NAME="https://www.topcoder.com" export TC_SOFTWARE_SERVER_NAME="https://www.topcoder.com" #export DISABLE_CONSOLE_LOG=true diff --git a/deploy/development.sh b/deploy/development.sh index 11f08580c..7ec784d66 100755 --- a/deploy/development.sh +++ b/deploy/development.sh @@ -60,7 +60,8 @@ export TC_EMAIL_PASSWORD=tc_public_email export TC_EMAIL_FROM=tc.ldap.test.1@gmail.com export TC_EMAIL_TEMPLATE_DIR=mail_templates -export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_ACTIVATION_SERVER_NAME="https://api.topcoder.com/pub/activation.html" +export TC_WWW_SERVER_NAME="https://www.topcoder.com" export TC_SOFTWARE_SERVER_NAME="https://software.topcoder.com" export TC_FORUMS_SERVER_NAME="http://apps.topcoder.com/forums" diff --git a/deploy/vm.sh b/deploy/vm.sh index 5f75cefe6..a193827d3 100644 --- a/deploy/vm.sh +++ b/deploy/vm.sh @@ -54,7 +54,8 @@ export TC_EMAIL_PASSWORD=tc_public_email export TC_EMAIL_FROM=tc.ldap.test.1@gmail.com export TC_EMAIL_TEMPLATE_DIR=mail_templates -export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_ACTIVATION_SERVER_NAME="https://api.topcoder.com/pub/activation.html" +export TC_WWW_SERVER_NAME="https://www.topcoder.com" export TC_SOFTWARE_SERVER_NAME="https://software.topcoder.com" export TC_FORUMS_SERVER_NAME="http://apps.topcoder.com/forums" diff --git a/local/env.sh b/local/env.sh index ac4e6ab9c..2c2556c28 100644 --- a/local/env.sh +++ b/local/env.sh @@ -58,7 +58,8 @@ export TC_EMAIL_PASSWORD=tc_public_email export TC_EMAIL_FROM=tc.ldap.test.1@gmail.com export TC_EMAIL_TEMPLATE_DIR=mail_templates -export TC_ACTIVATION_SERVER_NAME="https://www.topcoder.com" +export TC_ACTIVATION_SERVER_NAME="https://api.topcoder.com/pub/activation.html" +export TC_WWW_SERVER_NAME="https://www.topcoder.com" export TC_SOFTWARE_SERVER_NAME="https://software.topcoder.com" export TC_FORUMS_SERVER_NAME="http://apps.topcoder.com/forums" From 42a7ecaebecbb7c5fd8c6df8813b688bf7539c56 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Fri, 13 Oct 2017 23:25:33 +0800 Subject: [PATCH 53/67] revise user challenge eligibility check --- initializers/v3client.js | 70 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 765fc2a0e..b99d1435f 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -181,23 +181,85 @@ function getGroupMembers(connection, groupId, callback) { }); } + +/** + * Get groups that the current user can access. + * + * @param {Object} connection - the connection object provided by ActionHero + * @param {Function} callback - the callback. Receives either an error + * or the list of group's users an array of numeric IDs + */ +function getMemberGroups(connection, callback) { + getToken(connection, function (err, token) { + if (err) { + callback(err); + return; + } + + var userId = (connection.caller.userId || 0); + + // calls + callService({ + url: v3url + 'groups?membershipType=user&memberId=' + userId, + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + token + } + }, function (err, body) { + if (err) { + callback(err); + } else { + var groupIds = body.result.content.map(function (item) { + return item.id; + }); + + var result = []; + + groupIds.forEach(function(groupId) { + result.push(groupId); + callService({ + url: v3url + 'groups/' + groupId + '/getParentGroup?oneLevel=false', + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + token + } + }, function (err, body) { + if (err) { + callback(err); + } else { + var groupResponse = body.result.content; + while(groupResponse) { + result.push(groupResponse.id); + groupResponse = groupResponse.parentGroup; + } + } + }) + }); + + callback(null, result); + } + }); + }); +} + + + exports.v3client = function (api, next) { api.v3client = { /** * Check if the user belongs to the group * * @param {Object} connection - the connection object provided by ActionHero - * @param {Number} userId - the user ID * @param {Number} groupId - the group ID * @param {Function} callback - the callback. The second parameter * is boolean vwhich is true if the user is found in the group. */ - isUserInGroup: function (connection, userId, groupId, callback) { - getGroupMembers(connection, groupId, function (err, members) { + isUserInGroup: function (connection, groupId, callback) { + getMemberGroups(connection, groupId, function (err, groupIds) { if (err) { callback(err); } else { - callback(null, members.indexOf(userId) >= 0); + callback(null, groupIds.indexOf(groupId) >= 0); } }); } From 92cf6d88d6e81bf5fbde8ba56543a8b41772d763 Mon Sep 17 00:00:00 2001 From: skyhit Date: Fri, 13 Oct 2017 10:51:14 -0500 Subject: [PATCH 54/67] Revert "revise user challenge eligibility check" --- initializers/v3client.js | 70 +++------------------------------------- 1 file changed, 4 insertions(+), 66 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index b99d1435f..765fc2a0e 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -181,85 +181,23 @@ function getGroupMembers(connection, groupId, callback) { }); } - -/** - * Get groups that the current user can access. - * - * @param {Object} connection - the connection object provided by ActionHero - * @param {Function} callback - the callback. Receives either an error - * or the list of group's users an array of numeric IDs - */ -function getMemberGroups(connection, callback) { - getToken(connection, function (err, token) { - if (err) { - callback(err); - return; - } - - var userId = (connection.caller.userId || 0); - - // calls - callService({ - url: v3url + 'groups?membershipType=user&memberId=' + userId, - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + token - } - }, function (err, body) { - if (err) { - callback(err); - } else { - var groupIds = body.result.content.map(function (item) { - return item.id; - }); - - var result = []; - - groupIds.forEach(function(groupId) { - result.push(groupId); - callService({ - url: v3url + 'groups/' + groupId + '/getParentGroup?oneLevel=false', - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + token - } - }, function (err, body) { - if (err) { - callback(err); - } else { - var groupResponse = body.result.content; - while(groupResponse) { - result.push(groupResponse.id); - groupResponse = groupResponse.parentGroup; - } - } - }) - }); - - callback(null, result); - } - }); - }); -} - - - exports.v3client = function (api, next) { api.v3client = { /** * Check if the user belongs to the group * * @param {Object} connection - the connection object provided by ActionHero + * @param {Number} userId - the user ID * @param {Number} groupId - the group ID * @param {Function} callback - the callback. The second parameter * is boolean vwhich is true if the user is found in the group. */ - isUserInGroup: function (connection, groupId, callback) { - getMemberGroups(connection, groupId, function (err, groupIds) { + isUserInGroup: function (connection, userId, groupId, callback) { + getGroupMembers(connection, groupId, function (err, members) { if (err) { callback(err); } else { - callback(null, groupIds.indexOf(groupId) >= 0); + callback(null, members.indexOf(userId) >= 0); } }); } From e710cb37b270c012029889d977a9e6fc6426617a Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 00:05:16 +0800 Subject: [PATCH 55/67] fix api call --- initializers/v3client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index b99d1435f..750aec259 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -255,7 +255,7 @@ exports.v3client = function (api, next) { * is boolean vwhich is true if the user is found in the group. */ isUserInGroup: function (connection, groupId, callback) { - getMemberGroups(connection, groupId, function (err, groupIds) { + getMemberGroups(connection, function (err, groupIds) { if (err) { callback(err); } else { From d1cf1168699bf7463e397bcb9ee7bca2e680619c Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 00:23:06 +0800 Subject: [PATCH 56/67] fix api call --- .gitignore | 3 +++ initializers/challengeHelper.js | 2 +- initializers/v3client.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d5f8cf869..6f4d22e1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +*.iml +*.ipr +*.iws *.log log pids diff --git a/initializers/challengeHelper.js b/initializers/challengeHelper.js index 66edae75f..a8cc3331c 100644 --- a/initializers/challengeHelper.js +++ b/initializers/challengeHelper.js @@ -357,7 +357,7 @@ exports.challengeHelper = function (api, next) { cbx(!(_.isNull(record.user_group_xref_found) || _.isUndefined(record.user_group_xref_found))); } else { // New challenges: query the V3 API - api.v3client.isUserInGroup(connection, userId, record.group_id, function (err, result) { + api.v3client.isUserInGroup(connection, record.group_id, function (err, result) { if (err) { error = err; cbx(true); diff --git a/initializers/v3client.js b/initializers/v3client.js index 750aec259..542a8ef00 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -252,7 +252,7 @@ exports.v3client = function (api, next) { * @param {Object} connection - the connection object provided by ActionHero * @param {Number} groupId - the group ID * @param {Function} callback - the callback. The second parameter - * is boolean vwhich is true if the user is found in the group. + * is boolean which is true if the user has group id in challenge groups. */ isUserInGroup: function (connection, groupId, callback) { getMemberGroups(connection, function (err, groupIds) { From 1d936cf5c90b95ee11448c45520530fbad634c19 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 00:43:30 +0800 Subject: [PATCH 57/67] add debug code --- initializers/v3client.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/initializers/v3client.js b/initializers/v3client.js index 542a8ef00..d71e90d11 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -236,6 +236,7 @@ function getMemberGroups(connection, callback) { }) }); + console.log("member groups: " + result); callback(null, result); } }); @@ -259,6 +260,8 @@ exports.v3client = function (api, next) { if (err) { callback(err); } else { + console.log("challenge group id: " + groupId); + callback(null, groupIds.indexOf(groupId) >= 0); } }); From 257fcdaa9661d67c49ec9e95cd332f14c33566f2 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:08:13 +0800 Subject: [PATCH 58/67] more logging --- initializers/v3client.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/initializers/v3client.js b/initializers/v3client.js index d71e90d11..328c436bb 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -228,9 +228,11 @@ function getMemberGroups(connection, callback) { callback(err); } else { var groupResponse = body.result.content; + console.log(groupResponse); while(groupResponse) { result.push(groupResponse.id); groupResponse = groupResponse.parentGroup; + console.log(groupResponse); } } }) From ab15e6ceef8dd046c31a04435db8e1c165cb7dd8 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:14:40 +0800 Subject: [PATCH 59/67] rename variables --- initializers/v3client.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 328c436bb..0307da1bd 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -213,10 +213,10 @@ function getMemberGroups(connection, callback) { return item.id; }); - var result = []; + var memberGroups = []; groupIds.forEach(function(groupId) { - result.push(groupId); + memberGroups.push(groupId); callService({ url: v3url + 'groups/' + groupId + '/getParentGroup?oneLevel=false', method: 'GET', @@ -230,7 +230,7 @@ function getMemberGroups(connection, callback) { var groupResponse = body.result.content; console.log(groupResponse); while(groupResponse) { - result.push(groupResponse.id); + memberGroups.push(groupResponse.id); groupResponse = groupResponse.parentGroup; console.log(groupResponse); } @@ -238,8 +238,8 @@ function getMemberGroups(connection, callback) { }) }); - console.log("member groups: " + result); - callback(null, result); + console.log("member groups: " + memberGroups); + callback(null, memberGroups); } }); }); From c349f6bb0de2a7182f773dfb6e00661d8f351504 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:20:43 +0800 Subject: [PATCH 60/67] revise logging --- initializers/v3client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 0307da1bd..7bb462031 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -228,11 +228,11 @@ function getMemberGroups(connection, callback) { callback(err); } else { var groupResponse = body.result.content; - console.log(groupResponse); while(groupResponse) { + console.log("group id to add: " + groupResponse.id); memberGroups.push(groupResponse.id); + console.log("member group ids: " + memberGroups); groupResponse = groupResponse.parentGroup; - console.log(groupResponse); } } }) From f2b95ea317baebefb06e7f82c836530baa265875 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:39:20 +0800 Subject: [PATCH 61/67] only callback when all calls return --- initializers/v3client.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 7bb462031..40ce13aa5 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -216,7 +216,6 @@ function getMemberGroups(connection, callback) { var memberGroups = []; groupIds.forEach(function(groupId) { - memberGroups.push(groupId); callService({ url: v3url + 'groups/' + groupId + '/getParentGroup?oneLevel=false', method: 'GET', @@ -224,6 +223,9 @@ function getMemberGroups(connection, callback) { 'Authorization': 'Bearer ' + token } }, function (err, body) { + var idx = groupIds.indexOf(groupId); + groupIds.splice(idx, 1); + if (err) { callback(err); } else { @@ -234,12 +236,15 @@ function getMemberGroups(connection, callback) { console.log("member group ids: " + memberGroups); groupResponse = groupResponse.parentGroup; } + + if (groupIds.length == 0) { + console.log("member groups: " + memberGroups); + callback(null, memberGroups); + } + } }) }); - - console.log("member groups: " + memberGroups); - callback(null, memberGroups); } }); }); From fa0976a0fa74afb2c3dbe2728acec076bdac8e36 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:47:22 +0800 Subject: [PATCH 62/67] logging check --- initializers/v3client.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 40ce13aa5..b1ff18800 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -231,14 +231,11 @@ function getMemberGroups(connection, callback) { } else { var groupResponse = body.result.content; while(groupResponse) { - console.log("group id to add: " + groupResponse.id); memberGroups.push(groupResponse.id); - console.log("member group ids: " + memberGroups); groupResponse = groupResponse.parentGroup; } if (groupIds.length == 0) { - console.log("member groups: " + memberGroups); callback(null, memberGroups); } @@ -267,7 +264,11 @@ exports.v3client = function (api, next) { if (err) { callback(err); } else { - console.log("challenge group id: " + groupId); + console.log("member groups:"); + console.log(groupIds); + console.log("challenge group id:"); + console.log(groupId); + console.log(groupIds.indexOf(groupId) >= 0); callback(null, groupIds.indexOf(groupId) >= 0); } From 13d14cf8ce5ad4ed3b1721e627d77de553d50289 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 01:50:50 +0800 Subject: [PATCH 63/67] conver to group id string to looku --- initializers/v3client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index b1ff18800..83287817c 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -270,7 +270,7 @@ exports.v3client = function (api, next) { console.log(groupId); console.log(groupIds.indexOf(groupId) >= 0); - callback(null, groupIds.indexOf(groupId) >= 0); + callback(null, groupIds.indexOf("" + groupId) >= 0); } }); } From 6fef896d586401a9ca4bfac633c1fb7fcde34238 Mon Sep 17 00:00:00 2001 From: Guiqiang Zhang Date: Sat, 14 Oct 2017 02:00:28 +0800 Subject: [PATCH 64/67] remove debug code --- initializers/v3client.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/initializers/v3client.js b/initializers/v3client.js index 83287817c..c429842b5 100644 --- a/initializers/v3client.js +++ b/initializers/v3client.js @@ -264,12 +264,6 @@ exports.v3client = function (api, next) { if (err) { callback(err); } else { - console.log("member groups:"); - console.log(groupIds); - console.log("challenge group id:"); - console.log(groupId); - console.log(groupIds.indexOf(groupId) >= 0); - callback(null, groupIds.indexOf("" + groupId) >= 0); } }); From f4dcc35a842a06a819419c27df59b73cf27e0109 Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 15 Nov 2017 12:39:17 -0500 Subject: [PATCH 65/67] remove inefficient join This query was pulling in a redundant row for every user that agrees to the give terms_id. WIth 30k plus rows, this was really inefficient and locking up resources. --- queries/challenge_terms_of_use_noauth | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/challenge_terms_of_use_noauth b/queries/challenge_terms_of_use_noauth index 315661747..e50cacdfa 100644 --- a/queries/challenge_terms_of_use_noauth +++ b/queries/challenge_terms_of_use_noauth @@ -6,7 +6,7 @@ SELECT tou.terms_of_use_id as terms_of_use_id, FROM project_role_terms_of_use_xref INNER JOIN terms_of_use tou ON project_role_terms_of_use_xref.terms_of_use_id = tou.terms_of_use_id INNER JOIN terms_of_use_agreeability_type_lu touat ON touat.terms_of_use_agreeability_type_id = tou.terms_of_use_agreeability_type_id -LEFT JOIN user_terms_of_use_xref utuox ON utuox.terms_of_use_id = tou.terms_of_use_id +--LEFT JOIN user_terms_of_use_xref utuox ON utuox.terms_of_use_id = tou.terms_of_use_id LEFT JOIN terms_of_use_docusign_template_xref dtx ON dtx.terms_of_use_id = project_role_terms_of_use_xref.terms_of_use_id WHERE project_id = @challengeId@ AND resource_role_id IN (@resourceRoleIds@) From 84c642193c80bdefe7a26fc1667d9a949692fc9c Mon Sep 17 00:00:00 2001 From: TonyJ Date: Mon, 11 Dec 2017 11:40:34 -0500 Subject: [PATCH 66/67] remove case sensitive validation for sso login id --- queries/get_user_by_sso_login | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/get_user_by_sso_login b/queries/get_user_by_sso_login index 1f2257a3e..5deb249fa 100644 --- a/queries/get_user_by_sso_login +++ b/queries/get_user_by_sso_login @@ -2,4 +2,4 @@ SELECT s.user_id FROM user_sso_login s JOIN sso_login_provider p ON s.provider_id = p.sso_login_provider_id WHERE p.name = '@auth_connection@' -AND s.sso_user_id = '@sso_user_id@' +AND lower(s.sso_user_id) = lower('@sso_user_id@') From cd7919de6bb59ad1a78d9bdf2cdd970469eeda59 Mon Sep 17 00:00:00 2001 From: ajefts Date: Wed, 7 Mar 2018 17:18:47 -0500 Subject: [PATCH 67/67] exclude assigned tasks from rss --- queries/get_software_studio_challenges_rss | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/get_software_studio_challenges_rss b/queries/get_software_studio_challenges_rss index ad49d3bc6..addf73084 100644 --- a/queries/get_software_studio_challenges_rss +++ b/queries/get_software_studio_challenges_rss @@ -16,4 +16,5 @@ AND p.project_status_id IN (@project_status_id@) AND pcl.project_type_id IN (@project_type_id@) AND pp1.phase_status_id IN (@registration_phase_status@) AND p.project_category_id NOT IN (27, 37) +AND not exists (select 1 from resource r, project_info pi82 where r.project_id = p.project_id and r.resource_role_id = 1 and p.project_id = pi82.project_id and project_info_type_id = 82 and pi82.value = 1) -- exclude assigned tasks ORDER BY challenge_id ASC