Skip to content

Commit 97b0671

Browse files
committed
Merge branch 'dev' of github.com:topcoder-platform/challenge-api into dev
2 parents 885de82 + 48ab1fd commit 97b0671

File tree

8 files changed

+1410
-275
lines changed

8 files changed

+1410
-275
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ workflows:
8686
branches:
8787
only:
8888
- dev
89+
- feature/top-262-projectid-non-mandatory
8990

9091
- "build-qa":
9192
context: org-global

config/default.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ module.exports = {
9292
UPDATE: process.env.SCOPE_CHALLENGES_UPDATE || "update:challenges",
9393
DELETE: process.env.SCOPE_CHALLENGES_DELETE || "delete:challenges",
9494
ALL: process.env.SCOPE_CHALLENGES_ALL || "all:challenges",
95+
PAYMENT: process.env.SCOPE_PAYMENT || "create:payments",
9596
},
9697

9798
DEFAULT_CONFIDENTIALITY_TYPE: process.env.DEFAULT_CONFIDENTIALITY_TYPE || "public",
@@ -129,5 +130,8 @@ module.exports = {
129130
GRPC_CHALLENGE_SERVER_HOST: process.env.GRPC_DOMAIN_CHALLENGE_SERVER_HOST || "localhost",
130131
GRPC_CHALLENGE_SERVER_PORT: process.env.GRPC_DOMAIN_CHALLENGE_SERVER_PORT || 8888,
131132
GRPC_ACL_SERVER_HOST: process.env.GRPC_ACL_SERVER_HOST || "localhost",
132-
GRPC_ACL_SERVER_PORT: process.env.GRPC_ACL_SERVER_PORT || 8889,
133+
GRPC_ACL_SERVER_PORT: process.env.GRPC_ACL_SERVER_PORT || 40020,
134+
135+
SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID:
136+
process.env.SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID || "517e76b0-8824-4e72-9b48-a1ebde1793a8",
133137
};

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"dev": "nodemon app.js",
99
"lint": "prettier src/**/*.js",
1010
"lint:fix": "prettier --write src/**/*.js",
11+
"standard-lint": "standard",
12+
"standard-lint:fix": "standard --fix",
1113
"init-es": "node src/init-es.js",
1214
"init-db": "node src/init-db.js",
1315
"sync-es": "node src/scripts/sync-es.js",
@@ -37,13 +39,14 @@
3739
"mocha-prepare": "^0.1.0",
3840
"nodemon": "^2.0.20",
3941
"nyc": "^14.0.0",
40-
"prettier": "^2.8.1"
42+
"prettier": "^2.8.1",
43+
"standard": "^17.1.0"
4144
},
4245
"dependencies": {
4346
"@grpc/grpc-js": "^1.8.12",
4447
"@opensearch-project/opensearch": "^2.2.0",
45-
"@topcoder-framework/domain-challenge": "^0.24.1",
4648
"@topcoder-framework/domain-acl": "^0.24.0",
49+
"@topcoder-framework/domain-challenge": "^0.24.1",
4750
"@topcoder-framework/lib-common": "^0.24.1",
4851
"aws-sdk": "^2.1145.0",
4952
"axios": "^0.19.0",

src/common/challenge-helper.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,11 @@ class ChallengeHelper {
169169

170170
async validateCreateChallengeRequest(currentUser, challenge) {
171171
// projectId is required for non self-service challenges
172-
if (challenge.legacy.selfService == null && challenge.projectId == null) {
172+
if (
173+
challenge.legacy.selfService == null &&
174+
challenge.projectId == null &&
175+
this.isProjectIdRequired(challenge.timelineTemplateId)
176+
) {
173177
throw new errors.BadRequestError("projectId is required for non self-service challenges.");
174178
}
175179

@@ -524,6 +528,12 @@ class ChallengeHelper {
524528
delete overview.totalPrizesInCents;
525529
}
526530
}
531+
532+
isProjectIdRequired(timelineTemplateId) {
533+
const template = _.get(config, "SKIP_PROJECT_ID_BY_TIMLINE_TEMPLATE_ID", '517e76b0-8824-4e72-9b48-a1ebde1793a8');
534+
535+
return template !== timelineTemplateId;
536+
}
527537
}
528538

529539
module.exports = new ChallengeHelper();

src/controllers/ChallengeController.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,26 @@ async function updateChallenge(req, res) {
104104
res.send(result);
105105
}
106106

107+
/**
108+
* Update Legacy Payout (Updates informixoltp:payment_detail)
109+
* This has no effect other than to keep DW in sync for looker with
110+
* Updates that happen in Wallet
111+
*/
112+
async function updateLegacyPayout(req, res) {
113+
logger.debug(
114+
`updateLegacyPayout User: ${JSON.stringify(req.authUser)} - ChallengeID: ${
115+
req.params.challengeId
116+
} - Body: ${JSON.stringify(req.body)}`
117+
);
118+
const result = await service.updateLegacyPayout(
119+
req,
120+
req.authUser,
121+
req.params.challengeId,
122+
req.body
123+
);
124+
res.send(result);
125+
}
126+
107127
/**
108128
* Delete challenge
109129
* @param {Object} req the request
@@ -152,6 +172,7 @@ module.exports = {
152172
createChallenge,
153173
getChallenge,
154174
updateChallenge,
175+
updateLegacyPayout,
155176
deleteChallenge,
156177
getChallengeStatistics,
157178
sendNotifications,

src/routes.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
const constants = require("../app-constants");
66
const {
7-
SCOPES: { READ, CREATE, UPDATE, DELETE, ALL },
7+
SCOPES: { PAYMENT, READ, CREATE, UPDATE, DELETE, ALL },
88
} = require("config");
99

1010
module.exports = {
@@ -112,6 +112,14 @@ module.exports = {
112112
scopes: [UPDATE, ALL],
113113
},
114114
},
115+
"/challenges/:challengeId/legacy-payment": {
116+
patch: {
117+
controller: "ChallengeController",
118+
method: "updateLegacyPayout",
119+
auth: "jwt",
120+
scopes: [PAYMENT],
121+
},
122+
},
115123
"/challenges/:challengeId/statistics": {
116124
get: {
117125
controller: "ChallengeController",

src/services/ChallengeService.js

Lines changed: 159 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ async function createChallenge(currentUser, challenge, userToken) {
941941
console.log("TYPE", prizeTypeTmp);
942942
if (challenge.legacy.selfService) {
943943
// if self-service, create a new project (what about if projectId is provided in the payload? confirm with business!)
944-
if (!challenge.projectId) {
944+
if (!challenge.projectId && challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)) {
945945
const selfServiceProjectName = `Self service - ${currentUser.handle} - ${challenge.name}`;
946946
challenge.projectId = await helper.createSelfServiceProject(
947947
selfServiceProjectName,
@@ -963,14 +963,18 @@ async function createChallenge(currentUser, challenge, userToken) {
963963
}
964964

965965
/** Ensure project exists, and set direct project id, billing account id & markup */
966-
const { projectId } = challenge;
966+
if (challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)) {
967+
const { projectId } = challenge;
967968

968-
const { directProjectId } = await projectHelper.getProject(projectId, currentUser);
969-
const { billingAccountId, markup } = await projectHelper.getProjectBillingInformation(projectId);
969+
const { directProjectId } = await projectHelper.getProject(projectId, currentUser);
970+
const { billingAccountId, markup } = await projectHelper.getProjectBillingInformation(
971+
projectId
972+
);
970973

971-
_.set(challenge, "legacy.directProjectId", directProjectId);
972-
_.set(challenge, "billing.billingAccountId", billingAccountId);
973-
_.set(challenge, "billing.markup", markup || 0);
974+
_.set(challenge, "legacy.directProjectId", directProjectId);
975+
_.set(challenge, "billing.billingAccountId", billingAccountId);
976+
_.set(challenge, "billing.markup", markup || 0);
977+
}
974978

975979
if (!_.isUndefined(_.get(challenge, "legacy.reviewType"))) {
976980
_.set(challenge, "legacy.reviewType", _.toUpper(_.get(challenge, "legacy.reviewType")));
@@ -1510,19 +1514,22 @@ async function updateChallenge(currentUser, challengeId, data) {
15101514
convertPrizeSetValuesToDollars(challenge.prizeSets, challenge.overview);
15111515
}
15121516

1513-
const projectId = _.get(challenge, "projectId");
1517+
let projectId, billingAccountId, markup;
1518+
if (challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)) {
1519+
projectId = _.get(challenge, "projectId");
15141520

1515-
const { billingAccountId, markup } = await projectHelper.getProjectBillingInformation(projectId);
1521+
({ billingAccountId, markup } = await projectHelper.getProjectBillingInformation(projectId));
15161522

1517-
if (billingAccountId && _.isUndefined(_.get(challenge, "billing.billingAccountId"))) {
1518-
_.set(data, "billing.billingAccountId", billingAccountId);
1519-
_.set(data, "billing.markup", markup || 0);
1520-
}
1523+
if (billingAccountId && _.isUndefined(_.get(challenge, "billing.billingAccountId"))) {
1524+
_.set(data, "billing.billingAccountId", billingAccountId);
1525+
_.set(data, "billing.markup", markup || 0);
1526+
}
15211527

1522-
// Make sure the user cannot change the direct project ID
1523-
if (data.legacy) {
1524-
data.legacy = _.assign({}, challenge.legacy, data.legacy);
1525-
_.set(data, "legacy.directProjectId", challenge.legacy.directProjectId);
1528+
// Make sure the user cannot change the direct project ID
1529+
if (data.legacy) {
1530+
data.legacy = _.assign({}, challenge.legacy, data.legacy);
1531+
_.set(data, "legacy.directProjectId", challenge.legacy.directProjectId);
1532+
}
15261533
}
15271534

15281535
// Remove fields from data that are not allowed to be updated and that match the existing challenge
@@ -1565,7 +1572,8 @@ async function updateChallenge(currentUser, challengeId, data) {
15651572
if (
15661573
(data.status === constants.challengeStatuses.Approved ||
15671574
data.status === constants.challengeStatuses.Active) &&
1568-
challenge.status !== constants.challengeStatuses.Active
1575+
challenge.status !== constants.challengeStatuses.Active &&
1576+
challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)
15691577
) {
15701578
try {
15711579
const selfServiceProjectName = `Self service - ${challenge.createdBy} - ${challenge.name}`;
@@ -1601,7 +1609,10 @@ async function updateChallenge(currentUser, challengeId, data) {
16011609
}
16021610
}
16031611

1604-
if (data.status === constants.challengeStatuses.Draft) {
1612+
if (
1613+
data.status === constants.challengeStatuses.Draft &&
1614+
challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)
1615+
) {
16051616
try {
16061617
await helper.updateSelfServiceProjectInfo(
16071618
projectId,
@@ -1614,8 +1625,9 @@ async function updateChallenge(currentUser, challengeId, data) {
16141625
}
16151626

16161627
if (
1617-
data.status === constants.challengeStatuses.CancelledRequirementsInfeasible ||
1618-
data.status === constants.challengeStatuses.CancelledPaymentFailed
1628+
(data.status === constants.challengeStatuses.CancelledRequirementsInfeasible ||
1629+
data.status === constants.challengeStatuses.CancelledPaymentFailed) &&
1630+
challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)
16191631
) {
16201632
try {
16211633
await helper.cancelProject(challenge.projectId, data.cancelReason, currentUser);
@@ -1635,7 +1647,8 @@ async function updateChallenge(currentUser, challengeId, data) {
16351647
// if activating a challenge, the challenge must have a billing account id
16361648
if (
16371649
(!billingAccountId || billingAccountId === null) &&
1638-
challenge.status === constants.challengeStatuses.Draft
1650+
challenge.status === constants.challengeStatuses.Draft &&
1651+
challengeHelper.isProjectIdRequired(challenge.timelineTemplateId)
16391652
) {
16401653
throw new errors.BadRequestError(
16411654
"Cannot Activate this project, it has no active billing account."
@@ -2152,7 +2165,7 @@ updateChallenge.schema = {
21522165
.unknown(true)
21532166
)
21542167
.optional(),
2155-
overview: Joi.any().forbidden(),
2168+
overview: Joi.any().forbidden()
21562169
})
21572170
.unknown(true)
21582171
.required(),
@@ -2479,6 +2492,128 @@ async function indexChallengeAndPostToKafka(updatedChallenge, track, type) {
24792492
});
24802493
}
24812494

2495+
async function updateLegacyPayout(currentUser, challengeId, data) {
2496+
console.log(`Update legacy payment data for challenge: ${challengeId} with data: `, data);
2497+
const challenge = await challengeDomain.lookup(getLookupCriteria("id", challengeId));
2498+
2499+
// SQL qurey to fetch the payment and payment_detail record
2500+
let sql = `SELECT * FROM informixoltp:payment p
2501+
INNER JOIN informixoltp:payment_detail pd ON p.most_recent_detail_id = pd.payment_detail_id
2502+
WHERE p.user_id = ${data.userId} AND`;
2503+
2504+
if (challenge.legacyId != null) {
2505+
sql += ` pd.component_project_id = ${challenge.legacyId}`;
2506+
} else {
2507+
sql += ` pd.jira_issue_id = \'${challengeId}\'`;
2508+
}
2509+
2510+
sql += " ORDER BY pd.payment_detail_id ASC";
2511+
2512+
console.log("Fetch legacy payment detail: ", sql);
2513+
2514+
const result = await aclQueryDomain.rawQuery({ sql });
2515+
let updateClauses = [`date_modified = current`];
2516+
2517+
const statusMap = {
2518+
Paid: 53,
2519+
OnHold: 55,
2520+
OnHoldAdmin: 55,
2521+
Owed: 56,
2522+
Cancelled: 65,
2523+
EnteredIntoPaymentSystem: 70,
2524+
};
2525+
2526+
if (data.status != null) {
2527+
updateClauses.push(`payment_status_id = ${statusMap[data.status]}`);
2528+
if (data.status === "Paid") {
2529+
updateClauses.push(`date_paid = '${data.datePaid}'`);
2530+
} else {
2531+
updateClauses.push("date_paid = null");
2532+
}
2533+
}
2534+
2535+
if (data.releaseDate != null) {
2536+
updateClauses.push(`date_due = '${data.releaseDate}'`);
2537+
}
2538+
2539+
const paymentDetailIds = result.rows.map(
2540+
(row) => row.fields.find((field) => field.key === "payment_detail_id").value
2541+
);
2542+
2543+
if (data.amount != null) {
2544+
updateClauses.push(`total_amount = ${data.amount}`);
2545+
if (paymentDetailIds.length === 1) {
2546+
updateClauses.push(`net_amount = ${data.amount}`);
2547+
updateClauses.push(`gross_amount = ${data.amount}`);
2548+
}
2549+
}
2550+
2551+
if (paymentDetailIds.length === 0) {
2552+
return {
2553+
success: false,
2554+
message: "No payment detail record found",
2555+
};
2556+
}
2557+
2558+
const whereClause = [`payment_detail_id IN (${paymentDetailIds.join(",")})`];
2559+
2560+
const updateQuery = `UPDATE informixoltp:payment_detail SET ${updateClauses.join(
2561+
", "
2562+
)} WHERE ${whereClause.join(" AND ")}`;
2563+
2564+
console.log("Update Clauses", updateClauses);
2565+
console.log("Update Query", updateQuery);
2566+
2567+
await aclQueryDomain.rawQuery({ sql: updateQuery });
2568+
2569+
if (data.amount != null) {
2570+
if (paymentDetailIds.length > 1) {
2571+
const amountInCents = data.amount * 100;
2572+
2573+
const split1Cents = Math.round(amountInCents * 0.75);
2574+
const split2Cents = amountInCents - split1Cents;
2575+
2576+
const split1Dollars = Number((split1Cents / 100).toFixed(2));
2577+
const split2Dollars = Number((split2Cents / 100).toFixed(2));
2578+
2579+
const paymentUpdateQueries = paymentDetailIds.map((paymentDetailId, index) => {
2580+
let amt = 0;
2581+
if (index === 0) {
2582+
amt = split1Dollars;
2583+
}
2584+
if (index === 1) {
2585+
amt = split2Dollars;
2586+
}
2587+
2588+
return `UPDATE informixoltp:payment_detail SET date_modified = CURRENT, net_amount = ${amt}, gross_amount = ${amt} WHERE payment_detail_id = ${paymentDetailId}`;
2589+
});
2590+
2591+
console.log("Payment Update Queries", paymentUpdateQueries);
2592+
2593+
await Promise.all(
2594+
paymentUpdateQueries.map((query) => aclQueryDomain.rawQuery({ sql: query }))
2595+
);
2596+
}
2597+
}
2598+
2599+
return {
2600+
success: true,
2601+
message: "Successfully updated legacy payout",
2602+
};
2603+
}
2604+
updateLegacyPayout.schema = {
2605+
currentUser: Joi.any(),
2606+
challengeId: Joi.id(),
2607+
data: Joi.object()
2608+
.keys({
2609+
userId: Joi.number().integer().positive().required(),
2610+
amount: Joi.number().allow(null),
2611+
status: Joi.string().allow(null),
2612+
datePaid: Joi.string().allow(null),
2613+
releaseDate: Joi.string().allow(null),
2614+
})
2615+
};
2616+
24822617
/**
24832618
* Get SRM Schedule
24842619
* @param {Object} criteria the criteria
@@ -2543,6 +2678,7 @@ module.exports = {
25432678
getChallenge,
25442679
updateChallenge,
25452680
deleteChallenge,
2681+
updateLegacyPayout,
25462682
getChallengeStatistics,
25472683
sendNotifications,
25482684
advancePhase,

0 commit comments

Comments
 (0)