Skip to content

Commit 4eda48d

Browse files
committed
Merge branch 'develop' of github.com:topcoder-platform/billing-accounts-api-v6 into develop
2 parents 6d2de22 + 848b3bf commit 4eda48d

37 files changed

Lines changed: 16514 additions & 7 deletions

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,15 @@ AUTH0_AUDIENCE="" # e.g. https://m2m.topcoder-dev.com/
1111
AUTH0_ISSUER="" # e.g. https://topcoder-dev.auth0.com/
1212
ALLOWED_ROLES="Administrator,Topcoder User,Manager" # comma-separated
1313

14+
# Salesforce
15+
SALESFORCE_CLIENT_ID=""
16+
SALESFORCE_SUBJECT="" # e.g. integration@topcoder.com
17+
SALESFORCE_CLIENT_KEY="" # PEM private key (use `\\n` for newlines)
18+
SALESFORCE_AUDIENCE="https://login.salesforce.com" # or https://test.salesforce.com for sandbox
19+
20+
# Optional SFDC field mapping overrides (defaults used by service)
21+
SFDC_BILLING_ACCOUNT_NAME_FIELD="Billing_Account_name__c"
22+
SFDC_BILLING_ACCOUNT_MARKUP_FIELD="Mark_Up__c"
23+
SFDC_BILLING_ACCOUNT_ACTIVE_FIELD="Active__c"
1424
# Service
1525
PORT=3000

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- `GET /billing-accounts`
99
- `POST /billing-accounts`
1010
- `GET /billing-accounts/:billingAccountId` (includes locked/consumed arrays + budget totals)
11+
- `GET /billing-accounts/users/:userId` (list billing accounts accessible by the given Topcoder user ID — resolved via Salesforce resource object)
1112
- `PATCH /billing-accounts/:billingAccountId`
1213
- `PATCH /billing-accounts/:billingAccountId/lock-amount` (0 amount = unlock)
1314
- `PATCH /billing-accounts/:billingAccountId/consume-amount` (deletes locks for challenge, then upserts consumed)
@@ -44,6 +45,18 @@ pnpm run dev
4445
pnpm run build && pnpm start
4546
```
4647

48+
## Salesforce integration
49+
50+
- This service can resolve billing accounts a user has access to via Salesforce. To enable Salesforce calls, configure the following environment variables in `.env` (see `.env.example`):
51+
- `SALESFORCE_CLIENT_ID` — Connected App client ID
52+
- `SALESFORCE_SUBJECT` — integration user username (subject for JWT)
53+
- `SALESFORCE_CLIENT_KEY` — PEM private key for the connected app (escape newlines as `\\n` in single-line `.env` files)
54+
- `SALESFORCE_AUDIENCE` — login URL (default `https://login.salesforce.com`, use `https://test.salesforce.com` for sandbox)
55+
56+
- Optional overrides for field names returned by your Salesforce org are available via `SFDC_BILLING_ACCOUNT_NAME_FIELD`, `SFDC_BILLING_ACCOUNT_MARKUP_FIELD`, and `SFDC_BILLING_ACCOUNT_ACTIVE_FIELD`.
57+
58+
- Once configured, you can call `GET /v6/billing-accounts/users/:userId` to obtain billing accounts the specified user has access to (the service authenticates to Salesforce using JWT Bearer flow and queries resource objects).
59+
4760
## Import scripts
4861

4962
- Legacy import (clients, billing accounts, challenge budgets):

