Skip to content

Commit 1423528

Browse files
committed
wip: Read+write new vault schema
See TODOs.
1 parent cacb663 commit 1423528

File tree

8 files changed

+245
-53
lines changed

8 files changed

+245
-53
lines changed

api/resolvers/vault.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default {
1717
getVaultEntries: async (parent, { keysFilter }, { me, models }, info) => {
1818
if (!me) throw new GqlAuthenticationError()
1919

20+
// TODO: replace this usage because of new schema
2021
const entries = await models.vaultEntry.findMany({
2122
where: {
2223
userId: me.id,
@@ -52,6 +53,7 @@ export default {
5253
}
5354

5455
for (const entry of entries) {
56+
// TODO: replace this usage because of new schema
5557
txs.push(models.vaultEntry.update({
5658
where: { userId_key: { userId: me.id, key: entry.key } },
5759
data: { value: entry.value, iv: entry.iv }
@@ -67,6 +69,7 @@ export default {
6769
where: { id: me.id },
6870
data: { vaultKeyHash: '' }
6971
}))
72+
// TODO: replace this usage because of new schema
7073
txs.push(models.vaultEntry.deleteMany({ where: { userId: me.id } }))
7174
await models.$transaction(txs)
7275
return true

api/resolvers/wallet.js

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { canReceive, getWalletByType } from '@/wallets/common'
2929
import performPaidAction from '../paidAction'
3030
import performPayingAction from '../payingAction'
3131
import { timeoutSignal, withTimeout } from '@/lib/time'
32+
import { deleteVault, hasVault, vaultNewSchematoTypedef, vaultPrismaFragments } from '@/wallets/vault'
3233

3334
function injectResolvers (resolvers) {
3435
console.group('injected GraphQL resolvers:')
@@ -43,6 +44,7 @@ function injectResolvers (resolvers) {
4344
// this mutation was sent from an unsynced client
4445
// to pass validation, we need to add the existing vault entries for validation
4546
// in case the client is removing the receiving config
47+
// TODO: replace this usage because of new schema
4648
existingVaultEntries = await models.vaultEntry.findMany({
4749
where: {
4850
walletId: Number(data.id)
@@ -159,17 +161,17 @@ const resolvers = {
159161
throw new GqlAuthenticationError()
160162
}
161163

162-
return await models.wallet.findMany({
163-
include: {
164-
vaultEntries: true
165-
},
164+
const wallets = await models.wallet.findMany({
165+
include: vaultPrismaFragments().include,
166166
where: {
167167
userId: me.id
168168
},
169169
orderBy: {
170170
priority: 'asc'
171171
}
172172
})
173+
174+
return wallets.map(vaultNewSchematoTypedef)
173175
},
174176
withdrawl: getWithdrawl,
175177
direct: async (parent, { id }, { me, models }) => {
@@ -569,7 +571,11 @@ const resolvers = {
569571
}
570572

571573
const logger = walletLogger({ wallet, models })
572-
await models.wallet.delete({ where: { userId: me.id, id: Number(id) } })
574+
575+
await models.$transaction([
576+
deleteVault(models, wallet),
577+
models.wallet.delete({ where: { userId: me.id, id: Number(id) } })
578+
])
573579

574580
if (canReceive({ def: getWalletByType(wallet.type), config: wallet.wallet })) {
575581
logger.info('details for receiving deleted')
@@ -838,15 +844,12 @@ async function upsertWallet (
838844

839845
const txs = []
840846

841-
if (id) {
842-
const oldVaultEntries = await models.vaultEntry.findMany({ where: { userId: me.id, walletId: Number(id) } })
847+
const vaultFrags = vaultPrismaFragments({ ...wallet, vaultEntries })
843848

844-
// createMany is the set difference of the new - old
845-
// deleteMany is the set difference of the old - new
846-
// updateMany is the intersection of the old and new
847-
const difference = (a = [], b = [], key = 'key') => a.filter(x => !b.find(y => y[key] === x[key]))
848-
const intersectionMerge = (a = [], b = [], key = 'key') => a.filter(x => b.find(y => y[key] === x[key]))
849-
.map(x => ({ [key]: x[key], ...b.find(y => y[key] === x[key]) }))
849+
if (id) {
850+
const dbWallet = await models.wallet.findUnique({
851+
where: { id: Number(id), userId: me.id }
852+
})
850853

851854
txs.push(
852855
models.wallet.update({
@@ -859,57 +862,29 @@ async function upsertWallet (
859862
? {
860863
[wallet.field]: {
861864
upsert: {
862-
create: recvConfig,
863-
update: recvConfig
864-
}
865-
}
866-
}
867-
: {}),
868-
...(vaultEntries
869-
? {
870-
vaultEntries: {
871-
deleteMany: difference(oldVaultEntries, vaultEntries, 'key').map(({ key }) => ({
872-
userId: me.id, key
873-
})),
874-
create: difference(vaultEntries, oldVaultEntries, 'key').map(({ key, iv, value }) => ({
875-
key, iv, value, userId: me.id
876-
})),
877-
update: intersectionMerge(oldVaultEntries, vaultEntries, 'key').map(({ key, iv, value }) => ({
878-
where: { userId_key: { userId: me.id, key } },
879-
data: { value, iv }
880-
}))
865+
create: { ...recvConfig, ...vaultFrags?.create },
866+
update: { ...recvConfig, ...vaultFrags?.upsert }
867+
},
868+
// XXX the check is required because the update would fail if there is no row to delete ...
869+
update: await hasVault(models, dbWallet) ? vaultFrags?.deleteOnUpdate : undefined
881870
}
882871
}
883872
: {})
884-
885873
},
886-
include: {
887-
vaultEntries: true
888-
}
874+
include: vaultFrags?.include
889875
})
890876
)
891877
} else {
892878
txs.push(
893879
models.wallet.create({
894-
include: {
895-
vaultEntries: true
896-
},
880+
include: vaultFrags?.include,
897881
data: {
898882
enabled,
899883
priority,
900884
userId: me.id,
901885
type: wallet.type,
902886
// client only wallets have no receive config and thus don't have their own table
903-
...(Object.keys(recvConfig).length > 0 ? { [wallet.field]: { create: recvConfig } } : {}),
904-
...(vaultEntries
905-
? {
906-
vaultEntries: {
907-
createMany: {
908-
data: vaultEntries?.map(({ key, iv, value }) => ({ key, iv, value, userId: me.id }))
909-
}
910-
}
911-
}
912-
: {})
887+
...(Object.keys(recvConfig).length > 0 ? { [wallet.field]: { create: { ...recvConfig, ...vaultFrags?.create } } } : {})
913888
}
914889
})
915890
)
@@ -946,7 +921,9 @@ async function upsertWallet (
946921
}
947922

948923
const [upsertedWallet] = await models.$transaction(txs)
949-
return upsertedWallet
924+
925+
// migrate from old schema to new schema for vault
926+
return vaultNewSchematoTypedef(upsertedWallet)
950927
}
951928

952929
export async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd, headers, wallet, logger }) {

lib/object.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const get = (obj, path) => {
2+
if (!path) return obj
3+
const keys = path.split('.')
4+
return keys.reduce((obj, key) => obj?.[key], obj)
5+
}
6+
7+
export const set = (obj, path, value) => {
8+
const keys = path.split('.')
9+
const lastKey = keys.pop()
10+
const parent = get(obj, keys.join('.'))
11+
parent[lastKey] = value
12+
}
13+
14+
export const remove = (obj, path) => {
15+
const keys = path.split('.')
16+
const lastKey = keys.pop()
17+
const parent = get(obj, keys.join('.'))
18+
delete parent?.[lastKey]
19+
}
20+
21+
export const move = (obj, fromPath, toPath) => {
22+
const value = get(obj, fromPath)
23+
remove(obj, fromPath)
24+
set(obj, toPath, value)
25+
}

lib/object.spec.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* eslint-env jest */
2+
3+
import { get, move, remove, set } from './object'
4+
5+
describe('object helpers', () => {
6+
test.each([
7+
[{ a: 'b' }, '', { a: 'b' }],
8+
[{ a: 'b' }, 'a', 'b'],
9+
[{ a: { b: { c: 'd' } } }, 'a.b', { c: 'd' }]
10+
])(
11+
'gets a nested value: get(%p, %p) returns %p',
12+
(obj, path, expected) => {
13+
expect(get(obj, path)).toEqual(expected)
14+
})
15+
16+
test.each([
17+
[{ a: 'b' }, '', { a: 'b' }],
18+
[{ a: { b: { c: 'd' } } }, 'a.b.c', 'e', { a: { b: { c: 'e' } } }]
19+
])(
20+
'sets a nested value: set(%p, %p, %p) returns %p',
21+
() => {
22+
const obj = { a: { b: { c: 'd' } } }
23+
set(obj, 'a.b.c', 'e')
24+
expect(obj).toEqual({ a: { b: { c: 'e' } } })
25+
})
26+
27+
test.each([
28+
[{ a: 'b' }, 'a', {}],
29+
[{ a: { b: { c: 'd' } } }, 'a.b.c', { a: { b: {} } }]
30+
])(
31+
'removes a nested values: remove(%p, %p) returns %p',
32+
(obj, path, expected) => {
33+
remove(obj, path)
34+
expect(obj).toEqual(expected)
35+
})
36+
37+
test.each([
38+
[{ a: { b1: { c: 'd' } } }, 'a.b1.c', 'a.b1.d', { a: { b1: { d: 'd' } } }],
39+
[{ a: { b1: { c11: 'd1', c12: 'd2' }, b2: { c21: 'd3', c22: 'd4' } } }, 'a.b1.c11', 'a.b2.c22', { a: { b1: { c12: 'd2' }, b2: { c21: 'd3', c22: 'd1' } } }]
40+
])(
41+
'moves a nested value: move(%p, %p, %p) returns %p',
42+
(obj, fromPath, toPath, expected) => {
43+
move(obj, fromPath, toPath)
44+
expect(obj).toEqual(expected)
45+
}
46+
)
47+
})

wallets/blink/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export const fields = [
1616
.matches(/^blink_[A-Za-z0-9]+$/, { message: 'must match pattern blink_A-Za-z0-9' }),
1717
help: `you can get an API key from [Blink Dashboard](${galoyBlinkDashboardUrl}).\nPlease make sure to select ONLY the 'Read' and 'Write' scopes when generating this API key.`,
1818
requiredWithout: 'apiKeyRecv',
19-
optional: 'for sending'
19+
optional: 'for sending',
20+
TODO_newName: 'apiKeySend'
2021
},
2122
{
2223
name: 'currency',
@@ -32,7 +33,8 @@ export const fields = [
3233
.transform(value => value ? value.toUpperCase() : 'BTC')
3334
.oneOf(['USD', 'BTC'], 'must be BTC or USD'),
3435
optional: 'for sending',
35-
requiredWithout: 'currencyRecv'
36+
requiredWithout: 'currencyRecv',
37+
TODO_newName: 'currencySend'
3638
},
3739
{
3840
name: 'apiKeyRecv',

wallets/nwc/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export const fields = [
1515
optional: 'for sending',
1616
clientOnly: true,
1717
requiredWithout: 'nwcUrlRecv',
18-
validate: string().nwcUrl()
18+
validate: string().nwcUrl(),
19+
TODO_newName: 'nwcUrlSend'
1920
},
2021
{
2122
name: 'nwcUrlRecv',

0 commit comments

Comments
 (0)