Skip to content

Commit 9eacfed

Browse files
rakibansaryliuliquaneisbilir
authored
fix: save markup in legacy database (#61)
* fix: save markup Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix(deps): update @topcoder-framework packages for bug fixes Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * feat: add payment attribute to challenge * use this attribute to hold non winner payouts such as * copilot & reviewer payments Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * feat: add payment attribute to challenge * use this attribute to hold non winner payouts such as * copilot & reviewer payments Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * feat: save project payments Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: add payments to schema Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * PLAT-3221 lock/consume budget (#57) Co-authored-by: Rakib Ansary <rakibansary@topcoder.com> * feat: add challenge estimator Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: use 0 as reviewer prize when placement prizes are missing Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: do not call lockConsumeAmount for completed challenges Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: keepalive and connection management Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * chore(domain-acl): add additional logging Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * chore(domain-acl): add additional logging Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * feat: generate payments * this is a stopgap solution * payment generation should be moved to * a processor that is triggered through challenge completion * events published to Harmony Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: pinned typescript version Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: use updated topcoder-framework/* * fix: allow copilot prize of 0 Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * feat: generate payments for tasks * this is a stopgap solution * payment generation should be moved to * a processor that is triggered through challenge completion * events published to Harmony Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: generate task payments only when task is completed Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: generate task payments only when task is completed Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: incorrect url Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: incorrect overview calculation * overview should only include placement prizes * iterative reviewer payment is review board payment Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: duplicate budget lock/unlock calls * acl calls domain-challenge:update with status:completed * and there are other such calls - this update ensures * budget lock/unlcok call is made only when there is an * actual change in prize setrs Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: take markup into account when locking/unlocking budget Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: typo Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: typo Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: unlock budget on task completion Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: unlock budget on challenge cancellation Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: unlock budget on challenge cancellation Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: set correct enddate for tasks Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * fix: include m2m token when creating payments Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> * chore: use release version Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> --------- Signed-off-by: Rakib Ansary <rakibansary@topcoder.com> Co-authored-by: liuliquan <lester.castro@wipro.com> Co-authored-by: eisbilir <72204071+eisbilir@users.noreply.github.com>
1 parent e616fbd commit 9eacfed

36 files changed

+4195
-2886
lines changed

bin/nosql-client.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ const { execSync } = require("child_process");
55

66
const PROTO_DIR = path.join(
77
__dirname,
8-
"../node_modules/topcoder-interface/data-access-layer/nosql"
8+
"../node_modules/topcoder-proto-registry/data-access-layer/nosql"
99
);
1010
const MODEL_DIR = path.join(__dirname, "../src/dal/models/nosql/");
1111

1212
const PROTOC_PATH = "protoc";
13-
const PLUGIN_PATH = path.join(
14-
__dirname,
15-
"../node_modules/.bin/protoc-gen-ts_proto"
16-
);
13+
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");
1714

1815
rimraf.sync(`${MODEL_DIR}/*`, {
1916
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },

bin/server.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@ const rimraf = require("rimraf");
33

44
const { execSync } = require("child_process");
55

6-
const PROTO_DIR = path.join(__dirname, "../node_modules/topcoder-interface");
6+
const PROTO_DIR = path.join(__dirname, "../node_modules/topcoder-proto-registry");
77
const PROTO_REFLECTIONS = path.join(__dirname, "../reflections/reflection.bin");
88

99
const MODEL_DIR = path.join(__dirname, "../src/models/");
1010

1111
const PROTOC_PATH = "protoc";
12-
const PLUGIN_PATH = path.join(
13-
__dirname,
14-
"../node_modules/.bin/protoc-gen-ts_proto"
15-
);
12+
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");
1613

1714
rimraf.sync(`${MODEL_DIR}/*`, {
1815
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },

package.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
"@aws-sdk/util-utf8-node": "^3.259.0",
2424
"@grpc/grpc-js": "^1.7.1",
2525
"@opensearch-project/opensearch": "^2.2.0",
26-
"@topcoder-framework/domain-acl": "^0.23.1-PLAT-3614.0",
27-
"@topcoder-framework/lib-common": "^0.23.1-PLAT-3614.0",
28-
"topcoder-interface": "0.1.0-PLAT-3614.1",
26+
"@topcoder-framework/domain-acl": "0.24",
27+
"@topcoder-framework/lib-common": "0.24",
28+
"topcoder-proto-registry": "0.1.0",
2929
"@types/uuid": "^9.0.1",
3030
"aws-sdk": "^2.1339.0",
3131
"axios": "^1.2.2",
@@ -35,6 +35,7 @@
3535
"grpc-server-reflection": "^0.1.5",
3636
"http-aws-es": "^6.0.0",
3737
"lodash": "^4.17.21",
38+
"long": "^5.2.3",
3839
"moment": "^2.29.4",
3940
"source-map-support": "^0.5.21",
4041
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.4",
@@ -44,15 +45,15 @@
4445
},
4546
"volta": {
4647
"node": "18.14.1",
47-
"typescript": "4.9.4",
48+
"typescript": "5.2.2",
4849
"yarn": "1.22.19"
4950
},
5051
"devDependencies": {
5152
"@types/elasticsearch": "^5.0.40",
5253
"@types/lodash": "^4.14.191",
53-
"@types/node": "^18.11.17",
54+
"@types/node": "^20.8.0",
5455
"ts-node-dev": "^2.0.0",
55-
"ts-proto": "^1.126.1",
56-
"typescript": "^4.9.4"
56+
"ts-proto": "^1.159.1",
57+
"typescript": "^5.2.2"
5758
}
58-
}
59+
}

release.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/sh
2+
3+
# Ensure we have the required environment variables
4+
if [ -z "$GH_TOKEN" ]; then
5+
echo "ERROR: GH_TOKEN environment variable is not set."
6+
exit 1
7+
fi
8+
9+
if [ -z "$NPM_TOKEN" ]; then
10+
echo "WARNING: NPM_TOKEN environment variable is not set. Will not publish to npm."
11+
fi
12+
13+
# Detect current branch
14+
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
15+
16+
if [ "$CURRENT_BRANCH" = "main" ]; then
17+
# On main branch: Do a standard release
18+
npx semantic-release
19+
else
20+
# On feature branches: Dry run to see next version and changes (won't actually publish)
21+
npx semantic-release --dry-run
22+
fi

src/api/BillingAccount.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import _ from "lodash";
2+
import axios from "axios";
3+
import { StatusBuilder } from "@grpc/grpc-js";
4+
import { Status } from "@grpc/grpc-js/build/src/constants";
5+
6+
import m2m from "../helpers/MachineToMachineToken";
7+
import { ChallengeStatuses, TGBillingAccounts } from "../common/Constants";
8+
9+
const { V3_BA_API_URL = "https://api.topcoder-dev.com/v3/billing-accounts" } = process.env;
10+
11+
async function lockAmount(billingAccountId: number, dto: LockAmountDTO) {
12+
console.log("BA validation lock amount:", billingAccountId, dto);
13+
14+
try {
15+
const m2mToken = await m2m.getM2MToken();
16+
17+
await axios.patch(
18+
`${V3_BA_API_URL}/${billingAccountId}/lock-amount`,
19+
{
20+
param: dto,
21+
},
22+
{
23+
headers: {
24+
Authorization: `Bearer ${m2mToken}`,
25+
"Content-Type": "application/json",
26+
},
27+
}
28+
);
29+
} catch (err: any) {
30+
console.error(err.response?.data?.result?.content ?? "Failed to lock challenge amount");
31+
throw new StatusBuilder()
32+
.withCode(Status.INTERNAL)
33+
.withDetails(
34+
`Budget Error: Requested amount $${dto.lockAmount} exceeds available budget for Billing Account #${billingAccountId}. Please contact the Topcoder Project Manager for further assistance.`
35+
)
36+
.build();
37+
}
38+
}
39+
40+
async function consumeAmount(billingAccountId: number, dto: ConsumeAmountDTO) {
41+
console.log("BA validation consume amount:", billingAccountId, dto);
42+
43+
try {
44+
const m2mToken = await m2m.getM2MToken();
45+
46+
await axios.patch(
47+
`${V3_BA_API_URL}/${billingAccountId}/consume-amount`,
48+
{
49+
param: dto,
50+
},
51+
{
52+
headers: {
53+
Authorization: `Bearer ${m2mToken}`,
54+
"Content-Type": "application/json",
55+
},
56+
}
57+
);
58+
} catch (err: any) {
59+
throw new StatusBuilder()
60+
.withCode(Status.INTERNAL)
61+
.withDetails(err.response?.data?.result?.content ?? "Failed to consume challenge amount")
62+
.build();
63+
}
64+
}
65+
66+
interface LockAmountDTO {
67+
challengeId: string;
68+
lockAmount: number;
69+
}
70+
interface ConsumeAmountDTO {
71+
challengeId: string;
72+
consumeAmount: number;
73+
markup?: number;
74+
}
75+
76+
// prettier-ignore
77+
export async function lockConsumeAmount(baValidation: BAValidation, rollback: boolean = false): Promise<void> {
78+
if (!_.isNumber(baValidation.billingAccountId)) {
79+
console.warn("Challenge doesn't have billing account id:", baValidation);
80+
return;
81+
}
82+
if (_.includes(TGBillingAccounts, baValidation.billingAccountId)) {
83+
console.info("Ignore BA validation for Topgear account:", baValidation.billingAccountId);
84+
return;
85+
}
86+
87+
console.log("BA validation:", baValidation);
88+
89+
if (
90+
baValidation.status === ChallengeStatuses.New ||
91+
baValidation.status === ChallengeStatuses.Draft ||
92+
baValidation.status === ChallengeStatuses.Active ||
93+
baValidation.status === ChallengeStatuses.Approved
94+
) {
95+
// Update lock amount
96+
const currAmount = baValidation.totalPrizesInCents / 100;
97+
const prevAmount = baValidation.prevTotalPrizesInCents / 100;
98+
99+
if (currAmount !== prevAmount) {
100+
await lockAmount(baValidation.billingAccountId, {
101+
challengeId: baValidation.challengeId!,
102+
lockAmount: (rollback ? prevAmount : currAmount) * (1 + baValidation.markup!),
103+
});
104+
}
105+
} else if (baValidation.status === ChallengeStatuses.Completed) {
106+
// Note an already completed challenge could still be updated with prizes
107+
const currAmount = baValidation.totalPrizesInCents / 100;
108+
const prevAmount = baValidation.prevStatus === ChallengeStatuses.Completed ? baValidation.prevTotalPrizesInCents / 100 : 0;
109+
110+
if (currAmount !== prevAmount) {
111+
await consumeAmount(baValidation.billingAccountId, {
112+
challengeId: baValidation.challengeId!,
113+
consumeAmount: (rollback ? prevAmount : currAmount) * (1 + baValidation.markup!),
114+
markup: baValidation.markup,
115+
});
116+
}
117+
} else if (
118+
baValidation.status === ChallengeStatuses.Deleted ||
119+
baValidation.status === ChallengeStatuses.Canceled ||
120+
baValidation.status === ChallengeStatuses.CancelledFailedReview ||
121+
baValidation.status === ChallengeStatuses.CancelledFailedScreening ||
122+
baValidation.status === ChallengeStatuses.CancelledZeroSubmissions ||
123+
baValidation.status === ChallengeStatuses.CancelledWinnerUnresponsive ||
124+
baValidation.status === ChallengeStatuses.CancelledClientRequest ||
125+
baValidation.status === ChallengeStatuses.CancelledRequirementsInfeasible ||
126+
baValidation.status === ChallengeStatuses.CancelledZeroRegistrations ||
127+
baValidation.status === ChallengeStatuses.CancelledPaymentFailed
128+
) {
129+
// Challenge canceled, unlock previous locked amount
130+
const currAmount = 0;
131+
const prevAmount = baValidation.prevTotalPrizesInCents / 100;
132+
133+
if (currAmount !== prevAmount) {
134+
await lockAmount(baValidation.billingAccountId, {
135+
challengeId: baValidation.challengeId!,
136+
lockAmount: rollback ? prevAmount : 0,
137+
});
138+
}
139+
}
140+
}
141+
142+
export interface BAValidation {
143+
challengeId?: string;
144+
billingAccountId?: number;
145+
markup?: number;
146+
prevStatus?: string;
147+
status?: string;
148+
prevTotalPrizesInCents: number;
149+
totalPrizesInCents: number;
150+
}

0 commit comments

Comments
 (0)