From 1d02666c1c8d9ef49d927a1dab60f55c7360b908 Mon Sep 17 00:00:00 2001 From: golobitch Date: Tue, 25 Feb 2025 22:02:03 +0100 Subject: [PATCH 01/26] feat(graphql): schema update for specifying settings when creating tenant --- .../generated/graphql.ts | 2 ++ .../src/graphql/generated/graphql.schema.json | 20 +++++++++++++++++++ .../backend/src/graphql/generated/graphql.ts | 2 ++ packages/backend/src/graphql/schema.graphql | 2 ++ packages/frontend/app/generated/graphql.ts | 2 ++ .../src/generated/graphql.ts | 2 ++ test/integration/lib/generated/graphql.ts | 2 ++ 7 files changed, 32 insertions(+) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 77a84f73a1..05a1c263eb 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -378,6 +378,8 @@ export type CreateTenantInput = { idpSecret?: InputMaybe; /** Public name for the tenant. */ publicName?: InputMaybe; + /** Initial settings for tenant. */ + settings?: InputMaybe>; }; export type CreateTenantSettingsInput = { diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 77a408266d..bc1fdc29c4 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -2202,6 +2202,26 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "settings", + "description": "Initial settings for tenant.", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "TenantSettingInput", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 77a84f73a1..05a1c263eb 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -378,6 +378,8 @@ export type CreateTenantInput = { idpSecret?: InputMaybe; /** Public name for the tenant. */ publicName?: InputMaybe; + /** Initial settings for tenant. */ + settings?: InputMaybe>; }; export type CreateTenantSettingsInput = { diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 397c1feb61..cd588f656f 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1650,6 +1650,8 @@ input CreateTenantInput { idpSecret: String "Public name for the tenant." publicName: String + "Initial settings for tenant." + settings: [TenantSettingInput!] } input UpdateTenantInput { diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index edf1b67584..ffdece0ff8 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -378,6 +378,8 @@ export type CreateTenantInput = { idpSecret?: InputMaybe; /** Public name for the tenant. */ publicName?: InputMaybe; + /** Initial settings for tenant. */ + settings?: InputMaybe>; }; export type CreateTenantSettingsInput = { diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 77a84f73a1..05a1c263eb 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -378,6 +378,8 @@ export type CreateTenantInput = { idpSecret?: InputMaybe; /** Public name for the tenant. */ publicName?: InputMaybe; + /** Initial settings for tenant. */ + settings?: InputMaybe>; }; export type CreateTenantSettingsInput = { diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 77a84f73a1..05a1c263eb 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -378,6 +378,8 @@ export type CreateTenantInput = { idpSecret?: InputMaybe; /** Public name for the tenant. */ publicName?: InputMaybe; + /** Initial settings for tenant. */ + settings?: InputMaybe>; }; export type CreateTenantSettingsInput = { From 5537c1780208564a10d7e547e5fdb9f44ad96210 Mon Sep 17 00:00:00 2001 From: golobitch Date: Tue, 25 Feb 2025 22:02:38 +0100 Subject: [PATCH 02/26] feat(tenant): add possibility to specify initial tenant settings as an operator --- packages/backend/src/tenants/service.ts | 18 ++++++++++-------- packages/backend/src/tenants/settings/model.ts | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 4cd56dce85..fb19ddee29 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -8,6 +8,7 @@ import { TenantSettingService } from './settings/service' import { TenantSetting } from './settings/model' import type { IAppConfig } from '../config/app' import { TenantError } from './errors' +import { TenantSettingInput } from '../graphql/generated/graphql' export interface TenantService { get: (id: string, includeDeleted?: boolean) => Promise @@ -79,6 +80,7 @@ interface CreateTenantOptions { idpSecret?: string idpConsentUrl?: string publicName?: string + settings?: TenantSettingInput[] } async function createTenant( @@ -87,7 +89,7 @@ async function createTenant( ): Promise { const trx = await deps.knex.transaction() try { - const { email, apiSecret, publicName, idpSecret, idpConsentUrl } = options + const { email, apiSecret, publicName, idpSecret, idpConsentUrl, settings } = options const tenant = await Tenant.query(trx).insertAndFetch({ email, publicName, @@ -102,13 +104,13 @@ async function createTenant( idpConsentUrl }) - await deps.tenantSettingService.create( - { - tenantId: tenant.id, - setting: TenantSetting.default() - }, - { trx } - ) + const createInitialTenantSettingsOptions = { tenantId: tenant.id, setting: TenantSetting.default() } ; + if (settings) { + createInitialTenantSettingsOptions.setting = createInitialTenantSettingsOptions.setting.concat(settings) + } + + await deps.tenantSettingService.create(createInitialTenantSettingsOptions, { trx } +) await trx.commit() diff --git a/packages/backend/src/tenants/settings/model.ts b/packages/backend/src/tenants/settings/model.ts index e91e24c057..cbba5ac831 100644 --- a/packages/backend/src/tenants/settings/model.ts +++ b/packages/backend/src/tenants/settings/model.ts @@ -6,7 +6,8 @@ export const TenantSettingKeys = { EXCHANGE_RATES_URL: { name: 'EXCHANGE_RATES_URL' }, WEBHOOK_URL: { name: 'WEBHOOK_URL' }, WEBHOOK_TIMEOUT: { name: 'WEBHOOK_TIMEOUT', default: 2000 }, - WEBHOOK_MAX_RETRY: { name: 'WEBHOOK_MAX_RETRY', default: 10 } + WEBHOOK_MAX_RETRY: { name: 'WEBHOOK_MAX_RETRY', default: 10 }, + WALLET_ADDRESS_URL: { name: 'WALLET_ADDRESS_URL' } } export class TenantSetting extends BaseModel { From dfb42e9f09bb41798d4733e073c253638cae37d2 Mon Sep 17 00:00:00 2001 From: golobitch Date: Tue, 25 Feb 2025 22:02:53 +0100 Subject: [PATCH 03/26] test(tenant): create with settings --- packages/backend/src/tenants/service.test.ts | 28 +++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/tenants/service.test.ts b/packages/backend/src/tenants/service.test.ts index 6dcb645737..904e543617 100644 --- a/packages/backend/src/tenants/service.test.ts +++ b/packages/backend/src/tenants/service.test.ts @@ -15,7 +15,7 @@ import { createTenant } from '../tests/tenant' import { CacheDataStore } from '../middleware/cache/data-stores' import { AuthServiceClient } from '../auth-service-client/client' import { withConfigOverride } from '../tests/helpers' -import { TenantSetting } from './settings/model' +import { TenantSetting, TenantSettingKeys } from './settings/model' import { TenantSettingService } from './settings/service' import { isTenantError, TenantError } from './errors' @@ -146,6 +146,32 @@ describe('Tenant Service', (): void => { expect(tenantSettings.length).toBeGreaterThan(0) }) + test('can create a tenant with a setting', async () => { + const walletAddressUrl = 'https://example.com' + const createOptions = { + apiSecret: 'test-api-secret', + publicName: 'test tenant', + email: faker.internet.email(), + idpConsentUrl: faker.internet.url(), + idpSecret: 'test-idp-secret', + settings: [ + { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: walletAddressUrl } + ] + } + + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) + + const tenant = await tenantService.create(createOptions) + const tenantSetting = await TenantSetting.query() + .where('tenantId', tenant.id) + .andWhere('key', TenantSettingKeys.WALLET_ADDRESS_URL.name) + + expect(tenantSetting.length).toBe(1) + expect(tenantSetting[0].value).toEqual(walletAddressUrl) + }) + test('tenant creation rolls back if auth tenant create fails', async (): Promise => { const createOptions = { apiSecret: 'test-api-secret', From ae1ab4b61318e1e2900f92161dfe1aa4f1c5e487 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 1 Mar 2025 11:13:56 +0100 Subject: [PATCH 04/26] feat(wallet-address): rename url to address --- ...30_rename_wallet_address_url_to_address.js | 19 ++++++++ .../src/open_payments/wallet_address/model.ts | 4 +- .../wallet_address/service.test.ts | 46 +++++++++++-------- .../open_payments/wallet_address/service.ts | 8 ++-- packages/backend/src/tests/walletAddress.ts | 4 +- 5 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js diff --git a/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js b/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js new file mode 100644 index 0000000000..88a248b3e1 --- /dev/null +++ b/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js @@ -0,0 +1,19 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex.schema.alterTable('walletAddresses', (table) => { + table.renameColumn('url', 'address') + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.alterTable('walletAddresses', (table) => { + table.renameColumn('address', 'url') + }) +} diff --git a/packages/backend/src/open_payments/wallet_address/model.ts b/packages/backend/src/open_payments/wallet_address/model.ts index 3991c16f53..972c1ce2f5 100644 --- a/packages/backend/src/open_payments/wallet_address/model.ts +++ b/packages/backend/src/open_payments/wallet_address/model.ts @@ -56,7 +56,7 @@ export class WalletAddress public keys?: WalletAddressKey[] public additionalProperties?: WalletAddressAdditionalProperty[] - public url!: string + public address!: string public publicName?: string public readonly assetId!: string @@ -124,7 +124,7 @@ export class WalletAddress resourceServer: string }): OpenPaymentsWalletAddress { const returnVal: OpenPaymentsWalletAddress = { - id: this.url, + id: this.address, publicName: this.publicName, assetCode: this.asset.code, assetScale: this.asset.scale, diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 1fb01fc48d..5b04484575 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -27,6 +27,8 @@ import { sleep } from '../../shared/utils' import { withConfigOverride } from '../../tests/helpers' import { WalletAddressAdditionalProperty } from './additional_property/model' import { CacheDataStore } from '../../middleware/cache/data-stores' +import { createTenantSettings } from '../../tests/tenantSettings' +import { TenantSettingKeys } from '../../tenants/settings/model' describe('Open Payments Wallet Address Service', (): void => { let deps: IocContract @@ -57,14 +59,22 @@ describe('Open Payments Wallet Address Service', (): void => { await appContainer.shutdown() }) - describe('Create or Get Wallet Address', (): void => { + describe('Create or Get Wallet Address3', (): void => { let options: CreateOptions beforeEach(async (): Promise => { const { id: tenantId } = await createTenant(deps) const { id: assetId } = await createAsset(deps, undefined, tenantId) + + await createTenantSettings(deps, { + tenantId: tenantId, + setting: [ + { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: 'https://alice.me' } + ] + }) + options = { - url: 'https://alice.me/.well-known/pay', + address: 'https://alice.me/.well-known/pay', assetId, tenantId } @@ -110,7 +120,7 @@ describe('Open Payments Wallet Address Service', (): void => { await expect( walletAddressService.create({ ...options, - url + address: url }) ).resolves.toEqual(WalletAddressError.InvalidUrl) } @@ -119,17 +129,17 @@ describe('Open Payments Wallet Address Service', (): void => { test.each(FORBIDDEN_PATHS.map((path) => [path]))( 'Wallet address cannot be created with forbidden url path (%s)', async (path): Promise => { - const url = `https://alice.me${path}` + const address = `https://alice.me${path}` await expect( walletAddressService.create({ ...options, - url + address }) ).resolves.toEqual(WalletAddressError.InvalidUrl) await expect( walletAddressService.create({ ...options, - url: `${url}/more/path` + address: `${address}/more/path` }) ).resolves.toEqual(WalletAddressError.InvalidUrl) } @@ -144,26 +154,26 @@ describe('Open Payments Wallet Address Service', (): void => { }) test('Creating wallet address with case insensitiveness', async (): Promise => { - const url = 'https://Alice.me/pay' + const address = 'https://Alice.me/pay' await expect( walletAddressService.create({ ...options, - url + address }) - ).resolves.toMatchObject({ url: url.toLowerCase() }) + ).resolves.toMatchObject({ address: address.toLowerCase() }) }) test('Wallet address cannot be created if the url is duplicated', async (): Promise => { - const url = 'https://Alice.me/pay' + const address = 'https://Alice.me/pay' const wallet = walletAddressService.create({ ...options, - url + address }) assert.ok(!isWalletAddressError(wallet)) await expect( walletAddressService.create({ ...options, - url + address }) ).resolves.toEqual(WalletAddressError.DuplicateWalletAddress) }) @@ -443,15 +453,15 @@ describe('Open Payments Wallet Address Service', (): void => { tenantId: Config.operatorTenantId }) await expect( - walletAddressService.getByUrl(walletAddress.url) + walletAddressService.getByUrl(walletAddress.address) ).resolves.toEqual(walletAddress) await expect( - walletAddressService.getByUrl(walletAddress.url + '/path') + walletAddressService.getByUrl(walletAddress.address + '/path') ).resolves.toBeUndefined() await expect( - walletAddressService.getByUrl('prefix+' + walletAddress.url) + walletAddressService.getByUrl('prefix+' + walletAddress.address) ).resolves.toBeUndefined() }) @@ -476,7 +486,7 @@ describe('Open Payments Wallet Address Service', (): void => { tenantId: Config.operatorTenantId }) await expect( - walletAddressService.getOrPollByUrl(walletAddress.url) + walletAddressService.getOrPollByUrl(walletAddress.address) ).resolves.toEqual(walletAddress) }) }) @@ -521,7 +531,7 @@ describe('Open Payments Wallet Address Service', (): void => { await sleep(5) return createWalletAddress(deps, { tenantId: Config.operatorTenantId, - url: walletAddressUrl + address: walletAddressUrl }) })() ]) @@ -905,7 +915,7 @@ describe('Open Payments Wallet Address Service using Cache', (): void => { walletAddress.id, expect.objectContaining({ id: walletAddress.id, - url: walletAddress.url + address: walletAddress.address }) ) diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index c5f5d852d8..41c4833c57 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -40,7 +40,7 @@ export type WalletAddressAdditionalPropertyInput = Pick< export interface CreateOptions extends Options { tenantId: string - url: string + address: string assetId: string additionalProperties?: WalletAddressAdditionalPropertyInput[] } @@ -165,7 +165,7 @@ async function createWalletAddress( deps: ServiceDependencies, options: CreateOptions ): Promise { - if (!isValidWalletAddressUrl(options.url)) { + if (!isValidWalletAddressUrl(options.address)) { return WalletAddressError.InvalidUrl } @@ -182,7 +182,7 @@ async function createWalletAddress( deps.knex ).insertGraphAndFetch({ tenantId: options.tenantId, - url: options.url.toLowerCase(), + address: options.address.toLowerCase(), publicName: options.publicName, assetId: asset.id, additionalProperties: additionalProperties @@ -341,7 +341,7 @@ async function getWalletAddressByUrl( if (tenantId) query.andWhere({ tenantId }) const walletAddress = await query.findOne({ - url: url.toLowerCase() + address: url.toLowerCase() }) if (walletAddress) { const asset = await deps.assetService.get(walletAddress.assetId) diff --git a/packages/backend/src/tests/walletAddress.ts b/packages/backend/src/tests/walletAddress.ts index cc3c55092d..a2b1653efa 100644 --- a/packages/backend/src/tests/walletAddress.ts +++ b/packages/backend/src/tests/walletAddress.ts @@ -36,7 +36,7 @@ export async function createWalletAddress( assetId: options.assetId || (await createAsset(deps, undefined, tenantIdToUse)).id, tenantId: tenantIdToUse, - url: options.url || `https://${faker.internet.domainName()}/.well-known/pay` + address: options.address || `https://${faker.internet.domainName()}/.well-known/pay` })) as MockWalletAddress if (isWalletAddressError(walletAddressOrError)) { throw new Error(walletAddressOrError) @@ -52,7 +52,7 @@ export async function createWalletAddress( ) } if (options.mockServerPort) { - const url = new URL(walletAddressOrError.url) + const url = new URL(walletAddressOrError.address) walletAddressOrError.scope = nock(url.origin) .get((uri) => uri.startsWith(url.pathname)) .matchHeader('Accept', /application\/((ilp-stream|spsp4)\+)?json*./) From fba2634636056646360d010a5f267c1444a12b40 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 1 Mar 2025 23:11:35 +0100 Subject: [PATCH 05/26] fix(tenant-settings): duplicate key for tenant --- ...250301203110_unique_tenant_settings_key.js | 19 +++++++++++++++++++ .../backend/src/tenants/settings/service.ts | 13 ++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 packages/backend/migrations/20250301203110_unique_tenant_settings_key.js diff --git a/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js b/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js new file mode 100644 index 0000000000..f487bd7761 --- /dev/null +++ b/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js @@ -0,0 +1,19 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex.schema.alterTable('tenantSettings', function(table) { + table.unique(['tenantId', 'key']); + }); +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.alterTable('tenantSettings', function(table) { + table.dropUnique(['tenantId', 'key']); + }); +} diff --git a/packages/backend/src/tenants/settings/service.ts b/packages/backend/src/tenants/settings/service.ts index bf2abb1935..684e807371 100644 --- a/packages/backend/src/tenants/settings/service.ts +++ b/packages/backend/src/tenants/settings/service.ts @@ -118,20 +118,23 @@ async function createTenantSetting( options: CreateOptions, extra?: ExtraOptions ) { - const dataToInsert = options.setting + const dataToUpsert = options.setting .filter((setting) => Object.keys(TenantSettingKeys).includes(setting.key)) .map((s) => ({ tenantId: options.tenantId, ...s })) - if (Object.keys(dataToInsert).length <= 0) { + if (Object.keys(dataToUpsert).length <= 0) { return [] } - return TenantSetting.query(extra?.trx ?? deps.knex).insertAndFetch( - dataToInsert - ) + return TenantSetting + .query(extra?.trx ?? deps.knex) + .insert(dataToUpsert) + .onConflict(['tenantId', 'key']) + .merge() + .returning('*'); } async function getTenantSettingPageForTenant( From 7e5c363cc6f882a9444b17a0dff1681cd3804cb8 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 1 Mar 2025 23:16:40 +0100 Subject: [PATCH 06/26] feat(wallet-address): replace url field with address field url field was replaced with address field, because now with range in wallet address, it is possible for the caller, to specify just the portion of the wallet address url. There is no need to specify the whole url in order to create wallet address. #3278 --- packages/backend/src/index.ts | 3 +- .../open_payments/wallet_address/errors.ts | 10 +++- .../wallet_address/service.test.ts | 27 ++++++++- .../open_payments/wallet_address/service.ts | 59 +++++++++++++++++-- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d84b7c2c7b..8583d7fd09 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -420,7 +420,8 @@ export function initIocContainer( accountingService: await deps.use('accountingService'), webhookService: await deps.use('webhookService'), assetService: await deps.use('assetService'), - walletAddressCache: await deps.use('walletAddressCache') + walletAddressCache: await deps.use('walletAddressCache'), + tenantSettingService: await deps.use('tenantSettingService') }) }) container.singleton('spspRoutes', async (deps) => { diff --git a/packages/backend/src/open_payments/wallet_address/errors.ts b/packages/backend/src/open_payments/wallet_address/errors.ts index 03d672762c..3d529215e4 100644 --- a/packages/backend/src/open_payments/wallet_address/errors.ts +++ b/packages/backend/src/open_payments/wallet_address/errors.ts @@ -4,7 +4,8 @@ export enum WalletAddressError { InvalidUrl = 'InvalidUrl', UnknownAsset = 'UnknownAsset', UnknownWalletAddress = 'UnknownWalletAddress', - DuplicateWalletAddress = 'DuplicateWalletAddress' + DuplicateWalletAddress = 'DuplicateWalletAddress', + WalletAddressSettingNotFound = 'WalletAddressSettingNotFound', } // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types @@ -17,7 +18,8 @@ export const errorToCode: { [WalletAddressError.InvalidUrl]: GraphQLErrorCode.BadUserInput, [WalletAddressError.UnknownAsset]: GraphQLErrorCode.BadUserInput, [WalletAddressError.UnknownWalletAddress]: GraphQLErrorCode.NotFound, - [WalletAddressError.DuplicateWalletAddress]: GraphQLErrorCode.Duplicate + [WalletAddressError.DuplicateWalletAddress]: GraphQLErrorCode.Duplicate, + [WalletAddressError.WalletAddressSettingNotFound]: GraphQLErrorCode.NotFound } export const errorToMessage: { @@ -27,5 +29,7 @@ export const errorToMessage: { [WalletAddressError.UnknownAsset]: 'unknown asset', [WalletAddressError.UnknownWalletAddress]: 'unknown wallet address', [WalletAddressError.DuplicateWalletAddress]: - 'Duplicate wallet address found with the same url' + 'Duplicate wallet address found with the same url', + [WalletAddressError.WalletAddressSettingNotFound]: + 'Setting for wallet address has not been found.' } diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 5b04484575..704f38744f 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -60,10 +60,11 @@ describe('Open Payments Wallet Address Service', (): void => { }) describe('Create or Get Wallet Address3', (): void => { + let tenantId: string; let options: CreateOptions beforeEach(async (): Promise => { - const { id: tenantId } = await createTenant(deps) + tenantId = (await createTenant(deps)).id const { id: assetId } = await createAsset(deps, undefined, tenantId) await createTenantSettings(deps, { @@ -99,6 +100,30 @@ describe('Open Payments Wallet Address Service', (): void => { } ) + test.each` + setting | address | generated + ${'https://alice.me/ilp'} | ${'https://alice.me/ilp/test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp'} | ${'test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp'} | ${'/test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp/'} | ${'test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp/'} | ${'/test'} | ${'https://alice.me/ilp/test'} + `('should create address $generated with address $address and setting $setting', async ({ setting, address, generated }): Promise => { + await createTenantSettings(deps, { + tenantId: tenantId, + setting: [ + { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: setting } + ] + }) + + const walletAddress = await walletAddressService.create({ + ...options, + address + }) + + assert.ok(!isWalletAddressError(walletAddress)) + expect(walletAddress.address).toEqual(generated) + }) + test('Cannot create wallet address with unknown asset', async (): Promise => { await expect( walletAddressService.create({ diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 41c4833c57..69f60d42de 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -28,6 +28,8 @@ import { poll } from '../../shared/utils' import { WalletAddressAdditionalProperty } from './additional_property/model' import { AssetService } from '../../asset/service' import { CacheDataStore } from '../../middleware/cache/data-stores' +import { TenantSetting, TenantSettingKeys } from '../../tenants/settings/model' +import { TenantSettingService } from '../../tenants/settings/service' interface Options { publicName?: string @@ -84,6 +86,7 @@ interface ServiceDependencies extends BaseService { webhookService: WebhookService assetService: AssetService walletAddressCache: CacheDataStore + tenantSettingService: TenantSettingService } export async function createWalletAddressService({ @@ -93,7 +96,8 @@ export async function createWalletAddressService({ accountingService, webhookService, assetService, - walletAddressCache + walletAddressCache, + tenantSettingService }: ServiceDependencies): Promise { const log = logger.child({ service: 'WalletAddressService' @@ -105,7 +109,8 @@ export async function createWalletAddressService({ accountingService, webhookService, assetService, - walletAddressCache + walletAddressCache, + tenantSettingService } return { create: (options) => createWalletAddress(deps, options), @@ -165,7 +170,53 @@ async function createWalletAddress( deps: ServiceDependencies, options: CreateOptions ): Promise { - if (!isValidWalletAddressUrl(options.address)) { + const found = await deps.tenantSettingService.get({ + tenantId: options.tenantId, + key: TenantSettingKeys.WALLET_ADDRESS_URL.name + }) as TenantSetting[]; + + if (!found || found.length === 0) { + return WalletAddressError.WalletAddressSettingNotFound; + } + + const tenantWalletAddressUrl = new URL(found[0].value); + + let tenantBaseUrl = tenantWalletAddressUrl.toString(); + if (!tenantWalletAddressUrl.pathname.endsWith('/')) { + tenantBaseUrl = tenantWalletAddressUrl.origin + tenantWalletAddressUrl.pathname + '/' + } + + const isValidUrl = (str: string): boolean => { + try { + new URL(str); + return true; + } catch { + return false; + } + }; + + let finalWalletAddressUrl: string; + if (isValidUrl(options.address)) { + // in case that client provided full url, verify that it starts with the tenant's URL + const walletAddressUrl = new URL(options.address); + if (!walletAddressUrl.href.startsWith(tenantWalletAddressUrl.href)) { + return WalletAddressError.InvalidUrl; + } + finalWalletAddressUrl = walletAddressUrl.toString() + } else { + // in case that client provided just the path / wallet address name, construct the address using the wallet address url from tenant setting + try { + let relativePath = options.address + if (relativePath.startsWith('/')) { + relativePath = relativePath.substring(1) + } + finalWalletAddressUrl = tenantBaseUrl + relativePath + } catch (err) { + return WalletAddressError.InvalidUrl; + } + } + + if (!isValidWalletAddressUrl(finalWalletAddressUrl)) { return WalletAddressError.InvalidUrl } @@ -182,7 +233,7 @@ async function createWalletAddress( deps.knex ).insertGraphAndFetch({ tenantId: options.tenantId, - address: options.address.toLowerCase(), + address: finalWalletAddressUrl.toLowerCase(), publicName: options.publicName, assetId: asset.id, additionalProperties: additionalProperties From 212ce26f50b5edf0b7f4f48090824e30b93e56a2 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 1 Mar 2025 23:18:47 +0100 Subject: [PATCH 07/26] chore(backend): format --- ...30_rename_wallet_address_url_to_address.js | 12 ++--- ...250301203110_unique_tenant_settings_key.js | 12 ++--- .../open_payments/wallet_address/errors.ts | 2 +- .../wallet_address/service.test.ts | 50 +++++++++++-------- .../open_payments/wallet_address/service.ts | 33 ++++++------ packages/backend/src/tenants/service.test.ts | 5 +- packages/backend/src/tenants/service.ts | 18 ++++--- .../backend/src/tenants/settings/service.ts | 5 +- packages/backend/src/tests/walletAddress.ts | 4 +- 9 files changed, 79 insertions(+), 62 deletions(-) diff --git a/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js b/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js index 88a248b3e1..b5bdee041d 100644 --- a/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js +++ b/packages/backend/migrations/20250301103930_rename_wallet_address_url_to_address.js @@ -3,9 +3,9 @@ * @returns { Promise } */ exports.up = function (knex) { - return knex.schema.alterTable('walletAddresses', (table) => { - table.renameColumn('url', 'address') - }) + return knex.schema.alterTable('walletAddresses', (table) => { + table.renameColumn('url', 'address') + }) } /** @@ -13,7 +13,7 @@ exports.up = function (knex) { * @returns { Promise } */ exports.down = function (knex) { - return knex.schema.alterTable('walletAddresses', (table) => { - table.renameColumn('address', 'url') - }) + return knex.schema.alterTable('walletAddresses', (table) => { + table.renameColumn('address', 'url') + }) } diff --git a/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js b/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js index f487bd7761..41d3ce1402 100644 --- a/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js +++ b/packages/backend/migrations/20250301203110_unique_tenant_settings_key.js @@ -3,9 +3,9 @@ * @returns { Promise } */ exports.up = function (knex) { - return knex.schema.alterTable('tenantSettings', function(table) { - table.unique(['tenantId', 'key']); - }); + return knex.schema.alterTable('tenantSettings', function (table) { + table.unique(['tenantId', 'key']) + }) } /** @@ -13,7 +13,7 @@ exports.up = function (knex) { * @returns { Promise } */ exports.down = function (knex) { - return knex.schema.alterTable('tenantSettings', function(table) { - table.dropUnique(['tenantId', 'key']); - }); + return knex.schema.alterTable('tenantSettings', function (table) { + table.dropUnique(['tenantId', 'key']) + }) } diff --git a/packages/backend/src/open_payments/wallet_address/errors.ts b/packages/backend/src/open_payments/wallet_address/errors.ts index 3d529215e4..6d0f2151cd 100644 --- a/packages/backend/src/open_payments/wallet_address/errors.ts +++ b/packages/backend/src/open_payments/wallet_address/errors.ts @@ -5,7 +5,7 @@ export enum WalletAddressError { UnknownAsset = 'UnknownAsset', UnknownWalletAddress = 'UnknownWalletAddress', DuplicateWalletAddress = 'DuplicateWalletAddress', - WalletAddressSettingNotFound = 'WalletAddressSettingNotFound', + WalletAddressSettingNotFound = 'WalletAddressSettingNotFound' } // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 704f38744f..b0e528d77f 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -60,7 +60,7 @@ describe('Open Payments Wallet Address Service', (): void => { }) describe('Create or Get Wallet Address3', (): void => { - let tenantId: string; + let tenantId: string let options: CreateOptions beforeEach(async (): Promise => { @@ -70,7 +70,10 @@ describe('Open Payments Wallet Address Service', (): void => { await createTenantSettings(deps, { tenantId: tenantId, setting: [ - { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: 'https://alice.me' } + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: 'https://alice.me' + } ] }) @@ -101,28 +104,31 @@ describe('Open Payments Wallet Address Service', (): void => { ) test.each` - setting | address | generated - ${'https://alice.me/ilp'} | ${'https://alice.me/ilp/test'} | ${'https://alice.me/ilp/test'} - ${'https://alice.me/ilp'} | ${'test'} | ${'https://alice.me/ilp/test'} - ${'https://alice.me/ilp'} | ${'/test'} | ${'https://alice.me/ilp/test'} - ${'https://alice.me/ilp/'} | ${'test'} | ${'https://alice.me/ilp/test'} - ${'https://alice.me/ilp/'} | ${'/test'} | ${'https://alice.me/ilp/test'} - `('should create address $generated with address $address and setting $setting', async ({ setting, address, generated }): Promise => { - await createTenantSettings(deps, { - tenantId: tenantId, - setting: [ - { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: setting } - ] - }) + setting | address | generated + ${'https://alice.me/ilp'} | ${'https://alice.me/ilp/test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp'} | ${'test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp'} | ${'/test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp/'} | ${'test'} | ${'https://alice.me/ilp/test'} + ${'https://alice.me/ilp/'} | ${'/test'} | ${'https://alice.me/ilp/test'} + `( + 'should create address $generated with address $address and setting $setting', + async ({ setting, address, generated }): Promise => { + await createTenantSettings(deps, { + tenantId: tenantId, + setting: [ + { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: setting } + ] + }) - const walletAddress = await walletAddressService.create({ - ...options, - address - }) + const walletAddress = await walletAddressService.create({ + ...options, + address + }) - assert.ok(!isWalletAddressError(walletAddress)) - expect(walletAddress.address).toEqual(generated) - }) + assert.ok(!isWalletAddressError(walletAddress)) + expect(walletAddress.address).toEqual(generated) + } + ) test('Cannot create wallet address with unknown asset', async (): Promise => { await expect( diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 69f60d42de..77a3038d1d 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -170,37 +170,38 @@ async function createWalletAddress( deps: ServiceDependencies, options: CreateOptions ): Promise { - const found = await deps.tenantSettingService.get({ + const found = (await deps.tenantSettingService.get({ tenantId: options.tenantId, key: TenantSettingKeys.WALLET_ADDRESS_URL.name - }) as TenantSetting[]; + })) as TenantSetting[] if (!found || found.length === 0) { - return WalletAddressError.WalletAddressSettingNotFound; + return WalletAddressError.WalletAddressSettingNotFound } - const tenantWalletAddressUrl = new URL(found[0].value); - - let tenantBaseUrl = tenantWalletAddressUrl.toString(); + const tenantWalletAddressUrl = new URL(found[0].value) + + let tenantBaseUrl = tenantWalletAddressUrl.toString() if (!tenantWalletAddressUrl.pathname.endsWith('/')) { - tenantBaseUrl = tenantWalletAddressUrl.origin + tenantWalletAddressUrl.pathname + '/' + tenantBaseUrl = + tenantWalletAddressUrl.origin + tenantWalletAddressUrl.pathname + '/' } const isValidUrl = (str: string): boolean => { try { - new URL(str); - return true; + new URL(str) + return true } catch { - return false; + return false } - }; + } - let finalWalletAddressUrl: string; + let finalWalletAddressUrl: string if (isValidUrl(options.address)) { // in case that client provided full url, verify that it starts with the tenant's URL - const walletAddressUrl = new URL(options.address); + const walletAddressUrl = new URL(options.address) if (!walletAddressUrl.href.startsWith(tenantWalletAddressUrl.href)) { - return WalletAddressError.InvalidUrl; + return WalletAddressError.InvalidUrl } finalWalletAddressUrl = walletAddressUrl.toString() } else { @@ -212,10 +213,10 @@ async function createWalletAddress( } finalWalletAddressUrl = tenantBaseUrl + relativePath } catch (err) { - return WalletAddressError.InvalidUrl; + return WalletAddressError.InvalidUrl } } - + if (!isValidWalletAddressUrl(finalWalletAddressUrl)) { return WalletAddressError.InvalidUrl } diff --git a/packages/backend/src/tenants/service.test.ts b/packages/backend/src/tenants/service.test.ts index 904e543617..19fce7b38f 100644 --- a/packages/backend/src/tenants/service.test.ts +++ b/packages/backend/src/tenants/service.test.ts @@ -155,7 +155,10 @@ describe('Tenant Service', (): void => { idpConsentUrl: faker.internet.url(), idpSecret: 'test-idp-secret', settings: [ - { key: TenantSettingKeys.WALLET_ADDRESS_URL.name, value: walletAddressUrl } + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: walletAddressUrl + } ] } diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index fb19ddee29..2bc9af8461 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -89,7 +89,8 @@ async function createTenant( ): Promise { const trx = await deps.knex.transaction() try { - const { email, apiSecret, publicName, idpSecret, idpConsentUrl, settings } = options + const { email, apiSecret, publicName, idpSecret, idpConsentUrl, settings } = + options const tenant = await Tenant.query(trx).insertAndFetch({ email, publicName, @@ -104,13 +105,18 @@ async function createTenant( idpConsentUrl }) - const createInitialTenantSettingsOptions = { tenantId: tenant.id, setting: TenantSetting.default() } ; + const createInitialTenantSettingsOptions = { + tenantId: tenant.id, + setting: TenantSetting.default() + } if (settings) { - createInitialTenantSettingsOptions.setting = createInitialTenantSettingsOptions.setting.concat(settings) + createInitialTenantSettingsOptions.setting = + createInitialTenantSettingsOptions.setting.concat(settings) } - - await deps.tenantSettingService.create(createInitialTenantSettingsOptions, { trx } -) + + await deps.tenantSettingService.create(createInitialTenantSettingsOptions, { + trx + }) await trx.commit() diff --git a/packages/backend/src/tenants/settings/service.ts b/packages/backend/src/tenants/settings/service.ts index 684e807371..5623928bf1 100644 --- a/packages/backend/src/tenants/settings/service.ts +++ b/packages/backend/src/tenants/settings/service.ts @@ -129,12 +129,11 @@ async function createTenantSetting( return [] } - return TenantSetting - .query(extra?.trx ?? deps.knex) + return TenantSetting.query(extra?.trx ?? deps.knex) .insert(dataToUpsert) .onConflict(['tenantId', 'key']) .merge() - .returning('*'); + .returning('*') } async function getTenantSettingPageForTenant( diff --git a/packages/backend/src/tests/walletAddress.ts b/packages/backend/src/tests/walletAddress.ts index a2b1653efa..6b84abda64 100644 --- a/packages/backend/src/tests/walletAddress.ts +++ b/packages/backend/src/tests/walletAddress.ts @@ -36,7 +36,9 @@ export async function createWalletAddress( assetId: options.assetId || (await createAsset(deps, undefined, tenantIdToUse)).id, tenantId: tenantIdToUse, - address: options.address || `https://${faker.internet.domainName()}/.well-known/pay` + address: + options.address || + `https://${faker.internet.domainName()}/.well-known/pay` })) as MockWalletAddress if (isWalletAddressError(walletAddressOrError)) { throw new Error(walletAddressOrError) From 1a88f2c6372c2e65e7c9e93450a4a68625eeec19 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 2 Mar 2025 01:08:31 +0100 Subject: [PATCH 08/26] fix(tests): some of them --- .../generated/graphql.ts | 10 +-- .../src/graphql/generated/graphql.schema.json | 64 +++++++++---------- .../backend/src/graphql/generated/graphql.ts | 10 +-- .../graphql/resolvers/wallet_address.test.ts | 18 +++--- .../src/graphql/resolvers/wallet_address.ts | 4 +- packages/backend/src/graphql/schema.graphql | 8 +-- .../open_payments/payment/incoming/model.ts | 4 +- .../src/open_payments/quote/routes.test.ts | 2 +- packages/backend/src/tests/walletAddress.ts | 19 +++++- packages/frontend/app/generated/graphql.ts | 14 ++-- .../app/lib/api/wallet-address.server.ts | 4 +- .../src/generated/graphql.ts | 10 +-- test/integration/lib/generated/graphql.ts | 10 +-- 13 files changed, 95 insertions(+), 82 deletions(-) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 05a1c263eb..284dfd5f75 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -396,6 +396,8 @@ export type CreateTenantSettingsMutationResponse = { export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; + /** Wallet address. This cannot be changed. */ + address: Scalars['String']['input']; /** Unique identifier of the asset associated with the wallet address. This cannot be changed. */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency). */ @@ -404,8 +406,6 @@ export type CreateWalletAddressInput = { publicName?: InputMaybe; /** Unique identifier of the tenant associated with the wallet address. This cannot be changed. Optional, if not provided, the tenantId will be obtained from the signature. */ tenantId?: InputMaybe; - /** Wallet address URL. This cannot be changed. */ - url: Scalars['String']['input']; }; export type CreateWalletAddressKeyInput = { @@ -1671,6 +1671,8 @@ export type WalletAddress = Model & { __typename?: 'WalletAddress'; /** Additional properties associated with the wallet address. */ additionalProperties?: Maybe>>; + /** Wallet Address. */ + address: Scalars['String']['output']; /** Asset of the wallet address. */ asset: Asset; /** The date and time when the wallet address was created. */ @@ -1691,8 +1693,6 @@ export type WalletAddress = Model & { status: WalletAddressStatus; /** Tenant ID of the wallet address. */ tenantId?: Maybe; - /** Wallet Address URL. */ - url: Scalars['String']['output']; /** List of keys associated with this wallet address */ walletAddressKeys?: Maybe; }; @@ -2707,6 +2707,7 @@ export type UpdateWalletAddressMutationResponseResolvers = { additionalProperties?: Resolver>>, ParentType, ContextType>; + address?: Resolver; asset?: Resolver; createdAt?: Resolver; id?: Resolver; @@ -2717,7 +2718,6 @@ export type WalletAddressResolvers, ParentType, ContextType, Partial>; status?: Resolver; tenantId?: Resolver, ParentType, ContextType>; - url?: Resolver; walletAddressKeys?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index bc1fdc29c4..7225215114 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -2324,6 +2324,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "address", + "description": "Wallet address. This cannot be changed.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetId", "description": "Unique identifier of the asset associated with the wallet address. This cannot be changed.", @@ -2375,22 +2391,6 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "url", - "description": "Wallet address URL. This cannot be changed.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null } ], "interfaces": null, @@ -9343,6 +9343,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "address", + "description": "Wallet Address.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "asset", "description": "Asset of the wallet address.", @@ -9662,22 +9678,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "url", - "description": "Wallet Address URL.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "walletAddressKeys", "description": "List of keys associated with this wallet address", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 05a1c263eb..284dfd5f75 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -396,6 +396,8 @@ export type CreateTenantSettingsMutationResponse = { export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; + /** Wallet address. This cannot be changed. */ + address: Scalars['String']['input']; /** Unique identifier of the asset associated with the wallet address. This cannot be changed. */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency). */ @@ -404,8 +406,6 @@ export type CreateWalletAddressInput = { publicName?: InputMaybe; /** Unique identifier of the tenant associated with the wallet address. This cannot be changed. Optional, if not provided, the tenantId will be obtained from the signature. */ tenantId?: InputMaybe; - /** Wallet address URL. This cannot be changed. */ - url: Scalars['String']['input']; }; export type CreateWalletAddressKeyInput = { @@ -1671,6 +1671,8 @@ export type WalletAddress = Model & { __typename?: 'WalletAddress'; /** Additional properties associated with the wallet address. */ additionalProperties?: Maybe>>; + /** Wallet Address. */ + address: Scalars['String']['output']; /** Asset of the wallet address. */ asset: Asset; /** The date and time when the wallet address was created. */ @@ -1691,8 +1693,6 @@ export type WalletAddress = Model & { status: WalletAddressStatus; /** Tenant ID of the wallet address. */ tenantId?: Maybe; - /** Wallet Address URL. */ - url: Scalars['String']['output']; /** List of keys associated with this wallet address */ walletAddressKeys?: Maybe; }; @@ -2707,6 +2707,7 @@ export type UpdateWalletAddressMutationResponseResolvers = { additionalProperties?: Resolver>>, ParentType, ContextType>; + address?: Resolver; asset?: Resolver; createdAt?: Resolver; id?: Resolver; @@ -2717,7 +2718,6 @@ export type WalletAddressResolvers, ParentType, ContextType, Partial>; status?: Resolver; tenantId?: Resolver, ParentType, ContextType>; - url?: Resolver; walletAddressKeys?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index 045956da46..dd316caa29 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -789,7 +789,7 @@ describe('Wallet Address Resolvers', (): void => { const newWalletAddress = await walletAddressService.create({ assetId: (newAsset as Asset).id, tenantId: newTenant!.id, - url: 'https://alice.me/.well-known/pay-2' + address: 'https://alice.me/.well-known/pay-2' }) const id = (newWalletAddress as WalletAddressModel).id @@ -899,7 +899,7 @@ describe('Wallet Address Resolvers', (): void => { code: walletAddress.asset.code, scale: walletAddress.asset.scale }, - url: walletAddress.url, + address: walletAddress.address, publicName: publicName ?? null, additionalProperties: [ { @@ -931,7 +931,7 @@ describe('Wallet Address Resolvers', (): void => { publicName, createLiquidityAccount: true }) - const args = { url: walletAddress.url } + const args = { url: walletAddress.address } const query = await appContainer.apolloClient .query({ query: gql` @@ -943,7 +943,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName additionalProperties { key @@ -972,7 +972,7 @@ describe('Wallet Address Resolvers', (): void => { code: walletAddress.asset.code, scale: walletAddress.asset.scale }, - url: walletAddress.url, + address: walletAddress.address, publicName: publicName ?? null, additionalProperties: [] }) @@ -1042,7 +1042,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName } cursor @@ -1071,7 +1071,7 @@ describe('Wallet Address Resolvers', (): void => { code: walletAddress.asset.code, scale: walletAddress.asset.scale }, - url: walletAddress.url, + address: walletAddress.address, publicName: walletAddress.publicName }) }) @@ -1097,7 +1097,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName } cursor @@ -1129,7 +1129,7 @@ describe('Wallet Address Resolvers', (): void => { code: walletAddress.asset.code, scale: walletAddress.asset.scale }, - url: walletAddress.url, + address: walletAddress.address, publicName: walletAddress.publicName }) }) diff --git a/packages/backend/src/graphql/resolvers/wallet_address.ts b/packages/backend/src/graphql/resolvers/wallet_address.ts index d4766b521d..8e678acbaa 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.ts @@ -122,7 +122,7 @@ export const createWalletAddress: MutationResolvers['createW tenantId, additionalProperties: addProps, publicName: args.input.publicName, - url: args.input.url + address: args.input.address } const walletAddressOrError = await walletAddressService.create(options) @@ -206,7 +206,7 @@ export function walletAddressToGraphql( ): SchemaWalletAddress { return { id: walletAddress.id, - url: walletAddress.url, + address: walletAddress.address, asset: assetToGraphql(walletAddress.asset), publicName: walletAddress.publicName ?? undefined, createdAt: new Date(+walletAddress.createdAt).toISOString(), diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index cd588f656f..d25bb297a7 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -796,8 +796,8 @@ type WalletAddress implements Model { "Current amount of liquidity available for this wallet address." liquidity: UInt64 - "Wallet Address URL." - url: String! + "Wallet Address." + address: String! "Public name associated with the wallet address. This is visible to anyone with the wallet address URL." publicName: String @@ -1302,8 +1302,8 @@ input CreateWalletAddressInput { tenantId: ID "Unique identifier of the asset associated with the wallet address. This cannot be changed." assetId: String! - "Wallet address URL. This cannot be changed." - url: String! + "Wallet address. This cannot be changed." + address: String! "Public name associated with the wallet address. This is visible to anyone with the wallet address URL." publicName: String "Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency)." diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 4739efc9dc..3f833d97ac 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -144,7 +144,7 @@ export class IncomingPayment } public getUrl(walletAddress: WalletAddress): string { - const url = new URL(walletAddress.url) + const url = new URL(walletAddress.address) return `${url.origin}/${walletAddress.tenantId}${IncomingPayment.urlPath}/${this.id}` } @@ -225,7 +225,7 @@ export class IncomingPayment ): OpenPaymentsIncomingPayment { return { id: this.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, incomingAmount: this.incomingAmount ? serializeAmount(this.incomingAmount) : undefined, diff --git a/packages/backend/src/open_payments/quote/routes.test.ts b/packages/backend/src/open_payments/quote/routes.test.ts index 2c480bd45d..5de578b8b9 100644 --- a/packages/backend/src/open_payments/quote/routes.test.ts +++ b/packages/backend/src/open_payments/quote/routes.test.ts @@ -85,7 +85,7 @@ describe('Quote Routes', (): void => { tenantId, assetId }) - baseUrl = new URL(walletAddress.url).origin + baseUrl = new URL(walletAddress.address).origin }) afterEach(async (): Promise => { diff --git a/packages/backend/src/tests/walletAddress.ts b/packages/backend/src/tests/walletAddress.ts index 6b84abda64..9ca836514c 100644 --- a/packages/backend/src/tests/walletAddress.ts +++ b/packages/backend/src/tests/walletAddress.ts @@ -12,6 +12,8 @@ import { isWalletAddressError } from '../open_payments/wallet_address/errors' import { WalletAddress } from '../open_payments/wallet_address/model' import { CreateOptions as BaseCreateOptions } from '../open_payments/wallet_address/service' import { LiquidityAccountType } from '../accounting/service' +import { createTenantSettings } from './tenantSettings' +import { TenantSettingKeys } from '../tenants/settings/model' const nock = (global as unknown as { nock: typeof import('nock') }).nock @@ -31,14 +33,25 @@ export async function createWalletAddress( ): Promise { const walletAddressService = await deps.use('walletAddressService') const tenantIdToUse = options.tenantId || (await createTenant(deps)).id + + const baseWalletAddressUrl = new URL( + options.address || `https://${faker.internet.domainName()}` + ) + await createTenantSettings(deps, { + tenantId: tenantIdToUse, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: baseWalletAddressUrl.origin + } + ] + }) const walletAddressOrError = (await walletAddressService.create({ ...options, assetId: options.assetId || (await createAsset(deps, undefined, tenantIdToUse)).id, tenantId: tenantIdToUse, - address: - options.address || - `https://${faker.internet.domainName()}/.well-known/pay` + address: options.address || `${baseWalletAddressUrl.origin}/.well-known/pay` })) as MockWalletAddress if (isWalletAddressError(walletAddressOrError)) { throw new Error(walletAddressOrError) diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index ffdece0ff8..2a3a47560c 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -396,6 +396,8 @@ export type CreateTenantSettingsMutationResponse = { export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; + /** Wallet address. This cannot be changed. */ + address: Scalars['String']['input']; /** Unique identifier of the asset associated with the wallet address. This cannot be changed. */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency). */ @@ -404,8 +406,6 @@ export type CreateWalletAddressInput = { publicName?: InputMaybe; /** Unique identifier of the tenant associated with the wallet address. This cannot be changed. Optional, if not provided, the tenantId will be obtained from the signature. */ tenantId?: InputMaybe; - /** Wallet address URL. This cannot be changed. */ - url: Scalars['String']['input']; }; export type CreateWalletAddressKeyInput = { @@ -1671,6 +1671,8 @@ export type WalletAddress = Model & { __typename?: 'WalletAddress'; /** Additional properties associated with the wallet address. */ additionalProperties?: Maybe>>; + /** Wallet Address. */ + address: Scalars['String']['output']; /** Asset of the wallet address. */ asset: Asset; /** The date and time when the wallet address was created. */ @@ -1691,8 +1693,6 @@ export type WalletAddress = Model & { status: WalletAddressStatus; /** Tenant ID of the wallet address. */ tenantId?: Maybe; - /** Wallet Address URL. */ - url: Scalars['String']['output']; /** List of keys associated with this wallet address */ walletAddressKeys?: Maybe; }; @@ -2707,6 +2707,7 @@ export type UpdateWalletAddressMutationResponseResolvers = { additionalProperties?: Resolver>>, ParentType, ContextType>; + address?: Resolver; asset?: Resolver; createdAt?: Resolver; id?: Resolver; @@ -2717,7 +2718,6 @@ export type WalletAddressResolvers, ParentType, ContextType, Partial>; status?: Resolver; tenantId?: Resolver, ParentType, ContextType>; - url?: Resolver; walletAddressKeys?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -3090,7 +3090,7 @@ export type GetWalletAddressQueryVariables = Exact<{ }>; -export type GetWalletAddressQuery = { __typename?: 'Query', walletAddress?: { __typename?: 'WalletAddress', id: string, url: string, publicName?: string | null, status: WalletAddressStatus, createdAt: string, liquidity?: bigint | null, asset: { __typename?: 'Asset', id: string, code: string, scale: number, withdrawalThreshold?: bigint | null } } | null }; +export type GetWalletAddressQuery = { __typename?: 'Query', walletAddress?: { __typename?: 'WalletAddress', id: string, address: string, publicName?: string | null, status: WalletAddressStatus, createdAt: string, liquidity?: bigint | null, asset: { __typename?: 'Asset', id: string, code: string, scale: number, withdrawalThreshold?: bigint | null } } | null }; export type ListWalletAddresssQueryVariables = Exact<{ after?: InputMaybe; @@ -3100,7 +3100,7 @@ export type ListWalletAddresssQueryVariables = Exact<{ }>; -export type ListWalletAddresssQuery = { __typename?: 'Query', walletAddresses: { __typename?: 'WalletAddressesConnection', edges: Array<{ __typename?: 'WalletAddressEdge', cursor: string, node: { __typename?: 'WalletAddress', id: string, publicName?: string | null, status: WalletAddressStatus, url: string } }>, pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean } } }; +export type ListWalletAddresssQuery = { __typename?: 'Query', walletAddresses: { __typename?: 'WalletAddressesConnection', edges: Array<{ __typename?: 'WalletAddressEdge', cursor: string, node: { __typename?: 'WalletAddress', id: string, publicName?: string | null, status: WalletAddressStatus, address: string } }>, pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean } } }; export type UpdateWalletAddressMutationVariables = Exact<{ input: UpdateWalletAddressInput; diff --git a/packages/frontend/app/lib/api/wallet-address.server.ts b/packages/frontend/app/lib/api/wallet-address.server.ts index d87387a920..4ce2733911 100644 --- a/packages/frontend/app/lib/api/wallet-address.server.ts +++ b/packages/frontend/app/lib/api/wallet-address.server.ts @@ -29,7 +29,7 @@ export const getWalletAddress = async ( query GetWalletAddressQuery($id: String!) { walletAddress(id: $id) { id - url + address publicName status createdAt @@ -77,7 +77,7 @@ export const listWalletAddresses = async ( id publicName status - url + address } } pageInfo { diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 05a1c263eb..284dfd5f75 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -396,6 +396,8 @@ export type CreateTenantSettingsMutationResponse = { export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; + /** Wallet address. This cannot be changed. */ + address: Scalars['String']['input']; /** Unique identifier of the asset associated with the wallet address. This cannot be changed. */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency). */ @@ -404,8 +406,6 @@ export type CreateWalletAddressInput = { publicName?: InputMaybe; /** Unique identifier of the tenant associated with the wallet address. This cannot be changed. Optional, if not provided, the tenantId will be obtained from the signature. */ tenantId?: InputMaybe; - /** Wallet address URL. This cannot be changed. */ - url: Scalars['String']['input']; }; export type CreateWalletAddressKeyInput = { @@ -1671,6 +1671,8 @@ export type WalletAddress = Model & { __typename?: 'WalletAddress'; /** Additional properties associated with the wallet address. */ additionalProperties?: Maybe>>; + /** Wallet Address. */ + address: Scalars['String']['output']; /** Asset of the wallet address. */ asset: Asset; /** The date and time when the wallet address was created. */ @@ -1691,8 +1693,6 @@ export type WalletAddress = Model & { status: WalletAddressStatus; /** Tenant ID of the wallet address. */ tenantId?: Maybe; - /** Wallet Address URL. */ - url: Scalars['String']['output']; /** List of keys associated with this wallet address */ walletAddressKeys?: Maybe; }; @@ -2707,6 +2707,7 @@ export type UpdateWalletAddressMutationResponseResolvers = { additionalProperties?: Resolver>>, ParentType, ContextType>; + address?: Resolver; asset?: Resolver; createdAt?: Resolver; id?: Resolver; @@ -2717,7 +2718,6 @@ export type WalletAddressResolvers, ParentType, ContextType, Partial>; status?: Resolver; tenantId?: Resolver, ParentType, ContextType>; - url?: Resolver; walletAddressKeys?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 05a1c263eb..284dfd5f75 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -396,6 +396,8 @@ export type CreateTenantSettingsMutationResponse = { export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; + /** Wallet address. This cannot be changed. */ + address: Scalars['String']['input']; /** Unique identifier of the asset associated with the wallet address. This cannot be changed. */ assetId: Scalars['String']['input']; /** Unique key to ensure duplicate or retried requests are processed only once. For more information, refer to [idempotency](https://rafiki.dev/apis/graphql/admin-api-overview/#idempotency). */ @@ -404,8 +406,6 @@ export type CreateWalletAddressInput = { publicName?: InputMaybe; /** Unique identifier of the tenant associated with the wallet address. This cannot be changed. Optional, if not provided, the tenantId will be obtained from the signature. */ tenantId?: InputMaybe; - /** Wallet address URL. This cannot be changed. */ - url: Scalars['String']['input']; }; export type CreateWalletAddressKeyInput = { @@ -1671,6 +1671,8 @@ export type WalletAddress = Model & { __typename?: 'WalletAddress'; /** Additional properties associated with the wallet address. */ additionalProperties?: Maybe>>; + /** Wallet Address. */ + address: Scalars['String']['output']; /** Asset of the wallet address. */ asset: Asset; /** The date and time when the wallet address was created. */ @@ -1691,8 +1693,6 @@ export type WalletAddress = Model & { status: WalletAddressStatus; /** Tenant ID of the wallet address. */ tenantId?: Maybe; - /** Wallet Address URL. */ - url: Scalars['String']['output']; /** List of keys associated with this wallet address */ walletAddressKeys?: Maybe; }; @@ -2707,6 +2707,7 @@ export type UpdateWalletAddressMutationResponseResolvers = { additionalProperties?: Resolver>>, ParentType, ContextType>; + address?: Resolver; asset?: Resolver; createdAt?: Resolver; id?: Resolver; @@ -2717,7 +2718,6 @@ export type WalletAddressResolvers, ParentType, ContextType, Partial>; status?: Resolver; tenantId?: Resolver, ParentType, ContextType>; - url?: Resolver; walletAddressKeys?: Resolver, ParentType, ContextType, Partial>; __isTypeOf?: IsTypeOfResolverFn; }; From 1f6c45097e411f58f9aaea385a2ac2bcf493e140 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 2 Mar 2025 01:24:54 +0100 Subject: [PATCH 09/26] fix(frontend): due to wallet address url change --- .../frontend/app/routes/wallet-addresses.$walletAddressId.tsx | 2 +- packages/frontend/app/routes/wallet-addresses._index.tsx | 2 +- packages/frontend/app/routes/wallet-addresses.create.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/app/routes/wallet-addresses.$walletAddressId.tsx b/packages/frontend/app/routes/wallet-addresses.$walletAddressId.tsx index 77339ff04f..88b4950ee4 100644 --- a/packages/frontend/app/routes/wallet-addresses.$walletAddressId.tsx +++ b/packages/frontend/app/routes/wallet-addresses.$walletAddressId.tsx @@ -86,7 +86,7 @@ export default function ViewWalletAddressPage() { /> diff --git a/packages/frontend/app/routes/wallet-addresses._index.tsx b/packages/frontend/app/routes/wallet-addresses._index.tsx index 0771f895bb..e5946d0ca4 100644 --- a/packages/frontend/app/routes/wallet-addresses._index.tsx +++ b/packages/frontend/app/routes/wallet-addresses._index.tsx @@ -69,7 +69,7 @@ export default function WalletAddressesPage() { className='cursor-pointer' onClick={() => navigate(`/wallet-addresses/${pp.node.id}`)} > - {pp.node.url} + {pp.node.address}
{pp.node.publicName ? ( diff --git a/packages/frontend/app/routes/wallet-addresses.create.tsx b/packages/frontend/app/routes/wallet-addresses.create.tsx index 174d5ae8ca..b199ef44da 100644 --- a/packages/frontend/app/routes/wallet-addresses.create.tsx +++ b/packages/frontend/app/routes/wallet-addresses.create.tsx @@ -162,7 +162,7 @@ export async function action({ request }: ActionFunctionArgs) { const path = removeTrailingAndLeadingSlash(result.data.name) const response = await createWalletAddress(request, { - url: `${baseUrl}/${path}`, + address: `${baseUrl}/${path}`, publicName: result.data.publicName, assetId: result.data.asset, tenantId: result.data.tenantId, From f86d832f0cb7bf2b320aa918d01d4d6f01aae433 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 2 Mar 2025 01:28:57 +0100 Subject: [PATCH 10/26] fix(mase): due to wallet address url change --- packages/mock-account-service-lib/src/requesters.ts | 2 +- packages/mock-account-service-lib/src/seed.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mock-account-service-lib/src/requesters.ts b/packages/mock-account-service-lib/src/requesters.ts index 2f79324190..1c2d900a16 100644 --- a/packages/mock-account-service-lib/src/requesters.ts +++ b/packages/mock-account-service-lib/src/requesters.ts @@ -341,7 +341,7 @@ export async function createWalletAddress( ` const createWalletAddressInput: CreateWalletAddressInput = { assetId, - url: accountUrl, + address: accountUrl, publicName: accountName, additionalProperties: [] } diff --git a/packages/mock-account-service-lib/src/seed.ts b/packages/mock-account-service-lib/src/seed.ts index 6ccc50681a..db21d7ea74 100644 --- a/packages/mock-account-service-lib/src/seed.ts +++ b/packages/mock-account-service-lib/src/seed.ts @@ -152,7 +152,7 @@ export async function setupFromSeed( await mockAccounts.setWalletAddress( account.id, walletAddress.id, - walletAddress.url + walletAddress.address ) await createWalletAddressKey({ From 052af1c6bab3573fc7ff1ade449f4b5fe5b81abf Mon Sep 17 00:00:00 2001 From: golobitch Date: Thu, 13 Mar 2025 23:57:25 +0100 Subject: [PATCH 11/26] fix(backend): tests due to wallet address url change --- .../app/lib/requesters.ts | 2 +- .../app/lib/wallet.server.ts | 2 +- .../app/lib/webhooks.server.ts | 2 +- packages/backend/src/asset/service.test.ts | 18 ++++++- .../src/graphql/resolvers/tenant.test.ts | 50 +++---------------- .../graphql/resolvers/wallet_address.test.ts | 43 ++++++++++++---- .../payment/incoming/model.test.ts | 10 ++-- .../payment/incoming/routes.test.ts | 8 +-- .../payment/incoming/service.test.ts | 2 +- .../open_payments/payment/outgoing/model.ts | 4 +- .../payment/outgoing/routes.test.ts | 6 +-- .../payment/outgoing/service.test.ts | 2 +- .../backend/src/open_payments/quote/model.ts | 4 +- .../src/open_payments/quote/routes.test.ts | 12 ++--- .../src/open_payments/quote/service.test.ts | 10 ++-- .../src/open_payments/receiver/model.test.ts | 2 +- .../open_payments/receiver/service.test.ts | 8 +-- .../wallet_address/key/routes.test.ts | 6 +-- .../wallet_address/middleware.test.ts | 10 ++-- .../wallet_address/middleware.ts | 6 +-- .../wallet_address/model.test.ts | 2 +- .../wallet_address/routes.test.ts | 10 ++-- .../wallet_address/service.test.ts | 18 ------- .../backend/src/shared/pagination.test.ts | 14 +++--- packages/backend/src/tests/tenant.ts | 45 +++++++++++++++++ test/integration/lib/admin-client.ts | 2 +- test/integration/lib/integration-server.ts | 2 +- 27 files changed, 164 insertions(+), 136 deletions(-) diff --git a/localenv/mock-account-servicing-entity/app/lib/requesters.ts b/localenv/mock-account-servicing-entity/app/lib/requesters.ts index 14654de0a3..bda40fff4d 100644 --- a/localenv/mock-account-servicing-entity/app/lib/requesters.ts +++ b/localenv/mock-account-servicing-entity/app/lib/requesters.ts @@ -96,7 +96,7 @@ export async function createWalletAddress( ` const createWalletAddressInput: CreateWalletAddressInput = { assetId, - url: accountUrl, + address: accountUrl, publicName: accountName, additionalProperties: [] } diff --git a/localenv/mock-account-servicing-entity/app/lib/wallet.server.ts b/localenv/mock-account-servicing-entity/app/lib/wallet.server.ts index af3b1d7327..7da1c714cc 100644 --- a/localenv/mock-account-servicing-entity/app/lib/wallet.server.ts +++ b/localenv/mock-account-servicing-entity/app/lib/wallet.server.ts @@ -25,7 +25,7 @@ export async function createWallet({ await mockAccounts.setWalletAddress( accountId, walletAddress.id, - walletAddress.url + walletAddress.address ) await createWalletAddressKey({ diff --git a/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts b/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts index 72c44fd7fc..955bfb60ae 100644 --- a/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts +++ b/localenv/mock-account-servicing-entity/app/lib/webhooks.server.ts @@ -277,7 +277,7 @@ export async function handleWalletAddressNotFound(wh: Webhook) { await mockAccounts.setWalletAddress( account.id, walletAddress.id, - walletAddress.url + walletAddress.address ) } diff --git a/packages/backend/src/asset/service.test.ts b/packages/backend/src/asset/service.test.ts index df93c6bd24..c7b6008c03 100644 --- a/packages/backend/src/asset/service.test.ts +++ b/packages/backend/src/asset/service.test.ts @@ -26,6 +26,8 @@ import { TenantSettingService } from '../tenants/settings/service' import { exchangeRatesSetting } from '../tests/tenantSettings' +import { createTenantSettings } from '../tests/tenantSettings' +import { TenantSettingKeys } from '../tenants/settings/model' describe('Asset Service', (): void => { let deps: IocContract @@ -71,6 +73,18 @@ describe('Asset Service', (): void => { await appContainer.shutdown() }) + beforeEach(async () => { + await createTenantSettings(deps, { + tenantId: Config.operatorTenantId, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: 'https://alice.me' + } + ] + }) + }) + describe('create', (): void => { test.each` withdrawalThreshold | liquidityThreshold @@ -370,8 +384,8 @@ describe('Asset Service', (): void => { const newAssetId = newAsset.id // make sure there is at least 1 wallet address using asset - const walletAddress = walletAddressService.create({ - url: 'https://alice.me/.well-known/pay', + const walletAddress = await walletAddressService.create({ + address: 'https://alice.me/.well-known/pay', tenantId: Config.operatorTenantId, assetId: newAssetId }) diff --git a/packages/backend/src/graphql/resolvers/tenant.test.ts b/packages/backend/src/graphql/resolvers/tenant.test.ts index 99cda31197..2c76994ab9 100644 --- a/packages/backend/src/graphql/resolvers/tenant.test.ts +++ b/packages/backend/src/graphql/resolvers/tenant.test.ts @@ -10,56 +10,18 @@ import { } from '../generated/graphql' import { initIocContainer } from '../..' import { Config, IAppConfig } from '../../config/app' -import { createTenant, generateTenantInput } from '../../tests/tenant' +import { + createTenant, + createTenantedApolloClient, + generateTenantInput +} from '../../tests/tenant' import { ApolloError, gql, NormalizedCacheObject } from '@apollo/client' import { getPageTests } from './page.test' import { truncateTables } from '../../tests/tableManager' -import { - createHttpLink, - ApolloLink, - ApolloClient, - InMemoryCache -} from '@apollo/client' -import { setContext } from '@apollo/client/link/context' +import { ApolloClient } from '@apollo/client' import { GraphQLErrorCode } from '../errors' import { Tenant as TenantModel } from '../../tenants/model' -function createTenantedApolloClient( - appContainer: TestContainer, - tenantId: string -): ApolloClient { - const httpLink = createHttpLink({ - uri: `http://localhost:${appContainer.app.getAdminPort()}/graphql`, - fetch - }) - const authLink = setContext((_, { headers }) => { - return { - headers: { - ...headers, - 'tenant-id': tenantId - } - } - }) - - const link = ApolloLink.from([authLink, httpLink]) - - return new ApolloClient({ - cache: new InMemoryCache({}), - link: link, - defaultOptions: { - query: { - fetchPolicy: 'no-cache' - }, - mutate: { - fetchPolicy: 'no-cache' - }, - watchQuery: { - fetchPolicy: 'no-cache' - } - } - }) -} - describe('Tenant Resolvers', (): void => { let deps: IocContract let appContainer: TestContainer diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index dd316caa29..417a91b2f4 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -42,6 +42,8 @@ import { GraphQLErrorCode } from '../errors' import { AssetService } from '../../asset/service' import { faker } from '@faker-js/faker' import { Tenant } from '../../tenants/model' +import { createTenantSettings } from '../../tests/tenantSettings' +import { TenantSettingKeys } from '../../tenants/settings/model' import { createTenant } from '../../tests/tenant' describe('Wallet Address Resolvers', (): void => { @@ -71,6 +73,18 @@ describe('Wallet Address Resolvers', (): void => { await appContainer.shutdown() }) + beforeEach(async () => { + await createTenantSettings(deps, { + tenantId: Config.operatorTenantId, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: 'https://alice.me' + } + ] + }) + }) + describe('Create Wallet Address', (): void => { let asset: Asset let input: CreateWalletAddressInput @@ -80,7 +94,7 @@ describe('Wallet Address Resolvers', (): void => { input = { assetId: asset.id, tenantId: Config.operatorTenantId, - url: 'https://alice.me/.well-known/pay' + address: 'https://alice.me/.well-known/pay' } }) @@ -103,7 +117,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName } } @@ -125,7 +139,7 @@ describe('Wallet Address Resolvers', (): void => { expect(response.walletAddress).toEqual({ __typename: 'WalletAddress', id: response.walletAddress.id, - url: input.url, + address: input.address, asset: { __typename: 'Asset', code: asset.code, @@ -169,7 +183,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName additionalProperties { key @@ -196,7 +210,7 @@ describe('Wallet Address Resolvers', (): void => { expect(response.walletAddress).toEqual({ __typename: 'WalletAddress', id: response.walletAddress.id, - url: input.url, + address: input.address, asset: { __typename: 'Asset', code: asset.code, @@ -330,7 +344,7 @@ describe('Wallet Address Resolvers', (): void => { const badInputData = { tenantId: uuid(), // some tenant other than requestor assetId: input.assetId, - url: input.url + address: input.address } try { expect.assertions(2) @@ -786,6 +800,17 @@ describe('Wallet Address Resolvers', (): void => { scale: 2, tenantId: newTenant!.id }) + + await createTenantSettings(deps, { + tenantId: newTenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: 'https://alice.me' + } + ] + }) + const newWalletAddress = await walletAddressService.create({ assetId: (newAsset as Asset).id, tenantId: newTenant!.id, @@ -852,10 +877,10 @@ describe('Wallet Address Resolvers', (): void => { const additionalProperties = [walletProp01, walletProp02] const walletAddress = await createWalletAddress(deps, { - tenantId: Config.operatorTenantId, publicName, createLiquidityAccount: true, - additionalProperties + additionalProperties, + tenantId: Config.operatorTenantId }) const query = await appContainer.apolloClient .query({ @@ -868,7 +893,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address publicName additionalProperties { key diff --git a/packages/backend/src/open_payments/payment/incoming/model.test.ts b/packages/backend/src/open_payments/payment/incoming/model.test.ts index 3f1f91c76e..351d7150e4 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.test.ts @@ -45,7 +45,7 @@ describe('Models', (): void => { walletAddress = await createWalletAddress(deps, { tenantId: Config.operatorTenantId }) - baseUrl = new URL(walletAddress.url).origin + baseUrl = new URL(walletAddress.address).origin incomingPayment = await createIncomingPayment(deps, { walletAddressId: walletAddress.id, metadata: { description: 'my payment' }, @@ -57,7 +57,7 @@ describe('Models', (): void => { test('returns incoming payment', async () => { expect(incomingPayment.toOpenPaymentsType(walletAddress)).toEqual({ id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, completed: incomingPayment.completed, receivedAmount: serializeAmount(incomingPayment.receivedAmount), incomingAmount: incomingPayment.incomingAmount @@ -85,7 +85,7 @@ describe('Models', (): void => { ) ).toEqual({ id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, completed: incomingPayment.completed, receivedAmount: serializeAmount(incomingPayment.receivedAmount), incomingAmount: incomingPayment.incomingAmount @@ -110,7 +110,7 @@ describe('Models', (): void => { incomingPayment.toOpenPaymentsTypeWithMethods(walletAddress) ).toEqual({ id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, completed: incomingPayment.completed, receivedAmount: serializeAmount(incomingPayment.receivedAmount), incomingAmount: incomingPayment.incomingAmount @@ -141,7 +141,7 @@ describe('Models', (): void => { ) ).toEqual({ id: `${baseUrl}/${Config.operatorTenantId}${IncomingPayment.urlPath}/${incomingPayment.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, completed: incomingPayment.completed, receivedAmount: serializeAmount(incomingPayment.receivedAmount), incomingAmount: incomingPayment.incomingAmount diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 506aadf3ea..0b02c103f7 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -59,7 +59,7 @@ describe('Incoming Payment Routes', (): void => { tenantId, assetId: asset.id }) - baseUrl = new URL(walletAddress.url).origin + baseUrl = new URL(walletAddress.address).origin incomingAmount = { value: BigInt('123'), assetScale: asset.scale, @@ -97,7 +97,7 @@ describe('Incoming Payment Routes', (): void => { const response: Partial = { id: incomingPayment.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, completed: false, incomingAmount: incomingPayment.incomingAmount && @@ -280,7 +280,7 @@ describe('Incoming Payment Routes', (): void => { expect(ctx.response.body).toEqual({ id: `${baseUrl}/${tenantId}/incoming-payments/${incomingPaymentId}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, incomingAmount: incomingAmount ? amount : undefined, expiresAt: expiresAt || expect.any(String), createdAt: expect.any(String), @@ -335,7 +335,7 @@ describe('Incoming Payment Routes', (): void => { expect(ctx.response).toSatisfyApiSpec() expect(ctx.body).toEqual({ id: incomingPayment.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, incomingAmount: { value: '123', assetCode: asset.code, diff --git a/packages/backend/src/open_payments/payment/incoming/service.test.ts b/packages/backend/src/open_payments/payment/incoming/service.test.ts index 39b436341b..59754ed899 100644 --- a/packages/backend/src/open_payments/payment/incoming/service.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/service.test.ts @@ -60,7 +60,7 @@ describe('Incoming Payment Service', (): void => { assetId: asset.id }) walletAddressId = address.id - client = address.url + client = address.address }) afterEach(async (): Promise => { diff --git a/packages/backend/src/open_payments/payment/outgoing/model.ts b/packages/backend/src/open_payments/payment/outgoing/model.ts index 302d8ba72e..3a9b8201d4 100644 --- a/packages/backend/src/open_payments/payment/outgoing/model.ts +++ b/packages/backend/src/open_payments/payment/outgoing/model.ts @@ -108,7 +108,7 @@ export class OutgoingPayment } public getUrl(walletAddress: WalletAddress): string { - const url = new URL(walletAddress.url) + const url = new URL(walletAddress.address) return `${url.origin}/${this.tenantId}${OutgoingPayment.urlPath}/${this.id}` } @@ -206,7 +206,7 @@ export class OutgoingPayment ): OpenPaymentsOutgoingPayment { return { id: this.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, quoteId: this.quote?.getUrl(walletAddress) ?? undefined, receiveAmount: serializeAmount(this.receiveAmount), debitAmount: serializeAmount(this.debitAmount), diff --git a/packages/backend/src/open_payments/payment/outgoing/routes.test.ts b/packages/backend/src/open_payments/payment/outgoing/routes.test.ts index 4f04a3642c..87015c93b9 100644 --- a/packages/backend/src/open_payments/payment/outgoing/routes.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/routes.test.ts @@ -84,7 +84,7 @@ describe('Outgoing Payment Routes', (): void => { tenantId, assetId: asset.id }) - baseUrl = new URL(walletAddress.url).origin + baseUrl = new URL(walletAddress.address).origin }) afterEach(async (): Promise => { @@ -121,7 +121,7 @@ describe('Outgoing Payment Routes', (): void => { getBody: (outgoingPayment) => { return { id: `${baseUrl}/${tenantId}/outgoing-payments/${outgoingPayment.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver: outgoingPayment.receiver, quoteId: outgoingPayment.quote.getUrl(walletAddress), debitAmount: serializeAmount(outgoingPayment.debitAmount), @@ -252,7 +252,7 @@ describe('Outgoing Payment Routes', (): void => { .pop() expect(ctx.response.body).toEqual({ id: `${baseUrl}/${tenantId}/outgoing-payments/${outgoingPaymentId}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver: payment.receiver, quoteId: 'quoteId' in options ? options.quoteId : expect.any(String), diff --git a/packages/backend/src/open_payments/payment/outgoing/service.test.ts b/packages/backend/src/open_payments/payment/outgoing/service.test.ts index ae6e86fd54..a2fa3150ce 100644 --- a/packages/backend/src/open_payments/payment/outgoing/service.test.ts +++ b/packages/backend/src/open_payments/payment/outgoing/service.test.ts @@ -289,7 +289,7 @@ describe('OutgoingPaymentService', (): void => { assetId: sendAssetId }) walletAddressId = walletAddress.id - client = walletAddress.url + client = walletAddress.address const { id: destinationAssetId } = await createAsset(deps, destinationAsset) receiverWalletAddress = await createWalletAddress(deps, { tenantId, diff --git a/packages/backend/src/open_payments/quote/model.ts b/packages/backend/src/open_payments/quote/model.ts index a05352e228..104430496b 100644 --- a/packages/backend/src/open_payments/quote/model.ts +++ b/packages/backend/src/open_payments/quote/model.ts @@ -66,7 +66,7 @@ export class Quote extends WalletAddressSubresource { private debitAmountValue!: bigint public getUrl(walletAddress: WalletAddress): string { - const url = new URL(walletAddress.url) + const url = new URL(walletAddress.address) return `${url.origin}/${this.tenantId}${Quote.urlPath}/${this.id}` } @@ -134,7 +134,7 @@ export class Quote extends WalletAddressSubresource { public toOpenPaymentsType(walletAddress: WalletAddress): OpenPaymentsQuote { return { id: this.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiveAmount: serializeAmount(this.receiveAmount), debitAmount: serializeAmount(this.debitAmount), receiver: this.receiver, diff --git a/packages/backend/src/open_payments/quote/routes.test.ts b/packages/backend/src/open_payments/quote/routes.test.ts index 5de578b8b9..9e71da8419 100644 --- a/packages/backend/src/open_payments/quote/routes.test.ts +++ b/packages/backend/src/open_payments/quote/routes.test.ts @@ -109,7 +109,7 @@ describe('Quote Routes', (): void => { getBody: (quote) => { return { id: `${baseUrl}/${quote.tenantId}/quotes/${quote.id}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver: quote.receiver, debitAmount: serializeAmount(quote.debitAmount), receiveAmount: serializeAmount(quote.receiveAmount), @@ -145,7 +145,7 @@ describe('Quote Routes', (): void => { test('returns error on invalid debitAmount asset', async (): Promise => { options = { - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver, debitAmount: { ...debitAmount, @@ -174,7 +174,7 @@ describe('Quote Routes', (): void => { '$description', async ({ debitAmount, receiveAmount }): Promise => { options = { - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver, method: 'ilp' } @@ -227,7 +227,7 @@ describe('Quote Routes', (): void => { assert.ok(quote) expect(ctx.response.body).toEqual({ id: `${baseUrl}/${tenantId}/quotes/${quoteId}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver: quote.receiver, debitAmount: { ...quote.debitAmount, @@ -246,7 +246,7 @@ describe('Quote Routes', (): void => { test('receiver.incomingAmount', async (): Promise => { options = { - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver, method: 'ilp' } @@ -279,7 +279,7 @@ describe('Quote Routes', (): void => { assert.ok(quote) expect(ctx.response.body).toEqual({ id: `${baseUrl}/${tenantId}/quotes/${quoteId}`, - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, receiver: options.receiver, debitAmount: { ...quote.debitAmount, diff --git a/packages/backend/src/open_payments/quote/service.test.ts b/packages/backend/src/open_payments/quote/service.test.ts index 9ecc0601ba..f2f11b21c0 100644 --- a/packages/backend/src/open_payments/quote/service.test.ts +++ b/packages/backend/src/open_payments/quote/service.test.ts @@ -144,7 +144,7 @@ describe('QuoteService', (): void => { createQuote(deps, { tenantId, walletAddressId: sendingWalletAddress.id, - receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, + receiver: `${receivingWalletAddress.address}/incoming-payments/${uuid()}`, debitAmount: { value: BigInt(56), assetCode: asset.code, @@ -446,7 +446,7 @@ describe('QuoteService', (): void => { quoteService.create({ tenantId: unknownTenantId, walletAddressId: walletAddress.id, - receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, + receiver: `${receivingWalletAddress.address}/incoming-payments/${uuid()}`, debitAmount, method: 'ilp' }) @@ -466,7 +466,7 @@ describe('QuoteService', (): void => { quoteService.create({ tenantId, walletAddressId: unknownWalletAddressId, - receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, + receiver: `${receivingWalletAddress.address}/incoming-payments/${uuid()}`, debitAmount, method: 'ilp' }) @@ -490,7 +490,7 @@ describe('QuoteService', (): void => { quoteService.create({ tenantId, walletAddressId: walletAddress.id, - receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, + receiver: `${receivingWalletAddress.address}/incoming-payments/${uuid()}`, debitAmount, method: 'ilp' }) @@ -502,7 +502,7 @@ describe('QuoteService', (): void => { quoteService.create({ tenantId, walletAddressId: sendingWalletAddress.id, - receiver: `${receivingWalletAddress.url}/incoming-payments/${uuid()}`, + receiver: `${receivingWalletAddress.address}/incoming-payments/${uuid()}`, debitAmount, method: 'ilp' }) diff --git a/packages/backend/src/open_payments/receiver/model.test.ts b/packages/backend/src/open_payments/receiver/model.test.ts index 295de1dec9..a3dfbf5d8e 100644 --- a/packages/backend/src/open_payments/receiver/model.test.ts +++ b/packages/backend/src/open_payments/receiver/model.test.ts @@ -65,7 +65,7 @@ describe('Receiver Model', (): void => { sharedSecret: expect.any(Buffer), incomingPayment: { id: incomingPayment.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, updatedAt: incomingPayment.updatedAt, createdAt: incomingPayment.createdAt, completed: incomingPayment.completed, diff --git a/packages/backend/src/open_payments/receiver/service.test.ts b/packages/backend/src/open_payments/receiver/service.test.ts index 4881e0617f..3bde3215a5 100644 --- a/packages/backend/src/open_payments/receiver/service.test.ts +++ b/packages/backend/src/open_payments/receiver/service.test.ts @@ -105,7 +105,7 @@ describe('Receiver Service', (): void => { sharedSecret: expect.any(Buffer), incomingPayment: { id: incomingPayment.getUrl(walletAddress), - walletAddress: walletAddress.url, + walletAddress: walletAddress.address, incomingAmount: incomingPayment.incomingAmount, receivedAmount: incomingPayment.receivedAmount, completed: false, @@ -315,7 +315,7 @@ describe('Receiver Service', (): void => { 'create' ) const receiver = await receiverService.create({ - walletAddressUrl: walletAddress.url, + walletAddressUrl: walletAddress.address, incomingAmount, expiresAt, metadata, @@ -367,7 +367,7 @@ describe('Receiver Service', (): void => { await expect( receiverService.create({ - walletAddressUrl: walletAddress.url, + walletAddressUrl: walletAddress.address, tenantId }) ).resolves.toEqual(ReceiverError.InvalidAmount) @@ -380,7 +380,7 @@ describe('Receiver Service', (): void => { await expect( receiverService.create({ - walletAddressUrl: walletAddress.url, + walletAddressUrl: walletAddress.address, tenantId }) ).rejects.toThrow( diff --git a/packages/backend/src/open_payments/wallet_address/key/routes.test.ts b/packages/backend/src/open_payments/wallet_address/key/routes.test.ts index 1608b3580f..73222c87cb 100644 --- a/packages/backend/src/open_payments/wallet_address/key/routes.test.ts +++ b/packages/backend/src/open_payments/wallet_address/key/routes.test.ts @@ -61,7 +61,7 @@ describe('Wallet Address Keys Routes', (): void => { headers: { Accept: 'application/json' }, url: `/jwks.json` }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await expect(walletAddressKeyRoutes.get(ctx)).resolves.toBeUndefined() expect(ctx.response).toSatisfyApiSpec() @@ -79,7 +79,7 @@ describe('Wallet Address Keys Routes', (): void => { headers: { Accept: 'application/json' }, url: `/jwks.json` }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await expect(walletAddressKeyRoutes.get(ctx)).resolves.toBeUndefined() expect(ctx.body).toEqual({ @@ -134,7 +134,7 @@ describe('Wallet Address Keys Routes', (): void => { const ctx = createContext({ headers: { Accept: 'application/json' } }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address const getOrPollByUrlSpy = jest.spyOn( walletAddressService, diff --git a/packages/backend/src/open_payments/wallet_address/middleware.test.ts b/packages/backend/src/open_payments/wallet_address/middleware.test.ts index af62847107..97c6b0f869 100644 --- a/packages/backend/src/open_payments/wallet_address/middleware.test.ts +++ b/packages/backend/src/open_payments/wallet_address/middleware.test.ts @@ -142,7 +142,7 @@ describe('Wallet Address Middleware', (): void => { jest.spyOn(incomingPaymentService, 'get').mockResolvedValueOnce({ id: incomingPaymentId, walletAddress: { - url: walletAddressUrl + address: walletAddressUrl } } as IncomingPayment) @@ -203,7 +203,7 @@ describe('Wallet Address Middleware', (): void => { jest.spyOn(quoteService, 'get').mockResolvedValueOnce({ id: quoteId, walletAddress: { - url: walletAddressUrl + address: walletAddressUrl } } as Quote) @@ -319,7 +319,7 @@ describe('Wallet Address Middleware', (): void => { jest.spyOn(outgoingPaymentService, 'get').mockResolvedValueOnce({ id: outgoingPaymentId, walletAddress: { - url: walletAddressUrl + address: walletAddressUrl } } as OutgoingPayment) @@ -465,7 +465,7 @@ describe('Wallet Address Middleware', (): void => { const walletAddress = await createWalletAddress(deps, { tenantId: Config.operatorTenantId }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await walletAddress.$query().patch({ deactivatedAt: new Date() }) @@ -484,7 +484,7 @@ describe('Wallet Address Middleware', (): void => { const walletAddress = await createWalletAddress(deps, { tenantId: Config.operatorTenantId }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await expect( getWalletAddressForSubresource(ctx, next) diff --git a/packages/backend/src/open_payments/wallet_address/middleware.ts b/packages/backend/src/open_payments/wallet_address/middleware.ts index 49fa7c3f21..fefadbe10c 100644 --- a/packages/backend/src/open_payments/wallet_address/middleware.ts +++ b/packages/backend/src/open_payments/wallet_address/middleware.ts @@ -54,7 +54,7 @@ export async function getWalletAddressUrlFromIncomingPayment( }) } - ctx.walletAddressUrl = incomingPayment.walletAddress.url + ctx.walletAddressUrl = incomingPayment.walletAddress.address await next() } @@ -77,7 +77,7 @@ export async function getWalletAddressUrlFromOutgoingPayment( }) } - ctx.walletAddressUrl = outgoingPayment.walletAddress.url + ctx.walletAddressUrl = outgoingPayment.walletAddress.address await next() } @@ -98,7 +98,7 @@ export async function getWalletAddressUrlFromQuote( }) } - ctx.walletAddressUrl = quote.walletAddress.url + ctx.walletAddressUrl = quote.walletAddress.address await next() } diff --git a/packages/backend/src/open_payments/wallet_address/model.test.ts b/packages/backend/src/open_payments/wallet_address/model.test.ts index c46499557b..cc7bd279b4 100644 --- a/packages/backend/src/open_payments/wallet_address/model.test.ts +++ b/packages/backend/src/open_payments/wallet_address/model.test.ts @@ -58,7 +58,7 @@ export const setup = < options.params ) ctx.walletAddress = options.walletAddress - ctx.walletAddressUrl = options.walletAddress.url + ctx.walletAddressUrl = options.walletAddress.address ctx.grant = options.grant ctx.client = options.client ctx.accessAction = options.accessAction diff --git a/packages/backend/src/open_payments/wallet_address/routes.test.ts b/packages/backend/src/open_payments/wallet_address/routes.test.ts index 87da55b2ec..227fc40430 100644 --- a/packages/backend/src/open_payments/wallet_address/routes.test.ts +++ b/packages/backend/src/open_payments/wallet_address/routes.test.ts @@ -71,7 +71,7 @@ describe('Wallet Address Routes', (): void => { const ctx = createContext({ headers: { Accept: 'application/json' } }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address const getOrPollByUrlSpy = jest.spyOn( walletAddressService, @@ -112,11 +112,11 @@ describe('Wallet Address Routes', (): void => { headers: { Accept: 'application/json' }, url: '/' }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await expect(walletAddressRoutes.get(ctx)).resolves.toBeUndefined() expect(ctx.response).toSatisfyApiSpec() expect(ctx.body).toEqual({ - id: walletAddress.url, + id: walletAddress.address, publicName: walletAddress.publicName, assetCode: walletAddress.asset.code, assetScale: walletAddress.asset.scale, @@ -156,11 +156,11 @@ describe('Wallet Address Routes', (): void => { headers: { Accept: 'application/json' }, url: '/' }) - ctx.walletAddressUrl = walletAddress.url + ctx.walletAddressUrl = walletAddress.address await expect(walletAddressRoutes.get(ctx)).resolves.toBeUndefined() expect(ctx.response).toSatisfyApiSpec() expect(ctx.body).toEqual({ - id: walletAddress.url, + id: walletAddress.address, publicName: walletAddress.publicName, assetCode: walletAddress.asset.code, assetScale: walletAddress.asset.scale, diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index b0e528d77f..fe885b7faf 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -139,24 +139,6 @@ describe('Open Payments Wallet Address Service', (): void => { ).resolves.toEqual(WalletAddressError.UnknownAsset) }) - test.each` - url | description - ${'not a url'} | ${'without a valid url'} - ${'http://alice.me/pay'} | ${'with a non-https url'} - ${'https://alice.me'} | ${'with a url without a path'} - ${'https://alice.me/'} | ${'with a url without a path'} - `( - 'Wallet address cannot be created $description ($url)', - async ({ url }): Promise => { - await expect( - walletAddressService.create({ - ...options, - address: url - }) - ).resolves.toEqual(WalletAddressError.InvalidUrl) - } - ) - test.each(FORBIDDEN_PATHS.map((path) => [path]))( 'Wallet address cannot be created with forbidden url path (%s)', async (path): Promise => { diff --git a/packages/backend/src/shared/pagination.test.ts b/packages/backend/src/shared/pagination.test.ts index 1136e50a7a..ae645ecbff 100644 --- a/packages/backend/src/shared/pagination.test.ts +++ b/packages/backend/src/shared/pagination.test.ts @@ -66,9 +66,9 @@ describe('Pagination', (): void => { first, last, cursor, - 'wallet-address': walletAddress.url + 'wallet-address': walletAddress.address }) - ).toEqual({ ...result, walletAddress: walletAddress.url }) + ).toEqual({ ...result, walletAddress: walletAddress.address }) } ) }) @@ -144,7 +144,7 @@ describe('Pagination', (): void => { pagination }), page, - walletAddress: defaultWalletAddress.url + walletAddress: defaultWalletAddress.address }) expect(pageInfo).toEqual({ startCursor: paymentIds[start], @@ -179,7 +179,7 @@ describe('Pagination', (): void => { const payment = await createOutgoingPayment(deps, { tenantId, walletAddressId: defaultWalletAddress.id, - receiver: secondaryWalletAddress.url, + receiver: secondaryWalletAddress.address, method: 'ilp', debitAmount, validDestination: false @@ -202,7 +202,7 @@ describe('Pagination', (): void => { pagination }), page, - walletAddress: defaultWalletAddress.url + walletAddress: defaultWalletAddress.address }) expect(pageInfo).toEqual({ startCursor: paymentIds[start], @@ -237,7 +237,7 @@ describe('Pagination', (): void => { const quote = await createQuote(deps, { tenantId, walletAddressId: defaultWalletAddress.id, - receiver: secondaryWalletAddress.url, + receiver: secondaryWalletAddress.address, debitAmount, validDestination: false, method: 'ilp' @@ -260,7 +260,7 @@ describe('Pagination', (): void => { pagination }), page, - walletAddress: defaultWalletAddress.url + walletAddress: defaultWalletAddress.address }) expect(pageInfo).toEqual({ startCursor: quoteIds[start], diff --git a/packages/backend/src/tests/tenant.ts b/packages/backend/src/tests/tenant.ts index d8874b4573..a5a3394f26 100644 --- a/packages/backend/src/tests/tenant.ts +++ b/packages/backend/src/tests/tenant.ts @@ -2,6 +2,15 @@ import { IocContract } from '@adonisjs/fold' import { faker } from '@faker-js/faker' import { AppServices } from '../app' import { Tenant } from '../tenants/model' +import { + ApolloClient, + ApolloLink, + createHttpLink, + InMemoryCache, + NormalizedCacheObject +} from '@apollo/client' +import { setContext } from '@apollo/client/link/context' +import { TestContainer } from './app' interface CreateOptions { email: string @@ -11,6 +20,42 @@ interface CreateOptions { idpSecret: string } +export function createTenantedApolloClient( + appContainer: TestContainer, + tenantId: string +): ApolloClient { + const httpLink = createHttpLink({ + uri: `http://localhost:${appContainer.app.getAdminPort()}/graphql`, + fetch + }) + const authLink = setContext((_, { headers }) => { + return { + headers: { + ...headers, + 'tenant-id': tenantId + } + } + }) + + const link = ApolloLink.from([authLink, httpLink]) + + return new ApolloClient({ + cache: new InMemoryCache({}), + link: link, + defaultOptions: { + query: { + fetchPolicy: 'no-cache' + }, + mutate: { + fetchPolicy: 'no-cache' + }, + watchQuery: { + fetchPolicy: 'no-cache' + } + } + }) +} + export function generateTenantInput() { return { email: faker.internet.email(), diff --git a/test/integration/lib/admin-client.ts b/test/integration/lib/admin-client.ts index 589e6b5ae6..d7bb9faf4b 100644 --- a/test/integration/lib/admin-client.ts +++ b/test/integration/lib/admin-client.ts @@ -240,7 +240,7 @@ export class AdminClient { createWalletAddress(input: $input) { walletAddress { id - url + address publicName } } diff --git a/test/integration/lib/integration-server.ts b/test/integration/lib/integration-server.ts index 54ba96559c..722b17d479 100644 --- a/test/integration/lib/integration-server.ts +++ b/test/integration/lib/integration-server.ts @@ -129,7 +129,7 @@ export class WebhookEventHandler { const response = await this.adminClient.createWalletAddress({ assetId, publicName, - url, + address: url, additionalProperties: [] }) const { walletAddress } = response From fef7f9261d61bf776c763bbba7c8bc1d0e1f17d8 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 22 Mar 2025 00:37:38 +0100 Subject: [PATCH 12/26] test(integration): fix tests and have default address for operator --- .../backend/src/graphql/resolvers/wallet_address.ts | 4 +++- .../src/open_payments/wallet_address/service.ts | 11 ++++++++--- packages/mock-account-service-lib/src/requesters.ts | 5 +++-- test/integration/lib/integration-server.ts | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/wallet_address.ts b/packages/backend/src/graphql/resolvers/wallet_address.ts index 8e678acbaa..1fe36e61b7 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.ts @@ -107,6 +107,7 @@ export const createWalletAddress: MutationResolvers['createW }) const tenantId = ctx.forTenantId + if (!tenantId) throw new GraphQLError( `Assignment to the specified tenant is not permitted`, @@ -122,7 +123,8 @@ export const createWalletAddress: MutationResolvers['createW tenantId, additionalProperties: addProps, publicName: args.input.publicName, - address: args.input.address + address: args.input.address, + isOperator: ctx.isOperator } const walletAddressOrError = await walletAddressService.create(options) diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 77a3038d1d..0e301f0fb2 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -45,6 +45,7 @@ export interface CreateOptions extends Options { address: string assetId: string additionalProperties?: WalletAddressAdditionalPropertyInput[] + isOperator?: boolean } type Status = 'ACTIVE' | 'INACTIVE' @@ -170,17 +171,21 @@ async function createWalletAddress( deps: ServiceDependencies, options: CreateOptions ): Promise { + let tenantWalletAddressUrl = new URL(deps.config.openPaymentsUrl) + const found = (await deps.tenantSettingService.get({ tenantId: options.tenantId, key: TenantSettingKeys.WALLET_ADDRESS_URL.name })) as TenantSetting[] if (!found || found.length === 0) { - return WalletAddressError.WalletAddressSettingNotFound + if (!options.isOperator) { + return WalletAddressError.WalletAddressSettingNotFound + } + } else { + tenantWalletAddressUrl = new URL(found[0].value) } - const tenantWalletAddressUrl = new URL(found[0].value) - let tenantBaseUrl = tenantWalletAddressUrl.toString() if (!tenantWalletAddressUrl.pathname.endsWith('/')) { tenantBaseUrl = diff --git a/packages/mock-account-service-lib/src/requesters.ts b/packages/mock-account-service-lib/src/requesters.ts index 1c2d900a16..eba8f77fc7 100644 --- a/packages/mock-account-service-lib/src/requesters.ts +++ b/packages/mock-account-service-lib/src/requesters.ts @@ -333,12 +333,13 @@ export async function createWalletAddress( createWalletAddress(input: $input) { walletAddress { id - url + address publicName } } } ` + const createWalletAddressInput: CreateWalletAddressInput = { assetId, address: accountUrl, @@ -485,7 +486,7 @@ async function getWalletAddressByURL( walletAddressByUrl(url: $url) { id liquidity - url + address publicName asset { id diff --git a/test/integration/lib/integration-server.ts b/test/integration/lib/integration-server.ts index 722b17d479..b835c25fe3 100644 --- a/test/integration/lib/integration-server.ts +++ b/test/integration/lib/integration-server.ts @@ -141,7 +141,7 @@ export class WebhookEventHandler { await this.accounts.setWalletAddress( account.id, walletAddress.id, - walletAddress.url + walletAddress.address ) } From 455a9c37fe2123095d082fc114f465d46216a1e4 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 22 Mar 2025 10:21:22 +0100 Subject: [PATCH 13/26] fix(wallet-address): test for operator can perform cross tenant create@ --- .../src/graphql/resolvers/wallet_address.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/wallet_address.test.ts b/packages/backend/src/graphql/resolvers/wallet_address.test.ts index 417a91b2f4..d5b4212cf6 100644 --- a/packages/backend/src/graphql/resolvers/wallet_address.test.ts +++ b/packages/backend/src/graphql/resolvers/wallet_address.test.ts @@ -400,10 +400,20 @@ describe('Wallet Address Resolvers', (): void => { }, nonOperatorTenant.id ) + await createTenantSettings(deps, { + tenantId: nonOperatorTenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: 'https://bob.me' + } + ] + }) + const input = { tenantId: nonOperatorTenant.id, assetId: asset.id, - url: 'https://bob.me/.well-known/pay' + address: 'https://bob.me/.well-known/pay' } const response = await appContainer.apolloClient // operator client .mutate({ @@ -416,7 +426,7 @@ describe('Wallet Address Resolvers', (): void => { code scale } - url + address } } } @@ -437,7 +447,7 @@ describe('Wallet Address Resolvers', (): void => { expect(response.walletAddress).toEqual({ __typename: 'WalletAddress', id: response.walletAddress.id, - url: input.url, + address: input.address, asset: { __typename: 'Asset', code: asset.code, From 09c0b1f062b88b3021ec4156065800cd2459bfcd Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 22 Mar 2025 11:14:50 +0100 Subject: [PATCH 14/26] test(tenant-settings): remove pagination tests for tenant settings --- .../src/tenants/settings/service.test.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/backend/src/tenants/settings/service.test.ts b/packages/backend/src/tenants/settings/service.test.ts index 92592e3f54..ebb354ff5b 100644 --- a/packages/backend/src/tenants/settings/service.test.ts +++ b/packages/backend/src/tenants/settings/service.test.ts @@ -8,13 +8,7 @@ import { truncateTables } from '../../tests/tableManager' import { Tenant } from '../model' import { TenantService } from '../service' import { faker } from '@faker-js/faker' -import { getPageTests } from '../../shared/baseModel.test' -import { Pagination, SortOrder } from '../../shared/baseModel' -import { - createTenantSettings, - exchangeRatesSetting, - randomSetting -} from '../../tests/tenantSettings' +import { exchangeRatesSetting, randomSetting } from '../../tests/tenantSettings' import { TenantSetting } from './model' import { CreateOptions, @@ -276,21 +270,4 @@ describe('TenantSetting Service', (): void => { expect(dbTenantData.filter((x) => !x.deletedAt)).toHaveLength(0) }) }) - - describe('pagination', (): void => { - beforeEach(async () => { - await tenantSettingService.delete({ tenantId: tenant.id }) - }) - describe('getPage', (): void => { - getPageTests({ - createModel: () => - createTenantSettings(deps, { - tenantId: tenant.id, - setting: [exchangeRatesSetting()] - }) as Promise, - getPage: (pagination?: Pagination, sortOrder?: SortOrder) => - tenantSettingService.getPage(tenant.id, pagination, sortOrder) - }) - }) - }) }) From 194dbec32c2ccf8310b30d4a92f45fe0577ccd7d Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 22 Mar 2025 11:34:24 +0100 Subject: [PATCH 15/26] docs(bruno): rename of the walletaddress url variable --- .../Create Wallet Address.bru | 4 +-- .../Get Wallet Addresses Keys.bru | 2 +- .../Get Wallet Addresses.bru | 2 +- .../app/lib/requesters.ts | 2 +- .../backend/src/tenants/settings/model.ts | 29 ++++++++++++------- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru index 3918a11c8b..5ea2508c7b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Wallet Address.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{RafikiGraphqlHost}}/graphql + address: {{RafikiGraphqlHost}}/graphql body: graphql auth: none } @@ -17,7 +17,7 @@ body:graphql { id createdAt publicName - url + address status asset { code diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru index e8ed641928..57d229102b 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses Keys.bru @@ -18,7 +18,7 @@ body:graphql { node { id publicName - url + address walletAddressKeys { edges { cursor diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru index 462d4e81d5..318f38eaae 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Wallet Addresses.bru @@ -18,7 +18,7 @@ body:graphql { node { id publicName - url + address } } } diff --git a/localenv/mock-account-servicing-entity/app/lib/requesters.ts b/localenv/mock-account-servicing-entity/app/lib/requesters.ts index bda40fff4d..8118e2b4ce 100644 --- a/localenv/mock-account-servicing-entity/app/lib/requesters.ts +++ b/localenv/mock-account-servicing-entity/app/lib/requesters.ts @@ -88,7 +88,7 @@ export async function createWalletAddress( createWalletAddress(input: $input) { walletAddress { id - url + address publicName } } diff --git a/packages/backend/src/tenants/settings/model.ts b/packages/backend/src/tenants/settings/model.ts index cbba5ac831..3dba4062a4 100644 --- a/packages/backend/src/tenants/settings/model.ts +++ b/packages/backend/src/tenants/settings/model.ts @@ -2,7 +2,12 @@ import { Pojo } from 'objection' import { BaseModel } from '../../shared/baseModel' import { KeyValuePair } from './service' -export const TenantSettingKeys = { +interface TenantSettingKeyType { + name: string + default?: unknown +} + +export const TenantSettingKeys: { [key: string]: TenantSettingKeyType } = { EXCHANGE_RATES_URL: { name: 'EXCHANGE_RATES_URL' }, WEBHOOK_URL: { name: 'WEBHOOK_URL' }, WEBHOOK_TIMEOUT: { name: 'WEBHOOK_TIMEOUT', default: 2000 }, @@ -30,15 +35,19 @@ export class TenantSetting extends BaseModel { } static default(): KeyValuePair[] { - return [ - { - key: TenantSettingKeys.WEBHOOK_TIMEOUT.name, - value: TenantSettingKeys.WEBHOOK_TIMEOUT.default.toString() - }, - { - key: TenantSettingKeys.WEBHOOK_MAX_RETRY.name, - value: TenantSettingKeys.WEBHOOK_MAX_RETRY.default.toString() + const settings = [] + for (const key of Object.keys(TenantSettingKeys)) { + const data = TenantSettingKeys[key] + if (!data.default) { + continue } - ] + + settings.push({ + key: data.name, + value: String(data.default) + }) + } + + return settings } } From 0d592333155b7d1378a73d8edb9c70b3b550ad1d Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 30 Mar 2025 00:51:13 +0100 Subject: [PATCH 16/26] test(wallet-address): create with tenant settings or not as an operator or not --- .../wallet_address/service.test.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index fe885b7faf..44e2061974 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -103,6 +103,66 @@ describe('Open Payments Wallet Address Service', (): void => { } ) + test.each` + isOperator | tenantSettingUrl + ${false} | ${undefined} + ${true} | ${undefined} + ${true} | ${'https://alice.me'} + `( + 'operator - $isOperator with tenantSettingUrl - $tenantSettingUrl', + async({ isOperator, tenantSettingUrl }): Promise => { + const config = await deps.use('config') + const address = "test" + const tempTenant = await createTenant(deps) + const { id: tempAssetId } = await createAsset(deps, undefined, tempTenant.id) + + let expected: string = WalletAddressError.WalletAddressSettingNotFound; + if (tenantSettingUrl) { + await createTenantSettings(deps, { + tenantId: tempTenant.id, + setting: [ + { + key: TenantSettingKeys.WALLET_ADDRESS_URL.name, + value: tenantSettingUrl + } + ] + }) + expected = `${tenantSettingUrl}/${address}` + } else { + if (isOperator) { + expected = `https://op.example/${address}` + } + } + + const created = await walletAddressService.create({ + ...options, + address, + isOperator, + assetId: tempAssetId, + tenantId: tempTenant.id + }) + + if (isWalletAddressError(expected)) { + expect(created).toEqual(expected) + } else { + assert.ok(!isWalletAddressError(created)) + expect(created.address).toEqual(expected) + } + } + ) + + test('should return error without tenant settings if caller is not an operator', async () => { + const tempTenant = await createTenant(deps) + + expect( + await walletAddressService.create({ + ...options, + tenantId: tempTenant.id + }) + ).toEqual(WalletAddressError.WalletAddressSettingNotFound) + + }) + test.each` setting | address | generated ${'https://alice.me/ilp'} | ${'https://alice.me/ilp/test'} | ${'https://alice.me/ilp/test'} From 63453be8c92744eab66ca92ae67f625e0dbb9783 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 30 Mar 2025 01:04:43 +0100 Subject: [PATCH 17/26] feat(wallet-address): put creation of it into new function --- .../open_payments/wallet_address/service.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 0e301f0fb2..3ae0d4120a 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -6,7 +6,7 @@ import { } from 'objection' import { URL } from 'url' -import { WalletAddressError } from './errors' +import { isWalletAddressError, WalletAddressError } from './errors' import { WalletAddress, WalletAddressEvent, @@ -167,10 +167,10 @@ function cleanAdditionalProperties( .filter((prop) => prop.fieldKey.length > 0 && prop.fieldValue.length > 0) } -async function createWalletAddress( +async function createWalletAddressUrl( deps: ServiceDependencies, options: CreateOptions -): Promise { +): Promise { let tenantWalletAddressUrl = new URL(deps.config.openPaymentsUrl) const found = (await deps.tenantSettingService.get({ @@ -226,6 +226,19 @@ async function createWalletAddress( return WalletAddressError.InvalidUrl } + return finalWalletAddressUrl +} + +async function createWalletAddress( + deps: ServiceDependencies, + options: CreateOptions +): Promise { + const finalWalletAddressUrl = await createWalletAddressUrl(deps, options) + + if (isWalletAddressError(finalWalletAddressUrl)) { + return finalWalletAddressUrl; + } + try { const asset = await deps.assetService.get(options.assetId, options.tenantId) if (!asset) return WalletAddressError.UnknownAsset From 98c1ac5617001748a335053dff2e6c4858e5b01c Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 30 Mar 2025 01:05:40 +0100 Subject: [PATCH 18/26] chore(format): everything --- .../wallet_address/service.test.ts | 26 ++++++++++--------- .../open_payments/wallet_address/service.ts | 2 +- packages/backend/src/tenants/service.ts | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index 44e2061974..ee68f4db61 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -104,19 +104,22 @@ describe('Open Payments Wallet Address Service', (): void => { ) test.each` - isOperator | tenantSettingUrl - ${false} | ${undefined} - ${true} | ${undefined} - ${true} | ${'https://alice.me'} + isOperator | tenantSettingUrl + ${false} | ${undefined} + ${true} | ${undefined} + ${true} | ${'https://alice.me'} `( - 'operator - $isOperator with tenantSettingUrl - $tenantSettingUrl', - async({ isOperator, tenantSettingUrl }): Promise => { - const config = await deps.use('config') - const address = "test" + 'operator - $isOperator with tenantSettingUrl - $tenantSettingUrl', + async ({ isOperator, tenantSettingUrl }): Promise => { + const address = 'test' const tempTenant = await createTenant(deps) - const { id: tempAssetId } = await createAsset(deps, undefined, tempTenant.id) + const { id: tempAssetId } = await createAsset( + deps, + undefined, + tempTenant.id + ) - let expected: string = WalletAddressError.WalletAddressSettingNotFound; + let expected: string = WalletAddressError.WalletAddressSettingNotFound if (tenantSettingUrl) { await createTenantSettings(deps, { tenantId: tempTenant.id, @@ -153,14 +156,13 @@ describe('Open Payments Wallet Address Service', (): void => { test('should return error without tenant settings if caller is not an operator', async () => { const tempTenant = await createTenant(deps) - + expect( await walletAddressService.create({ ...options, tenantId: tempTenant.id }) ).toEqual(WalletAddressError.WalletAddressSettingNotFound) - }) test.each` diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 3ae0d4120a..f50aca0764 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -236,7 +236,7 @@ async function createWalletAddress( const finalWalletAddressUrl = await createWalletAddressUrl(deps, options) if (isWalletAddressError(finalWalletAddressUrl)) { - return finalWalletAddressUrl; + return finalWalletAddressUrl } try { diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 2bc9af8461..f0b4c647ed 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -109,6 +109,7 @@ async function createTenant( tenantId: tenant.id, setting: TenantSetting.default() } + if (settings) { createInitialTenantSettingsOptions.setting = createInitialTenantSettingsOptions.setting.concat(settings) From 493c5a29e42fa3950a98883776dbee7ed23223aa Mon Sep 17 00:00:00 2001 From: golobitch Date: Wed, 2 Apr 2025 21:43:11 +0200 Subject: [PATCH 19/26] feat(tenant-settings): add tests for upsert --- .../src/tenants/settings/service.test.ts | 30 +++++++++++++++++++ packages/backend/src/tests/tenantSettings.ts | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/tenants/settings/service.test.ts b/packages/backend/src/tenants/settings/service.test.ts index ebb354ff5b..da2a511ea1 100644 --- a/packages/backend/src/tenants/settings/service.test.ts +++ b/packages/backend/src/tenants/settings/service.test.ts @@ -91,6 +91,36 @@ describe('TenantSetting Service', (): void => { expect(tenantSetting).toEqual([]) }) + + test('should update existing tenant settings on conflict - upsert', async (): Promise => { + const initialOptions: CreateOptions = { + tenantId: tenant.id, + setting: [exchangeRatesSetting()] + } + + await tenantSettingService.create(initialOptions) + + const newValue = faker.internet.url() + const updatedOptions: CreateOptions = { + tenantId: tenant.id, + setting: [ + { + key: initialOptions.setting[0].key, + value: newValue + } + ] + } + + await tenantSettingService.create(updatedOptions) + const result = (await tenantSettingService.get({ + tenantId: tenant.id, + key: initialOptions.setting[0].key + })) as TenantSetting[] + + expect(result).toHaveLength(1) + expect(result[0].key).toEqual(initialOptions.setting[0].key) + expect(result[0].value).toEqual(newValue) + }) }) describe('get', () => { diff --git a/packages/backend/src/tests/tenantSettings.ts b/packages/backend/src/tests/tenantSettings.ts index b678cc4ec1..aa685b14b3 100644 --- a/packages/backend/src/tests/tenantSettings.ts +++ b/packages/backend/src/tests/tenantSettings.ts @@ -1,6 +1,6 @@ import { IocContract } from '@adonisjs/fold' import { AppServices } from '../app' -import { TenantSetting } from '../tenants/settings/model' +import { TenantSetting, TenantSettingKeys } from '../tenants/settings/model' import { CreateOptions, KeyValuePair } from '../tenants/settings/service' import { faker } from '@faker-js/faker' import { isTenantSettingError } from '../tenants/settings/errors' @@ -16,7 +16,7 @@ export function randomSetting(): KeyValuePair { export function exchangeRatesSetting(): KeyValuePair { return { - key: 'EXCHANGE_RATES_URL', + key: TenantSettingKeys.EXCHANGE_RATES_URL.name, value: faker.internet.url() } } From 67538ce1921f401a992694a27acd9d906bcbe4c3 Mon Sep 17 00:00:00 2001 From: golobitch Date: Fri, 4 Apr 2025 00:25:58 +0200 Subject: [PATCH 20/26] feat(tenant-settings): add more tests and remove pagination --- .../Rafiki Admin APIs/Get Tenant Settings.bru | 43 +++++ .../generated/graphql.ts | 49 +---- .../src/graphql/generated/graphql.schema.json | 169 ++---------------- .../backend/src/graphql/generated/graphql.ts | 49 +---- .../graphql/resolvers/tenant_settings.test.ts | 45 ++++- .../src/graphql/resolvers/tenant_settings.ts | 33 +--- packages/backend/src/graphql/schema.graphql | 27 +-- .../src/tenants/settings/service.test.ts | 71 ++++++++ packages/frontend/app/generated/graphql.ts | 49 +---- .../src/generated/graphql.ts | 49 +---- test/integration/lib/generated/graphql.ts | 49 +---- 11 files changed, 190 insertions(+), 443 deletions(-) create mode 100644 bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant Settings.bru diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant Settings.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant Settings.bru new file mode 100644 index 0000000000..91a475a3d1 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant Settings.bru @@ -0,0 +1,43 @@ +meta { + name: Get Tenant Settings + type: graphql + seq: 60 +} + +post { + url: {{RafikiGraphqlHost}}/graphql + body: graphql + auth: none +} + +headers { + tenant-id: 438fa74a-fa7d-4317-9ced-dde32ece1787 +} + +body:graphql { + mutation CreateTenantSettings($input: CreateTenantSettingsInput!) { + createTenantSettings(input:$input) { + settings { + key + value + } + } + } +} + +body:graphql:vars { + { + "input": { + "settings": [ + { "key": "EXCHANGE_RATES_URL", "value": "https://example.com" } + ] + } + } + +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 284dfd5f75..3f6a1c5b3d 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1490,16 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe; -}; - - -export type TenantSettingsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - sortOrder?: InputMaybe; + settings?: Maybe>; }; export type TenantEdge = { @@ -1523,14 +1514,6 @@ export type TenantSetting = { value: Scalars['String']['output']; }; -export type TenantSettingEdge = { - __typename?: 'TenantSettingEdge'; - /** A cursor for paginating through the tenants. */ - cursor: Scalars['String']['output']; - /** A tenant setting node in the list. */ - node: TenantSetting; -}; - export type TenantSettingInput = { /** Key for this setting. */ key: Scalars['String']['input']; @@ -1538,14 +1521,6 @@ export type TenantSettingInput = { value: Scalars['String']['input']; }; -export type TenantSettingsConnection = { - __typename?: 'TenantSettingsConnection'; - /** A list of edges representing tenant settings and cursors for pagination. */ - edges: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - export type TenantsConnection = { __typename?: 'TenantsConnection'; /** A list of edges representing tenants and cursors for pagination. */ @@ -1917,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; @@ -2034,9 +2009,7 @@ export type ResolversTypes = { TenantEdge: ResolverTypeWrapper>; TenantMutationResponse: ResolverTypeWrapper>; TenantSetting: ResolverTypeWrapper>; - TenantSettingEdge: ResolverTypeWrapper>; TenantSettingInput: ResolverTypeWrapper>; - TenantSettingsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; @@ -2172,9 +2145,7 @@ export type ResolversParentTypes = { TenantEdge: Partial; TenantMutationResponse: Partial; TenantSetting: Partial; - TenantSettingEdge: Partial; TenantSettingInput: Partial; - TenantSettingsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2643,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver, ParentType, ContextType, Partial>; + settings?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2664,18 +2635,6 @@ export type TenantSettingResolvers; }; -export type TenantSettingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type TenantSettingsConnectionResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2851,8 +2810,6 @@ export type Resolvers = { TenantEdge?: TenantEdgeResolvers; TenantMutationResponse?: TenantMutationResponseResolvers; TenantSetting?: TenantSettingResolvers; - TenantSettingEdge?: TenantSettingEdgeResolvers; - TenantSettingsConnection?: TenantSettingsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 7225215114..357cc55b23 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -8335,72 +8335,19 @@ { "name": "settings", "description": "List of settings for the tenant.", - "args": [ - { - "name": "after", - "description": "Forward pagination: Cursor (wallet address key ID) to start retrieving settings after this point.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "before", - "description": "Backward pagination: Cursor (wallet address key ID) to start retrieving keys before this point.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "first", - "description": "Forward pagination: Limit the result to the first **n** keys after the `after` cursor.", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "last", - "description": "Backward pagination: Limit the result to the last **n** keys before the `before` cursor.", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "sortOrder", - "description": "Specify the sort order of keys based on their creation data, either ascending or descending.", - "type": { - "kind": "ENUM", - "name": "SortOrder", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantSetting", "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null + } } - ], - "type": { - "kind": "OBJECT", - "name": "TenantSettingsConnection", - "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -8530,49 +8477,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "TenantSettingEdge", - "description": null, - "fields": [ - { - "name": "cursor", - "description": "A cursor for paginating through the tenants.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "node", - "description": "A tenant setting node in the list.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "TenantSetting", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "TenantSettingInput", @@ -8616,57 +8520,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "TenantSettingsConnection", - "description": null, - "fields": [ - { - "name": "edges", - "description": "A list of edges representing tenant settings and cursors for pagination.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "TenantSettingEdge", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "Information to aid in pagination.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "TenantsConnection", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 284dfd5f75..3f6a1c5b3d 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1490,16 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe; -}; - - -export type TenantSettingsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - sortOrder?: InputMaybe; + settings?: Maybe>; }; export type TenantEdge = { @@ -1523,14 +1514,6 @@ export type TenantSetting = { value: Scalars['String']['output']; }; -export type TenantSettingEdge = { - __typename?: 'TenantSettingEdge'; - /** A cursor for paginating through the tenants. */ - cursor: Scalars['String']['output']; - /** A tenant setting node in the list. */ - node: TenantSetting; -}; - export type TenantSettingInput = { /** Key for this setting. */ key: Scalars['String']['input']; @@ -1538,14 +1521,6 @@ export type TenantSettingInput = { value: Scalars['String']['input']; }; -export type TenantSettingsConnection = { - __typename?: 'TenantSettingsConnection'; - /** A list of edges representing tenant settings and cursors for pagination. */ - edges: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - export type TenantsConnection = { __typename?: 'TenantsConnection'; /** A list of edges representing tenants and cursors for pagination. */ @@ -1917,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; @@ -2034,9 +2009,7 @@ export type ResolversTypes = { TenantEdge: ResolverTypeWrapper>; TenantMutationResponse: ResolverTypeWrapper>; TenantSetting: ResolverTypeWrapper>; - TenantSettingEdge: ResolverTypeWrapper>; TenantSettingInput: ResolverTypeWrapper>; - TenantSettingsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; @@ -2172,9 +2145,7 @@ export type ResolversParentTypes = { TenantEdge: Partial; TenantMutationResponse: Partial; TenantSetting: Partial; - TenantSettingEdge: Partial; TenantSettingInput: Partial; - TenantSettingsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2643,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver, ParentType, ContextType, Partial>; + settings?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2664,18 +2635,6 @@ export type TenantSettingResolvers; }; -export type TenantSettingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type TenantSettingsConnectionResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2851,8 +2810,6 @@ export type Resolvers = { TenantEdge?: TenantEdgeResolvers; TenantMutationResponse?: TenantMutationResponseResolvers; TenantSetting?: TenantSettingResolvers; - TenantSettingEdge?: TenantSettingEdgeResolvers; - TenantSettingsConnection?: TenantSettingsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/packages/backend/src/graphql/resolvers/tenant_settings.test.ts b/packages/backend/src/graphql/resolvers/tenant_settings.test.ts index 473f1e0795..4fc96c90c8 100644 --- a/packages/backend/src/graphql/resolvers/tenant_settings.test.ts +++ b/packages/backend/src/graphql/resolvers/tenant_settings.test.ts @@ -85,7 +85,7 @@ describe('Tenant Settings Resolvers', (): void => { }) afterAll(async (): Promise => { - await appContainer.apolloClient.stop() + appContainer.apolloClient.stop() await appContainer.shutdown() }) @@ -123,4 +123,47 @@ describe('Tenant Settings Resolvers', (): void => { expect(response.settings.length).toBeGreaterThan(0) }) }) + + describe('Get Tenant Settings', (): void => { + test('can get tenant settings', async (): Promise => { + const tenant = await createTenant(deps) + const client = createTenantedApolloClient(appContainer, tenant.id) + + // Query the settings + const response = await client + .query({ + query: gql` + query GetTenantSettings($id: String!) { + tenant(id: $id) { + settings { + key + value + } + } + } + `, + variables: { id: tenant.id } + }) + .then((query): { key: string; value: string }[] => { + if (query.data && query.data.tenant) { + return query.data.tenant.settings + } + throw new Error('Data was empty') + }) + + expect(response.length).toBeGreaterThan(0) + expect(response).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + key: TenantSettingKeys.WEBHOOK_MAX_RETRY.name, + value: String(TenantSettingKeys.WEBHOOK_MAX_RETRY.default) + }), + expect.objectContaining({ + key: TenantSettingKeys.WEBHOOK_TIMEOUT.name, + value: String(TenantSettingKeys.WEBHOOK_TIMEOUT.default) + }) + ]) + ) + }) + }) }) diff --git a/packages/backend/src/graphql/resolvers/tenant_settings.ts b/packages/backend/src/graphql/resolvers/tenant_settings.ts index 37dfa2565b..5bd720cc7c 100644 --- a/packages/backend/src/graphql/resolvers/tenant_settings.ts +++ b/packages/backend/src/graphql/resolvers/tenant_settings.ts @@ -1,21 +1,14 @@ import { TenantedApolloContext } from '../../app' -import { Pagination } from '../../shared/baseModel' -import { getPageInfo } from '../../shared/pagination' import { TenantSetting } from '../../tenants/settings/model' import { ResolversTypes, - SortOrder, TenantResolvers, TenantSetting as SchemaTenantSetting, MutationResolvers } from '../generated/graphql' export const getTenantSettings: TenantResolvers['settings'] = - async ( - parent, - args, - ctx - ): Promise => { + async (parent, args, ctx): Promise => { if (!parent.id) { throw new Error('missing tenant id') } @@ -24,27 +17,11 @@ export const getTenantSettings: TenantResolvers['settings 'tenantSettingService' ) - const { sortOrder, ...pagination } = args - const order = sortOrder === 'ASC' ? SortOrder.Asc : SortOrder.Desc + const tenantSettings = (await tenantSettingsService.get({ + tenantId: parent.id + })) as TenantSetting[] - const tenantSettings = await tenantSettingsService.getPage( - parent.id, - pagination, - order - ) - const pageInfo = await getPageInfo({ - getPage: (pagination_?: Pagination, sortOrder_?: SortOrder) => - tenantSettingsService.getPage(parent.id!, pagination_, sortOrder_), - page: tenantSettings - }) - - return { - pageInfo, - edges: tenantSettings.map((ts: TenantSetting) => ({ - cursor: ts.id, - node: tenantSettingsToGraphql(ts) - })) - } + return tenantSettings.map((x) => tenantSettingsToGraphql(x)) } export const createTenantSettings: MutationResolvers['createTenantSettings'] = diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index d25bb297a7..a6ec1c6f83 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1590,32 +1590,7 @@ type Tenant implements Model { "The date and time that this tenant was deleted." deletedAt: String "List of settings for the tenant." - settings( - "Forward pagination: Cursor (wallet address key ID) to start retrieving settings after this point." - after: String - "Backward pagination: Cursor (wallet address key ID) to start retrieving keys before this point." - before: String - "Forward pagination: Limit the result to the first **n** keys after the `after` cursor." - first: Int - "Backward pagination: Limit the result to the last **n** keys before the `before` cursor." - last: Int - "Specify the sort order of keys based on their creation data, either ascending or descending." - sortOrder: SortOrder - ): TenantSettingsConnection -} - -type TenantSettingsConnection { - "Information to aid in pagination." - pageInfo: PageInfo! - "A list of edges representing tenant settings and cursors for pagination." - edges: [TenantSettingEdge!]! -} - -type TenantSettingEdge { - "A tenant setting node in the list." - node: TenantSetting! - "A cursor for paginating through the tenants." - cursor: String! + settings: [TenantSetting!] } type TenantsConnection { diff --git a/packages/backend/src/tenants/settings/service.test.ts b/packages/backend/src/tenants/settings/service.test.ts index da2a511ea1..f0631b14fc 100644 --- a/packages/backend/src/tenants/settings/service.test.ts +++ b/packages/backend/src/tenants/settings/service.test.ts @@ -300,4 +300,75 @@ describe('TenantSetting Service', (): void => { expect(dbTenantData.filter((x) => !x.deletedAt)).toHaveLength(0) }) }) + + describe('getTenantSettings', () => { + let tenantSetting: TenantSetting[] + + beforeEach(async (): Promise => { + const createOptions: CreateOptions = { + tenantId: tenant.id, + setting: [exchangeRatesSetting()] + } + + tenantSetting = await tenantSettingService.create(createOptions) + }) + + afterEach(async (): Promise => { + await tenantSettingService.delete({ tenantId: tenant.id }) + }) + + test('should retrieve tenant settings by tenantId', async (): Promise => { + const result = await tenantSettingService.get({ + tenantId: tenant.id + }) + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tenantId: tenant.id, + key: tenantSetting[0].key, + value: tenantSetting[0].value + }) + ]) + ) + }) + + test('should retrieve tenant settings by tenantId and key', async (): Promise => { + const result = await tenantSettingService.get({ + tenantId: tenant.id, + key: tenantSetting[0].key + }) + + expect(result).toEqual([ + expect.objectContaining({ + tenantId: tenant.id, + key: tenantSetting[0].key, + value: tenantSetting[0].value + }) + ]) + }) + + test('should return an empty array if no settings match', async (): Promise => { + const result = await tenantSettingService.get({ + tenantId: tenant.id, + key: 'nonexistent-key' + }) + + expect(result).toEqual([]) + }) + + test('should not retrieve deleted tenant settings', async (): Promise => { + await tenantSettingService.delete({ + tenantId: tenant.id, + key: tenantSetting[0].key + }) + + const result = await tenantSettingService.get({ + tenantId: tenant.id, + key: tenantSetting[0].key + }) + + expect(result).toEqual([]) + }) + }) }) diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 2a3a47560c..28d709fb37 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1490,16 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe; -}; - - -export type TenantSettingsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - sortOrder?: InputMaybe; + settings?: Maybe>; }; export type TenantEdge = { @@ -1523,14 +1514,6 @@ export type TenantSetting = { value: Scalars['String']['output']; }; -export type TenantSettingEdge = { - __typename?: 'TenantSettingEdge'; - /** A cursor for paginating through the tenants. */ - cursor: Scalars['String']['output']; - /** A tenant setting node in the list. */ - node: TenantSetting; -}; - export type TenantSettingInput = { /** Key for this setting. */ key: Scalars['String']['input']; @@ -1538,14 +1521,6 @@ export type TenantSettingInput = { value: Scalars['String']['input']; }; -export type TenantSettingsConnection = { - __typename?: 'TenantSettingsConnection'; - /** A list of edges representing tenant settings and cursors for pagination. */ - edges: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - export type TenantsConnection = { __typename?: 'TenantsConnection'; /** A list of edges representing tenants and cursors for pagination. */ @@ -1917,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; @@ -2034,9 +2009,7 @@ export type ResolversTypes = { TenantEdge: ResolverTypeWrapper>; TenantMutationResponse: ResolverTypeWrapper>; TenantSetting: ResolverTypeWrapper>; - TenantSettingEdge: ResolverTypeWrapper>; TenantSettingInput: ResolverTypeWrapper>; - TenantSettingsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; @@ -2172,9 +2145,7 @@ export type ResolversParentTypes = { TenantEdge: Partial; TenantMutationResponse: Partial; TenantSetting: Partial; - TenantSettingEdge: Partial; TenantSettingInput: Partial; - TenantSettingsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2643,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver, ParentType, ContextType, Partial>; + settings?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2664,18 +2635,6 @@ export type TenantSettingResolvers; }; -export type TenantSettingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type TenantSettingsConnectionResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2851,8 +2810,6 @@ export type Resolvers = { TenantEdge?: TenantEdgeResolvers; TenantMutationResponse?: TenantMutationResponseResolvers; TenantSetting?: TenantSettingResolvers; - TenantSettingEdge?: TenantSettingEdgeResolvers; - TenantSettingsConnection?: TenantSettingsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 284dfd5f75..3f6a1c5b3d 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1490,16 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe; -}; - - -export type TenantSettingsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - sortOrder?: InputMaybe; + settings?: Maybe>; }; export type TenantEdge = { @@ -1523,14 +1514,6 @@ export type TenantSetting = { value: Scalars['String']['output']; }; -export type TenantSettingEdge = { - __typename?: 'TenantSettingEdge'; - /** A cursor for paginating through the tenants. */ - cursor: Scalars['String']['output']; - /** A tenant setting node in the list. */ - node: TenantSetting; -}; - export type TenantSettingInput = { /** Key for this setting. */ key: Scalars['String']['input']; @@ -1538,14 +1521,6 @@ export type TenantSettingInput = { value: Scalars['String']['input']; }; -export type TenantSettingsConnection = { - __typename?: 'TenantSettingsConnection'; - /** A list of edges representing tenant settings and cursors for pagination. */ - edges: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - export type TenantsConnection = { __typename?: 'TenantsConnection'; /** A list of edges representing tenants and cursors for pagination. */ @@ -1917,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; @@ -2034,9 +2009,7 @@ export type ResolversTypes = { TenantEdge: ResolverTypeWrapper>; TenantMutationResponse: ResolverTypeWrapper>; TenantSetting: ResolverTypeWrapper>; - TenantSettingEdge: ResolverTypeWrapper>; TenantSettingInput: ResolverTypeWrapper>; - TenantSettingsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; @@ -2172,9 +2145,7 @@ export type ResolversParentTypes = { TenantEdge: Partial; TenantMutationResponse: Partial; TenantSetting: Partial; - TenantSettingEdge: Partial; TenantSettingInput: Partial; - TenantSettingsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2643,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver, ParentType, ContextType, Partial>; + settings?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2664,18 +2635,6 @@ export type TenantSettingResolvers; }; -export type TenantSettingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type TenantSettingsConnectionResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2851,8 +2810,6 @@ export type Resolvers = { TenantEdge?: TenantEdgeResolvers; TenantMutationResponse?: TenantMutationResponseResolvers; TenantSetting?: TenantSettingResolvers; - TenantSettingEdge?: TenantSettingEdgeResolvers; - TenantSettingsConnection?: TenantSettingsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 284dfd5f75..3f6a1c5b3d 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1490,16 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe; -}; - - -export type TenantSettingsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - sortOrder?: InputMaybe; + settings?: Maybe>; }; export type TenantEdge = { @@ -1523,14 +1514,6 @@ export type TenantSetting = { value: Scalars['String']['output']; }; -export type TenantSettingEdge = { - __typename?: 'TenantSettingEdge'; - /** A cursor for paginating through the tenants. */ - cursor: Scalars['String']['output']; - /** A tenant setting node in the list. */ - node: TenantSetting; -}; - export type TenantSettingInput = { /** Key for this setting. */ key: Scalars['String']['input']; @@ -1538,14 +1521,6 @@ export type TenantSettingInput = { value: Scalars['String']['input']; }; -export type TenantSettingsConnection = { - __typename?: 'TenantSettingsConnection'; - /** A list of edges representing tenant settings and cursors for pagination. */ - edges: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - export type TenantsConnection = { __typename?: 'TenantsConnection'; /** A list of edges representing tenants and cursors for pagination. */ @@ -1917,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes<_RefType extends Record> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; @@ -2034,9 +2009,7 @@ export type ResolversTypes = { TenantEdge: ResolverTypeWrapper>; TenantMutationResponse: ResolverTypeWrapper>; TenantSetting: ResolverTypeWrapper>; - TenantSettingEdge: ResolverTypeWrapper>; TenantSettingInput: ResolverTypeWrapper>; - TenantSettingsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; @@ -2172,9 +2145,7 @@ export type ResolversParentTypes = { TenantEdge: Partial; TenantMutationResponse: Partial; TenantSetting: Partial; - TenantSettingEdge: Partial; TenantSettingInput: Partial; - TenantSettingsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2643,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver, ParentType, ContextType, Partial>; + settings?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; @@ -2664,18 +2635,6 @@ export type TenantSettingResolvers; }; -export type TenantSettingEdgeResolvers = { - cursor?: Resolver; - node?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type TenantSettingsConnectionResolvers = { - edges?: Resolver, ParentType, ContextType>; - pageInfo?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2851,8 +2810,6 @@ export type Resolvers = { TenantEdge?: TenantEdgeResolvers; TenantMutationResponse?: TenantMutationResponseResolvers; TenantSetting?: TenantSettingResolvers; - TenantSettingEdge?: TenantSettingEdgeResolvers; - TenantSettingsConnection?: TenantSettingsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; From cfdcfbe221b45426865e1bb3b1eddc2b0264522e Mon Sep 17 00:00:00 2001 From: Tadej Golobic Date: Sat, 5 Apr 2025 12:00:34 +0200 Subject: [PATCH 21/26] Update packages/backend/src/open_payments/wallet_address/service.ts Co-authored-by: Max Kurapov --- packages/backend/src/open_payments/wallet_address/service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index f50aca0764..18ea61c83b 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -173,10 +173,10 @@ async function createWalletAddressUrl( ): Promise { let tenantWalletAddressUrl = new URL(deps.config.openPaymentsUrl) - const found = (await deps.tenantSettingService.get({ + const found = await deps.tenantSettingService.get({ tenantId: options.tenantId, key: TenantSettingKeys.WALLET_ADDRESS_URL.name - })) as TenantSetting[] + }) if (!found || found.length === 0) { if (!options.isOperator) { From 7c6b9386bbc77cb0b9dad1a5ad6905e309a198cd Mon Sep 17 00:00:00 2001 From: Tadej Golobic Date: Sat, 5 Apr 2025 12:00:42 +0200 Subject: [PATCH 22/26] Update packages/backend/src/graphql/resolvers/tenant_settings.ts Co-authored-by: Max Kurapov --- packages/backend/src/graphql/resolvers/tenant_settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/tenant_settings.ts b/packages/backend/src/graphql/resolvers/tenant_settings.ts index 5bd720cc7c..f0a4756a93 100644 --- a/packages/backend/src/graphql/resolvers/tenant_settings.ts +++ b/packages/backend/src/graphql/resolvers/tenant_settings.ts @@ -17,9 +17,9 @@ export const getTenantSettings: TenantResolvers['settings 'tenantSettingService' ) - const tenantSettings = (await tenantSettingsService.get({ + const tenantSettings = await tenantSettingsService.get({ tenantId: parent.id - })) as TenantSetting[] + }) return tenantSettings.map((x) => tenantSettingsToGraphql(x)) } From b19d84adad0fcb9c8ff8c66761ae3975d4107e01 Mon Sep 17 00:00:00 2001 From: Tadej Golobic Date: Sat, 5 Apr 2025 12:01:58 +0200 Subject: [PATCH 23/26] Update packages/backend/src/graphql/schema.graphql Co-authored-by: Max Kurapov --- packages/backend/src/graphql/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index a6ec1c6f83..7142a4d762 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1590,7 +1590,7 @@ type Tenant implements Model { "The date and time that this tenant was deleted." deletedAt: String "List of settings for the tenant." - settings: [TenantSetting!] + settings: [TenantSetting!]! } type TenantsConnection { From 98fa10820bd3879b08bb1dc9bc3e323064ecaf23 Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 5 Apr 2025 12:03:12 +0200 Subject: [PATCH 24/26] chore(graphql): generate schema --- .../generated/graphql.ts | 4 ++-- .../src/graphql/generated/graphql.schema.json | 14 +++++++++----- packages/backend/src/graphql/generated/graphql.ts | 4 ++-- .../open_payments/wallet_address/service.test.ts | 8 ++++++++ .../src/open_payments/wallet_address/service.ts | 2 +- packages/frontend/app/generated/graphql.ts | 4 ++-- .../src/generated/graphql.ts | 4 ++-- test/integration/lib/generated/graphql.ts | 4 ++-- 8 files changed, 28 insertions(+), 16 deletions(-) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 3f6a1c5b3d..8a02a6edbe 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1490,7 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe>; + settings: Array; }; export type TenantEdge = { @@ -2614,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver>, ParentType, ContextType>; + settings?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 357cc55b23..4e0058a90d 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -8337,15 +8337,19 @@ "description": "List of settings for the tenant.", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "OBJECT", - "name": "TenantSetting", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantSetting", + "ofType": null + } } } }, diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 3f6a1c5b3d..8a02a6edbe 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1490,7 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe>; + settings: Array; }; export type TenantEdge = { @@ -2614,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver>, ParentType, ContextType>; + settings?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index ee68f4db61..c81b810786 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -165,6 +165,14 @@ describe('Open Payments Wallet Address Service', (): void => { ).toEqual(WalletAddressError.WalletAddressSettingNotFound) }) + test('should return InvalidUrl error if wallet address URL does not start with tenant wallet address URL', async (): Promise => { + const result = await walletAddressService.create({ + ...options, + address: 'https://bob.me/.well-known/pay' + }) + expect(result).toEqual(WalletAddressError.InvalidUrl) + }) + test.each` setting | address | generated ${'https://alice.me/ilp'} | ${'https://alice.me/ilp/test'} | ${'https://alice.me/ilp/test'} diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 18ea61c83b..aa3f756d3a 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -28,7 +28,7 @@ import { poll } from '../../shared/utils' import { WalletAddressAdditionalProperty } from './additional_property/model' import { AssetService } from '../../asset/service' import { CacheDataStore } from '../../middleware/cache/data-stores' -import { TenantSetting, TenantSettingKeys } from '../../tenants/settings/model' +import { TenantSettingKeys } from '../../tenants/settings/model' import { TenantSettingService } from '../../tenants/settings/service' interface Options { diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 28d709fb37..587683072b 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1490,7 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe>; + settings: Array; }; export type TenantEdge = { @@ -2614,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver>, ParentType, ContextType>; + settings?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 3f6a1c5b3d..8a02a6edbe 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1490,7 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe>; + settings: Array; }; export type TenantEdge = { @@ -2614,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver>, ParentType, ContextType>; + settings?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 3f6a1c5b3d..8a02a6edbe 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1490,7 +1490,7 @@ export type Tenant = Model & { /** Public name for the tenant. */ publicName?: Maybe; /** List of settings for the tenant. */ - settings?: Maybe>; + settings: Array; }; export type TenantEdge = { @@ -2614,7 +2614,7 @@ export type TenantResolvers, ParentType, ContextType>; idpSecret?: Resolver, ParentType, ContextType>; publicName?: Resolver, ParentType, ContextType>; - settings?: Resolver>, ParentType, ContextType>; + settings?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; From 312c24251fd33731c0f7623022c8bcddac744b6a Mon Sep 17 00:00:00 2001 From: golobitch Date: Sat, 5 Apr 2025 21:52:17 +0200 Subject: [PATCH 25/26] fix(tenant): mapping settings to tenant --- packages/backend/src/graphql/resolvers/tenant.ts | 2 ++ .../src/graphql/resolvers/tenant_settings.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/graphql/resolvers/tenant.ts b/packages/backend/src/graphql/resolvers/tenant.ts index eae36ebc3a..854accc83f 100644 --- a/packages/backend/src/graphql/resolvers/tenant.ts +++ b/packages/backend/src/graphql/resolvers/tenant.ts @@ -10,6 +10,7 @@ import { GraphQLErrorCode } from '../errors' import { Tenant } from '../../tenants/model' import { Pagination, SortOrder } from '../../shared/baseModel' import { getPageInfo } from '../../shared/pagination' +import { tenantSettingsToGraphql } from './tenant_settings' export const whoami: QueryResolvers['whoami'] = async ( parent, @@ -182,6 +183,7 @@ export function tenantToGraphQl(tenant: Tenant): SchemaTenant { idpConsentUrl: tenant.idpConsentUrl, idpSecret: tenant.idpSecret, publicName: tenant.publicName, + settings: tenantSettingsToGraphql(tenant.settings), createdAt: new Date(+tenant.createdAt).toISOString(), deletedAt: tenant.deletedAt ? new Date(+tenant.deletedAt).toISOString() diff --git a/packages/backend/src/graphql/resolvers/tenant_settings.ts b/packages/backend/src/graphql/resolvers/tenant_settings.ts index f0a4756a93..ceb474c524 100644 --- a/packages/backend/src/graphql/resolvers/tenant_settings.ts +++ b/packages/backend/src/graphql/resolvers/tenant_settings.ts @@ -21,7 +21,7 @@ export const getTenantSettings: TenantResolvers['settings tenantId: parent.id }) - return tenantSettings.map((x) => tenantSettingsToGraphql(x)) + return tenantSettingsToGraphql(tenantSettings) } export const createTenantSettings: MutationResolvers['createTenantSettings'] = @@ -38,13 +38,22 @@ export const createTenantSettings: MutationResolvers['cre }) return { - settings: tenantSettings.map((x) => tenantSettingsToGraphql(x)) + settings: tenantSettingsToGraphql(tenantSettings) } } -export const tenantSettingsToGraphql = ( +const tenantSettingToGraphql = ( tenantSetting: TenantSetting ): SchemaTenantSetting => ({ key: tenantSetting.key, value: tenantSetting.value }) + +export const tenantSettingsToGraphql = ( + tenantSettings?: TenantSetting[] +): SchemaTenantSetting[] => { + if (!tenantSettings) { + return [] + } + return tenantSettings.map((x) => tenantSettingToGraphql(x)) +} From 0302d69c980f2a4c3ea9a7f0bb489a85652853bd Mon Sep 17 00:00:00 2001 From: golobitch Date: Sun, 6 Apr 2025 09:36:22 +0200 Subject: [PATCH 26/26] chore(graphql): generate schema --- localenv/mock-account-servicing-entity/generated/graphql.ts | 2 +- packages/backend/src/graphql/generated/graphql.ts | 2 +- packages/frontend/app/generated/graphql.ts | 2 +- packages/mock-account-service-lib/src/generated/graphql.ts | 2 +- test/integration/lib/generated/graphql.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 8a02a6edbe..8db21d81f0 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1892,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 8a02a6edbe..8db21d81f0 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1892,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 587683072b..6587a706db 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1892,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 8a02a6edbe..8db21d81f0 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1892,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 8a02a6edbe..8db21d81f0 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1892,7 +1892,7 @@ export type DirectiveResolverFn> = { +export type ResolversInterfaceTypes> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); };