Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.

Commit 32b189f

Browse files
authored
Breaking private api [2/X] (#382)
This adds the knex-stringcase library to automatically convert between camelCase in JS/TS land and snake_case in DB-land. This means that we can only work with camelCase stuff in TypeScript while still using the conventional snake_case names in our database.
1 parent 0db7e8c commit 32b189f

20 files changed

+118
-91
lines changed

@types/knex-stringcase/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'knex-stringcase'

package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"boom": "^7.3.0",
3131
"express": "^4.17.1",
3232
"knex": "^0.21.1",
33+
"knex-stringcase": "^1.4.1",
3334
"log4js": "^6.2.1",
3435
"pg": "8.2.1",
3536
"prom-client": "^12.0.0",

src/data-access/payIds.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ export default async function getAllAddressInfoFromDatabase(
1010
payId: string,
1111
): Promise<readonly AddressInformation[]> {
1212
const addressInformation = await knex
13-
.select('address.payment_network', 'address.environment', 'address.details')
13+
.select('address.paymentNetwork', 'address.environment', 'address.details')
1414
.from<Address>('address')
15-
.innerJoin('account', 'address.account_id', 'account.id')
16-
.where('account.pay_id', payId)
15+
.innerJoin('account', 'address.accountId', 'account.id')
16+
.where('account.payId', payId)
1717

1818
return addressInformation
1919
}

src/data-access/reports.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ import { PayIdCount } from '../types/reports'
44
/**
55
* Retrieve count of PayIDs, grouped by payment network and environment.
66
*
7-
* @returns A list with the number of PayIDs that have a given (payment_network, environment) tuple,
8-
* ordered by (payment_network, environment).
7+
* @returns A list with the number of PayIDs that have a given (paymentNetwork, environment) tuple,
8+
* ordered by (paymentNetwork, environment).
99
*/
1010
export default async function getPayIdCounts(): Promise<PayIdCount[]> {
1111
const payIdCounts: PayIdCount[] = await knex
12-
.select('address.payment_network', 'address.environment')
12+
.select('address.paymentNetwork', 'address.environment')
1313
.count('* as count')
1414
.from<PayIdCount>('address')
15-
.groupBy('address.payment_network', 'address.environment')
16-
.orderBy('address.payment_network')
15+
.groupBy('address.paymentNetwork', 'address.environment')
16+
.orderBy('address.paymentNetwork')
1717
.orderBy('address.environment')
1818

1919
return payIdCounts.map((record) => {
2020
return {
21-
payment_network: record.payment_network,
21+
paymentNetwork: record.paymentNetwork,
2222
environment: record.environment,
2323
count: Number(record.count),
2424
}

src/data-access/users.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ export async function insertUser(
2222
return knex.transaction(async (transaction: Transaction) => {
2323
const insertedAddresses = await knex
2424
.insert({
25-
pay_id: payId,
25+
payId,
2626
})
2727
.into<Account>('account')
2828
.transacting(transaction)
2929
.returning('id')
3030
.then(async (ids) => {
31-
const accountID = ids[0]
32-
const mappedAddresses = addAccountIDToAddresses(addresses, accountID)
31+
const accountId = ids[0]
32+
const mappedAddresses = addAccountIdToAddresses(addresses, accountId)
3333
return insertAddresses(mappedAddresses, transaction)
3434
})
3535
.then(transaction.commit)
@@ -55,23 +55,23 @@ export async function replaceUser(
5555
): Promise<readonly AddressInformation[] | null> {
5656
return knex.transaction(async (transaction: Transaction) => {
5757
const updatedAddresses = await knex<Account>('account')
58-
.where('pay_id', oldPayId)
59-
.update({ pay_id: newPayId })
58+
.where('payId', oldPayId)
59+
.update({ payId: newPayId })
6060
.transacting(transaction)
6161
.returning('id')
6262
.then(async (ids) => {
63-
const accountID = ids[0]
64-
if (accountID === undefined) {
63+
const accountId = ids[0]
64+
if (accountId === undefined) {
6565
return null
6666
}
6767

6868
// Delete existing addresses associated with that user
6969
await knex<Address>('address')
7070
.delete()
71-
.where('account_id', accountID)
71+
.where('accountId', accountId)
7272
.transacting(transaction)
7373

74-
const mappedAddresses = addAccountIDToAddresses(addresses, accountID)
74+
const mappedAddresses = addAccountIdToAddresses(addresses, accountId)
7575
return insertAddresses(mappedAddresses, transaction)
7676
})
7777
.then(transaction.commit)
@@ -90,7 +90,7 @@ export async function replaceUser(
9090
export async function removeUser(payId: string): Promise<void> {
9191
await knex<Account>('account')
9292
.delete()
93-
.where('pay_id', payId)
93+
.where('payId', payId)
9494
.then((count) => {
9595
/* istanbul ignore if */
9696
if (count > 1) {
@@ -109,25 +109,25 @@ export async function removeUser(payId: string): Promise<void> {
109109
// HELPER FUNCTIONS
110110

111111
interface DatabaseAddress extends AddressInformation {
112-
account_id: string
112+
accountId: string
113113
}
114114

115115
/**
116116
* Maps an array of AddressInformation objects into an array of DatabaseAddress objects,
117-
* by adding an 'account_id' property to each object.
117+
* by adding an 'accountId' property to each object.
118118
*
119119
* @param addresses - An array of payment addresses we want to insert into the database.
120-
* @param accountID - The account ID to add to all the addresses to allow inserting the addresses into the database.
120+
* @param accountId - The account ID to add to all the addresses to allow inserting the addresses into the database.
121121
*
122-
* @returns A new array of DatabaseAddress objects, where each address has a new property 'account_id'.
122+
* @returns A new array of DatabaseAddress objects, where each address has a new property 'accountId'.
123123
*/
124-
function addAccountIDToAddresses(
124+
function addAccountIdToAddresses(
125125
addresses: readonly AddressInformation[],
126-
accountID: string,
126+
accountId: string,
127127
): readonly DatabaseAddress[] {
128128
return addresses.map((address) => ({
129-
account_id: accountID,
130-
payment_network: address.payment_network.toUpperCase(),
129+
accountId,
130+
paymentNetwork: address.paymentNetwork.toUpperCase(),
131131
environment: address.environment?.toUpperCase(),
132132
details: address.details,
133133
}))
@@ -151,5 +151,5 @@ async function insertAddresses(
151151
.insert(addresses)
152152
.into<Address>('address')
153153
.transacting(transaction)
154-
.returning(['payment_network', 'environment', 'details'])
154+
.returning(['paymentNetwork', 'environment', 'details'])
155155
}

src/db/knex.ts

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
import * as knexInit from 'knex'
2+
import * as knexStringcase from 'knex-stringcase'
23

34
import config from '../config'
45
import { handleDatabaseError } from '../utils/errors'
56

6-
const knex = knexInit({
7+
const knexConfig = {
78
client: 'pg',
89
connection: config.database.connection,
910
pool: {
1011
/* eslint-disable */
11-
afterCreate(conn: any, done: Function): void {
12-
conn.query('SET timezone="UTC";', (err: Error) => {
13-
if (err) return done(err, conn)
12+
afterCreate(conn: any, done: Function): void {
13+
conn.query('SET timezone="UTC";', (err: Error) => {
14+
if (err) return done(err, conn)
1415

15-
conn.query('SET statement_timeout TO 3000;', (err: Error) => {
16-
// if err is not falsy, connection is discarded from pool
17-
done(err, conn)
18-
})
16+
conn.query('SET statement_timeout TO 3000;', (err: Error) => {
17+
// if err is not falsy, connection is discarded from pool
18+
done(err, conn)
1919
})
20-
},
21-
/* eslint-enable */
20+
})
2221
},
23-
})
22+
/* eslint-enable */
23+
},
24+
}
25+
26+
// Convert between camelCase in the Node app to snake_case in the DB
27+
const knex = knexInit(knexStringcase(knexConfig))
2428

29+
// Handle all database errors by listening on the callback
2530
knex.on('query-error', (error) => {
2631
handleDatabaseError(error)
2732
})

src/middlewares/payIds.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export default async function getPaymentInfo(
109109

110110
return handleHttpError(
111111
HttpStatus.BadRequest,
112-
`Missing Accept header. Must have an Accept header of the form "application/{payment_network}(-{environment})+json".
112+
`Missing Accept header. Must have an Accept header of the form "application/{paymentNetwork}(-{environment})+json".
113113
Examples:
114114
- 'Accept: application/xrpl-mainnet+json'
115115
- 'Accept: application/btc-testnet+json'
@@ -132,7 +132,7 @@ export default async function getPaymentInfo(
132132

133133
return handleHttpError(
134134
HttpStatus.BadRequest,
135-
`Invalid Accept header. Must be of the form "application/{payment_network}(-{environment})+json".
135+
`Invalid Accept header. Must be of the form "application/{paymentNetwork}(-{environment})+json".
136136
Examples:
137137
- 'Accept: application/xrpl-mainnet+json'
138138
- 'Accept: application/btc-testnet+json'
@@ -176,7 +176,7 @@ export default async function getPaymentInfo(
176176
addressDetailType: AddressDetailType.CryptoAddress,
177177
addressDetails: addressInformation.details as CryptoAddressDetails,
178178
}
179-
if (addressInformation.payment_network === 'ACH') {
179+
if (addressInformation.paymentNetwork === 'ACH') {
180180
response = {
181181
addressDetailType: AddressDetailType.AchAddress,
182182
addressDetails: addressInformation.details as AchAddressDetails,
@@ -191,7 +191,7 @@ export default async function getPaymentInfo(
191191
res.locals.paymentInformation = response
192192
res.locals.response = response
193193
recordPayIdLookupResult(
194-
addressInformation.payment_network,
194+
addressInformation.paymentNetwork,
195195
addressInformation.environment ?? 'unknown',
196196
true,
197197
)

src/middlewares/users.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export async function postUser(
8080

8181
// TODO:(hbergren) Need to test here and in `putUser()` that `req.body.addresses` is well formed.
8282
// This includes making sure that everything that is not ACH or ILP is in a CryptoAddressDetails format.
83-
// And that we `toUpperCase()` payment_network and environment as part of parsing the addresses.
83+
// And that we `toUpperCase()` paymentNetwork and environment as part of parsing the addresses.
8484
await insertUser(payId, req.body.addresses)
8585

8686
// Set HTTP status and save the PayID to generate the Location header in later middleware

src/services/payIdReport.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export async function generatePayIdCountMetrics(): Promise<void> {
1010

1111
payIdCounts.forEach((payIdCount) => {
1212
recordPayIdCount(
13-
payIdCount.payment_network,
13+
payIdCount.paymentNetwork,
1414
payIdCount.environment,
1515
payIdCount.count,
1616
)

src/types/database.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,31 @@ import { CryptoAddressDetails, AchAddressDetails } from './publicAPI'
55
*/
66
export interface Account {
77
readonly id: string
8-
pay_id: string
8+
payId: string
99

10-
readonly created_at: Date
11-
readonly updated_at: Date
10+
readonly createdAt: Date
11+
readonly updatedAt: Date
1212
}
1313

1414
/**
1515
* Model of the Address table schema for the database.
1616
*/
1717
export interface Address {
1818
readonly id: number
19-
account_id: string
19+
accountId: string
2020

21-
payment_network: string
21+
paymentNetwork: string
2222
environment?: string | null
2323
details: CryptoAddressDetails | AchAddressDetails
2424

25-
readonly created_at: Date
26-
readonly updated_at: Date
25+
readonly createdAt: Date
26+
readonly updatedAt: Date
2727
}
2828

2929
/**
3030
* The information retrieved from or inserted into the database for a given address.
3131
*/
3232
export type AddressInformation = Pick<
3333
Address,
34-
'payment_network' | 'environment' | 'details'
34+
'paymentNetwork' | 'environment' | 'details'
3535
>

src/types/reports.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* Query result record for a count of PayIDs by environment and payment_network.
2+
* Query result record for a count of PayIDs by environment and paymentNetwork.
33
*/
44
export interface PayIdCount {
5-
payment_network: string
5+
paymentNetwork: string
66
environment: string
77
count: number
88
}

src/utils/acceptHeader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function getPreferredPaymentInfo(
6161
for (const acceptType of sortedAcceptMediaTypes) {
6262
const addressInformationForAcceptType = addressInformations.find(
6363
(result) =>
64-
result.payment_network === acceptType.paymentNetwork &&
64+
result.paymentNetwork === acceptType.paymentNetwork &&
6565
(result.environment ?? '') === acceptType.environment,
6666
)
6767

test/integration/data-access/database-errors.test.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('Data Access - Database Errors', function (): void {
1616

1717
const exampleAddresses = [
1818
{
19-
payment_network: 'ABC',
19+
paymentNetwork: 'ABC',
2020
environment: 'XYZ',
2121
details: {
2222
address: 'abc.xyz',
@@ -96,7 +96,7 @@ describe('Data Access - Database Errors', function (): void {
9696
const payId = 'alice$example.com'
9797
const addresses = [
9898
{
99-
payment_network: 'XRPL',
99+
paymentNetwork: 'XRPL',
100100
environment: 'TESTNET',
101101
},
102102
]
@@ -114,12 +114,12 @@ describe('Data Access - Database Errors', function (): void {
114114
)
115115
})
116116

117-
it('Raises an error when attempting to insert an address with an empty payment_network', async function () {
117+
it('Raises an error when attempting to insert an address with an empty paymentNetwork', async function () {
118118
// GIVEN a PayID and associated addresses to insert
119119
const payId = 'alice$example.com'
120120
const addresses = [
121121
{
122-
payment_network: '',
122+
paymentNetwork: '',
123123
environment: 'TESTNET',
124124
details: {
125125
address: 'abc',
@@ -143,7 +143,7 @@ describe('Data Access - Database Errors', function (): void {
143143
const payId = 'alice$example.com'
144144
const addresses = [
145145
{
146-
payment_network: 'XRPL',
146+
paymentNetwork: 'XRPL',
147147
environment: '',
148148
details: {
149149
address: 'abc',
@@ -162,19 +162,19 @@ describe('Data Access - Database Errors', function (): void {
162162
)
163163
})
164164

165-
it('Raises an error when attempting to insert multiple addresses for the same (payment_network, environment)', async function () {
165+
it('Raises an error when attempting to insert multiple addresses for the same (paymentNetwork, environment)', async function () {
166166
// GIVEN a PayID and associated addresses to insert
167167
const payId = 'alice$example.com'
168168
const addresses = [
169169
{
170-
payment_network: 'XRPL',
170+
paymentNetwork: 'XRPL',
171171
environment: 'TESTNET',
172172
details: {
173173
address: 'abc',
174174
},
175175
},
176176
{
177-
payment_network: 'XRPL',
177+
paymentNetwork: 'XRPL',
178178
environment: 'TESTNET',
179179
details: {
180180
address: 'xyz',

0 commit comments

Comments
 (0)