Skip to content

fix: save markup in legacy database #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1675e9a
fix: save markup
rakibansary Sep 23, 2023
6df6ee5
Merge branch 'main' into PLAT-3491
rakibansary Sep 23, 2023
2d90ea9
fix(deps): update @topcoder-framework packages for bug fixes
rakibansary Sep 23, 2023
d6cb4e3
Merge branch 'main' into PLAT-3491
rakibansary Sep 30, 2023
b6f2d99
feat: add payment attribute to challenge
rakibansary Oct 1, 2023
839d271
feat: add payment attribute to challenge
rakibansary Oct 1, 2023
96791a3
feat: save project payments
rakibansary Oct 2, 2023
29b4fec
fix: add payments to schema
rakibansary Oct 2, 2023
c41abe7
PLAT-3221 lock/consume budget (#57)
Oct 3, 2023
f5aaa0f
feat: add challenge estimator
rakibansary Oct 3, 2023
1b4820e
fix: use 0 as reviewer prize when placement prizes are missing
rakibansary Oct 3, 2023
3ec1076
Merge branch 'main' into PLAT-3491
rakibansary Oct 6, 2023
b84eab7
fix: do not call lockConsumeAmount for completed challenges
rakibansary Oct 6, 2023
cf75dbd
fix: keepalive and connection management
rakibansary Oct 6, 2023
3e6d22e
chore(domain-acl): add additional logging
rakibansary Oct 6, 2023
1269f7f
chore(domain-acl): add additional logging
rakibansary Oct 6, 2023
05fdb9b
feat: generate payments
rakibansary Oct 6, 2023
ecb0dbd
Merge pull request #64 from topcoder-platform/PLAT-3591
eisbilir Oct 10, 2023
b3272bc
Merge branch 'main' into PLAT-3491
rakibansary Oct 21, 2023
5b109bd
fix: pinned typescript version
rakibansary Oct 23, 2023
3d1338f
fix: use updated topcoder-framework/*
rakibansary Oct 23, 2023
0e62432
fix: allow copilot prize of 0
rakibansary Oct 23, 2023
8e7af8e
feat: generate payments for tasks
rakibansary Oct 23, 2023
78088e1
fix: generate task payments only when task is completed
rakibansary Oct 23, 2023
6d8e4c3
fix: generate task payments only when task is completed
rakibansary Oct 23, 2023
d685e59
fix: incorrect url
rakibansary Oct 23, 2023
f741f91
fix: incorrect overview calculation
rakibansary Oct 29, 2023
ddaca4c
fix: duplicate budget lock/unlock calls
rakibansary Oct 29, 2023
da4ed47
fix: take markup into account when locking/unlocking budget
rakibansary Oct 30, 2023
f1eb4ba
fix: typo
rakibansary Oct 30, 2023
912c7e3
fix: typo
rakibansary Oct 30, 2023
1b351b1
fix: unlock budget on task completion
rakibansary Oct 30, 2023
dd23d2e
fix: unlock budget on challenge cancellation
rakibansary Nov 9, 2023
69f7672
fix: unlock budget on challenge cancellation
rakibansary Nov 9, 2023
ee73733
fix: set correct enddate for tasks
rakibansary Nov 9, 2023
8a7f7af
fix: include m2m token when creating payments
rakibansary Nov 11, 2023
843cded
chore: use release version
rakibansary Nov 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions bin/nosql-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ const { execSync } = require("child_process");

const PROTO_DIR = path.join(
__dirname,
"../node_modules/topcoder-interface/data-access-layer/nosql"
"../node_modules/topcoder-proto-registry/data-access-layer/nosql"
);
const MODEL_DIR = path.join(__dirname, "../src/dal/models/nosql/");

const PROTOC_PATH = "protoc";
const PLUGIN_PATH = path.join(
__dirname,
"../node_modules/.bin/protoc-gen-ts_proto"
);
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");

rimraf.sync(`${MODEL_DIR}/*`, {
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },
Expand Down
7 changes: 2 additions & 5 deletions bin/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ const rimraf = require("rimraf");

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

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

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

const PROTOC_PATH = "protoc";
const PLUGIN_PATH = path.join(
__dirname,
"../node_modules/.bin/protoc-gen-ts_proto"
);
const PLUGIN_PATH = path.join(__dirname, "../node_modules/.bin/protoc-gen-ts_proto");

rimraf.sync(`${MODEL_DIR}/*`, {
glob: { ignore: `${MODEL_DIR}/tsconfig.json` },
Expand Down
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"@aws-sdk/util-utf8-node": "^3.259.0",
"@grpc/grpc-js": "^1.7.1",
"@opensearch-project/opensearch": "^2.2.0",
"@topcoder-framework/domain-acl": "^0.23.1-PLAT-3614.0",
"@topcoder-framework/lib-common": "^0.23.1-PLAT-3614.0",
"topcoder-interface": "0.1.0-PLAT-3614.1",
"@topcoder-framework/domain-acl": "0.24",
"@topcoder-framework/lib-common": "0.24",
"topcoder-proto-registry": "0.1.0",
"@types/uuid": "^9.0.1",
"aws-sdk": "^2.1339.0",
"axios": "^1.2.2",
Expand All @@ -35,6 +35,7 @@
"grpc-server-reflection": "^0.1.5",
"http-aws-es": "^6.0.0",
"lodash": "^4.17.21",
"long": "^5.2.3",
"moment": "^2.29.4",
"source-map-support": "^0.5.21",
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.4",
Expand All @@ -44,15 +45,15 @@
},
"volta": {
"node": "18.14.1",
"typescript": "4.9.4",
"typescript": "5.2.2",
"yarn": "1.22.19"
},
"devDependencies": {
"@types/elasticsearch": "^5.0.40",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.17",
"@types/node": "^20.8.0",
"ts-node-dev": "^2.0.0",
"ts-proto": "^1.126.1",
"typescript": "^4.9.4"
"ts-proto": "^1.159.1",
"typescript": "^5.2.2"
}
}
}
22 changes: 22 additions & 0 deletions release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/sh

# Ensure we have the required environment variables
if [ -z "$GH_TOKEN" ]; then
echo "ERROR: GH_TOKEN environment variable is not set."
exit 1
fi

if [ -z "$NPM_TOKEN" ]; then
echo "WARNING: NPM_TOKEN environment variable is not set. Will not publish to npm."
fi

# Detect current branch
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)

if [ "$CURRENT_BRANCH" = "main" ]; then
# On main branch: Do a standard release
npx semantic-release
else
# On feature branches: Dry run to see next version and changes (won't actually publish)
npx semantic-release --dry-run
fi
150 changes: 150 additions & 0 deletions src/api/BillingAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import _ from "lodash";
import axios from "axios";
import { StatusBuilder } from "@grpc/grpc-js";
import { Status } from "@grpc/grpc-js/build/src/constants";

import m2m from "../helpers/MachineToMachineToken";
import { ChallengeStatuses, TGBillingAccounts } from "../common/Constants";

const { V3_BA_API_URL = "https://api.topcoder-dev.com/v3/billing-accounts" } = process.env;

async function lockAmount(billingAccountId: number, dto: LockAmountDTO) {
console.log("BA validation lock amount:", billingAccountId, dto);

try {
const m2mToken = await m2m.getM2MToken();

await axios.patch(
`${V3_BA_API_URL}/${billingAccountId}/lock-amount`,
{
param: dto,
},
{
headers: {
Authorization: `Bearer ${m2mToken}`,
"Content-Type": "application/json",
},
}
);
} catch (err: any) {
console.error(err.response?.data?.result?.content ?? "Failed to lock challenge amount");
throw new StatusBuilder()
.withCode(Status.INTERNAL)
.withDetails(
`Budget Error: Requested amount $${dto.lockAmount} exceeds available budget for Billing Account #${billingAccountId}. Please contact the Topcoder Project Manager for further assistance.`
)
.build();
}
}

async function consumeAmount(billingAccountId: number, dto: ConsumeAmountDTO) {
console.log("BA validation consume amount:", billingAccountId, dto);

try {
const m2mToken = await m2m.getM2MToken();

await axios.patch(
`${V3_BA_API_URL}/${billingAccountId}/consume-amount`,
{
param: dto,
},
{
headers: {
Authorization: `Bearer ${m2mToken}`,
"Content-Type": "application/json",
},
}
);
} catch (err: any) {
throw new StatusBuilder()
.withCode(Status.INTERNAL)
.withDetails(err.response?.data?.result?.content ?? "Failed to consume challenge amount")
.build();
}
}

interface LockAmountDTO {
challengeId: string;
lockAmount: number;
}
interface ConsumeAmountDTO {
challengeId: string;
consumeAmount: number;
markup?: number;
}

// prettier-ignore
export async function lockConsumeAmount(baValidation: BAValidation, rollback: boolean = false): Promise<void> {
if (!_.isNumber(baValidation.billingAccountId)) {
console.warn("Challenge doesn't have billing account id:", baValidation);
return;
}
if (_.includes(TGBillingAccounts, baValidation.billingAccountId)) {
console.info("Ignore BA validation for Topgear account:", baValidation.billingAccountId);
return;
}

console.log("BA validation:", baValidation);

if (
baValidation.status === ChallengeStatuses.New ||
baValidation.status === ChallengeStatuses.Draft ||
baValidation.status === ChallengeStatuses.Active ||
baValidation.status === ChallengeStatuses.Approved
) {
// Update lock amount
const currAmount = baValidation.totalPrizesInCents / 100;
const prevAmount = baValidation.prevTotalPrizesInCents / 100;

if (currAmount !== prevAmount) {
await lockAmount(baValidation.billingAccountId, {
challengeId: baValidation.challengeId!,
lockAmount: (rollback ? prevAmount : currAmount) * (1 + baValidation.markup!),
});
}
} else if (baValidation.status === ChallengeStatuses.Completed) {
// Note an already completed challenge could still be updated with prizes
const currAmount = baValidation.totalPrizesInCents / 100;
const prevAmount = baValidation.prevStatus === ChallengeStatuses.Completed ? baValidation.prevTotalPrizesInCents / 100 : 0;

if (currAmount !== prevAmount) {
await consumeAmount(baValidation.billingAccountId, {
challengeId: baValidation.challengeId!,
consumeAmount: (rollback ? prevAmount : currAmount) * (1 + baValidation.markup!),
markup: baValidation.markup,
});
}
} else if (
baValidation.status === ChallengeStatuses.Deleted ||
baValidation.status === ChallengeStatuses.Canceled ||
baValidation.status === ChallengeStatuses.CancelledFailedReview ||
baValidation.status === ChallengeStatuses.CancelledFailedScreening ||
baValidation.status === ChallengeStatuses.CancelledZeroSubmissions ||
baValidation.status === ChallengeStatuses.CancelledWinnerUnresponsive ||
baValidation.status === ChallengeStatuses.CancelledClientRequest ||
baValidation.status === ChallengeStatuses.CancelledRequirementsInfeasible ||
baValidation.status === ChallengeStatuses.CancelledZeroRegistrations ||
baValidation.status === ChallengeStatuses.CancelledPaymentFailed
) {
// Challenge canceled, unlock previous locked amount
const currAmount = 0;
const prevAmount = baValidation.prevTotalPrizesInCents / 100;

if (currAmount !== prevAmount) {
await lockAmount(baValidation.billingAccountId, {
challengeId: baValidation.challengeId!,
lockAmount: rollback ? prevAmount : 0,
});
}
}
}

export interface BAValidation {
challengeId?: string;
billingAccountId?: number;
markup?: number;
prevStatus?: string;
status?: string;
prevTotalPrizesInCents: number;
totalPrizesInCents: number;
}
Loading