package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919
"generate:subcontractor-sql": "ts-node scripts/generate-subcontractor-sql.ts"
2020
},
2121
"dependencies": {
22+
"@nestjs/cli": "^11.0.0",
2223
"@nestjs/common": "^10.3.0",
2324
"@nestjs/config": "^3.2.0",
2425
"@nestjs/core": "^10.3.0",
25-
"@nestjs/cli": "^11.0.0",
26-
"@nestjs/swagger": "^7.4.2",
2726
"@nestjs/mapped-types": "^2.1.0",
2827
"@nestjs/platform-express": "^10.3.0",
28+
"@nestjs/swagger": "^7.4.2",
2929
"@prisma/client": "^6.18.0",
3030
"@types/express": "^5.0.3",
31+
"axios": "^1.13.4",
3132
"class-transformer": "^0.5.1",
3233
"class-validator": "^0.14.0",
3334
"dotenv": "^16.4.5",
35+
"jsonwebtoken": "^9.0.3",
36+
"lodash": "^4.17.23",
3437
"pino": "^9.0.0",
3538
"prisma": "^6.18.0",
3639
"reflect-metadata": "^0.1.13",
@@ -41,6 +44,9 @@
4144
"devDependencies": {
4245
"@eslint/eslintrc": "^3.2.0",
4346
"@eslint/js": "^9.18.0",
47+
"@types/axios": "^0.14.4",
48+
"@types/jsonwebtoken": "^9.0.10",
49+
"@types/lodash": "^4.17.23",
4450
"@types/node": "^20.14.9",
4551
"eslint": "^9.18.0",
4652
"eslint-config-prettier": "^10.0.1",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./index"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
/* !!! This is code generated by Prisma. Do not edit directly. !!!
3+
/* eslint-disable */
4+
// biome-ignore-all lint: generated file
5+
module.exports = { ...require('.') }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./index"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
/* !!! This is code generated by Prisma. Do not edit directly. !!!
3+
/* eslint-disable */
4+
// biome-ignore-all lint: generated file
5+
module.exports = { ...require('#main-entry-point') }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./default"

packages/ba-prisma-client/edge.js

Lines changed: 262 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
2+
/* !!! This is code generated by Prisma. Do not edit directly. !!!
3+
/* eslint-disable */
4+
// biome-ignore-all lint: generated file
5+
6+
Object.defineProperty(exports, "__esModule", { value: true });
7+
8+
const {
9+
Decimal,
10+
objectEnumValues,
11+
makeStrictEnum,
12+
Public,
13+
getRuntime,
14+
skip
15+
} = require('./runtime/index-browser.js')
16+
17+
18+
const Prisma = {}
19+
20+
exports.Prisma = Prisma
21+
exports.$Enums = {}
22+
23+
/**
24+
* Prisma Client JS version: 6.19.0
25+
* Query Engine version: 2ba551f319ab1df4bc874a89965d8b3641056773
26+
*/
27+
Prisma.prismaVersion = {
28+
client: "6.19.0",
29+
engine: "2ba551f319ab1df4bc874a89965d8b3641056773"
30+
}
31+
32+
Prisma.PrismaClientKnownRequestError = () => {
33+
const runtimeName = getRuntime().prettyName;
34+
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
35+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
36+
)};
37+
Prisma.PrismaClientUnknownRequestError = () => {
38+
const runtimeName = getRuntime().prettyName;
39+
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
40+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
41+
)}
42+
Prisma.PrismaClientRustPanicError = () => {
43+
const runtimeName = getRuntime().prettyName;
44+
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
45+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
46+
)}
47+
Prisma.PrismaClientInitializationError = () => {
48+
const runtimeName = getRuntime().prettyName;
49+
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
50+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
51+
)}
52+
Prisma.PrismaClientValidationError = () => {
53+
const runtimeName = getRuntime().prettyName;
54+
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
55+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
56+
)}
57+
Prisma.Decimal = Decimal
58+
59+
/**
60+
* Re-export of sql-template-tag
61+
*/
62+
Prisma.sql = () => {
63+
const runtimeName = getRuntime().prettyName;
64+
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
65+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
66+
)}
67+
Prisma.empty = () => {
68+
const runtimeName = getRuntime().prettyName;
69+
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
70+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
71+
)}
72+
Prisma.join = () => {
73+
const runtimeName = getRuntime().prettyName;
74+
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
75+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
76+
)}
77+
Prisma.raw = () => {
78+
const runtimeName = getRuntime().prettyName;
79+
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
80+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
81+
)}
82+
Prisma.validator = Public.validator
83+
84+
/**
85+
* Extensions
86+
*/
87+
Prisma.getExtensionContext = () => {
88+
const runtimeName = getRuntime().prettyName;
89+
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
90+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
91+
)}
92+
Prisma.defineExtension = () => {
93+
const runtimeName = getRuntime().prettyName;
94+
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
95+
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
96+
)}
97+
98+
/**
99+
* Shorthand utilities for JSON filtering
100+
*/
101+
Prisma.DbNull = objectEnumValues.instances.DbNull
102+
Prisma.JsonNull = objectEnumValues.instances.JsonNull
103+
Prisma.AnyNull = objectEnumValues.instances.AnyNull
104+
105+
Prisma.NullTypes = {
106+
DbNull: objectEnumValues.classes.DbNull,
107+
JsonNull: objectEnumValues.classes.JsonNull,
108+
AnyNull: objectEnumValues.classes.AnyNull
109+
}
110+
111+
112+
113+
/**
114+
* Enums
115+
*/
116+
117+
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
118+
ReadUncommitted: 'ReadUncommitted',
119+
ReadCommitted: 'ReadCommitted',
120+
RepeatableRead: 'RepeatableRead',
121+
Serializable: 'Serializable'
122+
});
123+
124+
exports.Prisma.ClientScalarFieldEnum = {
125+
id: 'id',
126+
name: 'name',
127+
codeName: 'codeName',
128+
status: 'status',
129+
startDate: 'startDate',
130+
endDate: 'endDate',
131+
createdAt: 'createdAt',
132+
updatedAt: 'updatedAt'
133+
};
134+
135+
exports.Prisma.BillingAccountScalarFieldEnum = {
136+
id: 'id',
137+
projectId: 'projectId',
138+
name: 'name',
139+
description: 'description',
140+
subcontractingEndCustomer: 'subcontractingEndCustomer',
141+
status: 'status',
142+
startDate: 'startDate',
143+
endDate: 'endDate',
144+
budget: 'budget',
145+
markup: 'markup',
146+
clientId: 'clientId',
147+
poNumber: 'poNumber',
148+
subscriptionNumber: 'subscriptionNumber',
149+
isManualPrize: 'isManualPrize',
150+
paymentTerms: 'paymentTerms',
151+
salesTax: 'salesTax',
152+
billable: 'billable',
153+
createdBy: 'createdBy',
154+
createdAt: 'createdAt',
155+
updatedAt: 'updatedAt'
156+
};
157+
158+
exports.Prisma.LockedAmountScalarFieldEnum = {
159+
id: 'id',
160+
billingAccountId: 'billingAccountId',
161+
challengeId: 'challengeId',
162+
amount: 'amount',
163+
createdAt: 'createdAt',
164+
updatedAt: 'updatedAt'
165+
};
166+
167+
exports.Prisma.ConsumedAmountScalarFieldEnum = {
168+
id: 'id',
169+
billingAccountId: 'billingAccountId',
170+
challengeId: 'challengeId',
171+
amount: 'amount',
172+
createdAt: 'createdAt',
173+
updatedAt: 'updatedAt'
174+
};
175+
176+
exports.Prisma.BillingAccountAccessScalarFieldEnum = {
177+
id: 'id',
178+
billingAccountId: 'billingAccountId',
179+
userId: 'userId',
180+
createdAt: 'createdAt'
181+
};
182+
183+
exports.Prisma.SortOrder = {
184+
asc: 'asc',
185+
desc: 'desc'
186+
};
187+
188+
exports.Prisma.QueryMode = {
189+
default: 'default',
190+
insensitive: 'insensitive'
191+
};
192+
193+
exports.Prisma.NullsOrder = {
194+
first: 'first',
195+
last: 'last'
196+
};
197+
exports.ClientStatus = exports.$Enums.ClientStatus = {
198+
ACTIVE: 'ACTIVE',
199+
INACTIVE: 'INACTIVE'
200+
};
201+
202+
exports.BAStatus = exports.$Enums.BAStatus = {
203+
ACTIVE: 'ACTIVE',
204+
INACTIVE: 'INACTIVE'
205+
};
206+
207+
exports.Prisma.ModelName = {
208+
Client: 'Client',
209+
BillingAccount: 'BillingAccount',
210+
LockedAmount: 'LockedAmount',
211+
ConsumedAmount: 'ConsumedAmount',
212+
BillingAccountAccess: 'BillingAccountAccess'
213+
};
214+
215+
/**
216+
* This is a stub Prisma Client that will error at runtime if called.
217+
*/
218+
class PrismaClient {
219+
constructor() {
220+
return new Proxy(this, {
221+
get(target, prop) {
222+
let message
223+
const runtime = getRuntime()
224+
if (runtime.isEdge) {
225+
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
226+
- Use Prisma Accelerate: https://pris.ly/d/accelerate
227+
- Use Driver Adapters: https://pris.ly/d/driver-adapters
228+
`;
229+
} else {
230+
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
231+
}
232+
233+
message += `
234+
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
235+
236+
throw new Error(message)
237+
}
238+
})
239+
}
240+
}
241+
242+
exports.PrismaClient = PrismaClient
243+
244+
Object.assign(exports, Prisma)

0 commit comments

Comments
 (0)