diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant.bru index 03bc03926f..0e8a9e0b0e 100644 --- a/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant.bru +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Get Tenant.bru @@ -6,6 +6,31 @@ meta { post { url: {{RafikiGraphqlHost}}/graphql - body: none + body: graphql auth: none } + +body:graphql { + query GetTenant($id: ID!) { + tenant(id:$id) { + id + kratosIdentityId + createdAt + updatedAt + endpoints { + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + } + } + } +} + +body:graphql:vars { + { + "id": "7a0c75bd-6c09-4d38-b013-af89ab91557a" + } +} diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 7eeceba0a4..d2f1d62ed5 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -1305,10 +1305,12 @@ export enum SortOrder { Desc = 'DESC' } -export type Tenant = { +export type Tenant = Model & { __typename?: 'Tenant'; /** Date-time of creation */ createdAt: Scalars['String']['output']; + /** List of tenant endpoints associated with this tenant */ + endpoints: Array; /** Tenant ID that is used in subsequent resources */ id: Scalars['ID']['output']; /** Kratos identity ID */ @@ -1323,11 +1325,29 @@ export type TenantEdge = { node: Tenant; }; +export type TenantEndpoint = { + __typename?: 'TenantEndpoint'; + type: TenantEndpointType; + value: Scalars['String']['output']; +}; + +export type TenantEndpointEdge = { + __typename?: 'TenantEndpointEdge'; + cursor: Scalars['String']['output']; + node?: Maybe; +}; + export enum TenantEndpointType { RatesUrl = 'RatesUrl', WebhookBaseUrl = 'WebhookBaseUrl' } +export type TenantEndpointsConnection = { + __typename?: 'TenantEndpointsConnection'; + edges: Array; + pageInfo: PageInfo; +}; + export type TenantsConnection = { __typename?: 'TenantsConnection'; edges: Array; @@ -1644,7 +1664,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,7 +1776,10 @@ export type ResolversTypes = { String: ResolverTypeWrapper>; Tenant: ResolverTypeWrapper>; TenantEdge: ResolverTypeWrapper>; + TenantEndpoint: ResolverTypeWrapper>; + TenantEndpointEdge: ResolverTypeWrapper>; TenantEndpointType: ResolverTypeWrapper>; + TenantEndpointsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1885,6 +1908,9 @@ export type ResolversParentTypes = { String: Partial; Tenant: Partial; TenantEdge: Partial; + TenantEndpoint: Partial; + TenantEndpointEdge: Partial; + TenantEndpointsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2115,7 +2141,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2323,6 +2349,7 @@ export type SetFeeResponseResolvers = { createdAt?: Resolver; + endpoints?: Resolver, ParentType, ContextType>; id?: Resolver; kratosIdentityId?: Resolver; updatedAt?: Resolver; @@ -2335,6 +2362,24 @@ export type TenantEdgeResolvers; }; +export type TenantEndpointResolvers = { + type?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2500,6 +2545,9 @@ export type Resolvers = { SetFeeResponse?: SetFeeResponseResolvers; Tenant?: TenantResolvers; TenantEdge?: TenantEdgeResolvers; + TenantEndpoint?: TenantEndpointResolvers; + TenantEndpointEdge?: TenantEndpointEdgeResolvers; + TenantEndpointsConnection?: TenantEndpointsConnectionResolvers; 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 4073287bb8..447f462915 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -4161,6 +4161,11 @@ "name": "Peer", "ofType": null }, + { + "kind": "OBJECT", + "name": "Tenant", + "ofType": null + }, { "kind": "OBJECT", "name": "WalletAddress", @@ -7805,6 +7810,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "endpoints", + "description": "List of tenant endpoints associated with this tenant", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantEndpoint", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "id", "description": "Tenant ID that is used in subsequent resources", @@ -7855,7 +7884,13 @@ } ], "inputFields": null, - "interfaces": [], + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Model", + "ofType": null + } + ], "enumValues": null, "possibleTypes": null }, @@ -7902,6 +7937,88 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "TenantEndpoint", + "description": null, + "fields": [ + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "TenantEndpointType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "value", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TenantEndpointEdge", + "description": null, + "fields": [ + { + "name": "cursor", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "TenantEndpoint", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "ENUM", "name": "TenantEndpointType", @@ -7925,6 +8042,57 @@ ], "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "TenantEndpointsConnection", + "description": null, + "fields": [ + { + "name": "edges", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantEndpointEdge", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": null, + "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 7eeceba0a4..d2f1d62ed5 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -1305,10 +1305,12 @@ export enum SortOrder { Desc = 'DESC' } -export type Tenant = { +export type Tenant = Model & { __typename?: 'Tenant'; /** Date-time of creation */ createdAt: Scalars['String']['output']; + /** List of tenant endpoints associated with this tenant */ + endpoints: Array; /** Tenant ID that is used in subsequent resources */ id: Scalars['ID']['output']; /** Kratos identity ID */ @@ -1323,11 +1325,29 @@ export type TenantEdge = { node: Tenant; }; +export type TenantEndpoint = { + __typename?: 'TenantEndpoint'; + type: TenantEndpointType; + value: Scalars['String']['output']; +}; + +export type TenantEndpointEdge = { + __typename?: 'TenantEndpointEdge'; + cursor: Scalars['String']['output']; + node?: Maybe; +}; + export enum TenantEndpointType { RatesUrl = 'RatesUrl', WebhookBaseUrl = 'WebhookBaseUrl' } +export type TenantEndpointsConnection = { + __typename?: 'TenantEndpointsConnection'; + edges: Array; + pageInfo: PageInfo; +}; + export type TenantsConnection = { __typename?: 'TenantsConnection'; edges: Array; @@ -1644,7 +1664,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,7 +1776,10 @@ export type ResolversTypes = { String: ResolverTypeWrapper>; Tenant: ResolverTypeWrapper>; TenantEdge: ResolverTypeWrapper>; + TenantEndpoint: ResolverTypeWrapper>; + TenantEndpointEdge: ResolverTypeWrapper>; TenantEndpointType: ResolverTypeWrapper>; + TenantEndpointsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1885,6 +1908,9 @@ export type ResolversParentTypes = { String: Partial; Tenant: Partial; TenantEdge: Partial; + TenantEndpoint: Partial; + TenantEndpointEdge: Partial; + TenantEndpointsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2115,7 +2141,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2323,6 +2349,7 @@ export type SetFeeResponseResolvers = { createdAt?: Resolver; + endpoints?: Resolver, ParentType, ContextType>; id?: Resolver; kratosIdentityId?: Resolver; updatedAt?: Resolver; @@ -2335,6 +2362,24 @@ export type TenantEdgeResolvers; }; +export type TenantEndpointResolvers = { + type?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2500,6 +2545,9 @@ export type Resolvers = { SetFeeResponse?: SetFeeResponseResolvers; Tenant?: TenantResolvers; TenantEdge?: TenantEdgeResolvers; + TenantEndpoint?: TenantEndpointResolvers; + TenantEndpointEdge?: TenantEndpointEdgeResolvers; + TenantEndpointsConnection?: TenantEndpointsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index fa27b2224b..e6f05f04ae 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -68,6 +68,7 @@ import { getCombinedPayments } from './combined_payments' import { createOrUpdatePeerByUrl } from './auto-peering' import { getAccountingTransfers } from './accounting_transfer' import { createTenant, getTenant, getTenants } from './tenant' +import { getTenantEndpoints } from './tenant_endpoints' export const resolvers: Resolvers = { UInt8: GraphQLUInt8, diff --git a/packages/backend/src/graphql/resolvers/tenant.ts b/packages/backend/src/graphql/resolvers/tenant.ts index ffc42877e4..9d023ceda9 100644 --- a/packages/backend/src/graphql/resolvers/tenant.ts +++ b/packages/backend/src/graphql/resolvers/tenant.ts @@ -13,9 +13,11 @@ import { ResolversTypes, TenantEndpointType } from '../generated/graphql' -import { EndpointType, Tenant } from '../../tenant/model' +import { Tenant } from '../../tenant/model' import { Pagination, SortOrder } from '../../shared/baseModel' import { getPageInfo } from '../../shared/pagination' +import { EndpointType, TenantEndpoint } from '../../tenant/endpoints/model' +import { tenantEndpointToGraphql } from './tenant_endpoints' const mapTenantEndpointTypeToModelEndpointType = { [TenantEndpointType.RatesUrl]: EndpointType.RatesUrl, @@ -32,7 +34,7 @@ export const getTenants: QueryResolvers['tenants'] = async ( const order = sortOrder === 'ASC' ? SortOrder.Asc : SortOrder.Desc const tenants = await tenantService.getPage(pagination, order) const pageInfo = await getPageInfo({ - getPage: (pagination: Pagination, sortOrder?: SortOrder) => + getPage: (pagination: Pagination, sortOrder?: SortOrder) => tenantService.getPage(pagination, sortOrder), page: tenants, sortOrder: order @@ -62,7 +64,6 @@ export const getTenant: QueryResolvers['tenant'] = async ( } }) } - console.log('TENANT: ', tenant) return tenantToGraphql(tenant) } @@ -103,6 +104,8 @@ export function tenantToGraphql(tenant: Tenant): SchemaTenant { return { id: tenant.id, kratosIdentityId: tenant.kratosIdentityId, + //we should probably paginate this, but for now, that we only have like two endpoints it should be ok + endpoints: tenant.endpoints.map(tenantEndpointToGraphql), createdAt: new Date(tenant.createdAt).toISOString(), updatedAt: new Date(tenant.updatedAt).toISOString() } diff --git a/packages/backend/src/graphql/resolvers/tenant_endpoints.ts b/packages/backend/src/graphql/resolvers/tenant_endpoints.ts new file mode 100644 index 0000000000..14197d73ce --- /dev/null +++ b/packages/backend/src/graphql/resolvers/tenant_endpoints.ts @@ -0,0 +1,63 @@ +import { ApolloContext } from '../../app' +import { Pagination, SortOrder } from '../../shared/baseModel' +import { getPageInfo } from '../../shared/pagination' +import { EndpointType, TenantEndpoint } from '../../tenant/endpoints/model' +import { + ResolversTypes, + TenantResolvers, + TenantEndpoint as SchemaTenantEndpoint, + TenantEndpointType +} from '../generated/graphql' + +export const mapTenantEndpointTypeToModelEndpointType = { + [EndpointType.RatesUrl]: TenantEndpointType.RatesUrl, + [EndpointType.WebhookBaseUrl]: TenantEndpointType.WebhookBaseUrl +} + +export const getTenantEndpoints: TenantResolvers['endpoints'] = + async ( + parent, + args, + ctx + ): Promise => { + if (!parent.id) { + throw new Error('missing tenant id') + } + const tenantEndpointService = await ctx.container.use( + 'tenantEndpointService' + ) + + const { sortOrder, ...pagination } = args + const order = sortOrder === 'ASC' ? SortOrder.Asc : SortOrder.Desc + + const tenantEndpoints = await tenantEndpointService.getPage( + parent.id, + pagination, + order + ) + + console.log('TENANT ENDPOINTS: ', tenantEndpoints) + + const pageInfo = await getPageInfo({ + getPage: (pagination_?: Pagination, sortOrder_?: SortOrder) => + tenantEndpointService.getPage(parent.id!, pagination_, sortOrder_), + page: tenantEndpoints + }) + + return { + pageInfo, + edges: tenantEndpoints.map((endpoint: TenantEndpoint) => ({ + cursor: `${endpoint.tenantId}${endpoint.type}`, + node: tenantEndpointToGraphql(endpoint) + })) + } + } + +export function tenantEndpointToGraphql( + tenantEndpoint: TenantEndpoint +): SchemaTenantEndpoint { + return { + type: mapTenantEndpointTypeToModelEndpointType[tenantEndpoint.type], + value: tenantEndpoint.value + } +} diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 9b8b451562..eb90571157 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -303,6 +303,16 @@ type PageInfo { startCursor: String } +type TenantEndpointsConnection { + pageInfo: PageInfo! + edges: [TenantEndpointEdge!]! +} + +type TenantEndpointEdge { + node: TenantEndpoint + cursor: String! +} + type TenantsConnection { pageInfo: PageInfo! edges: [TenantEdge!]! @@ -1371,7 +1381,12 @@ type CreateTenantMutationResponse { tenant: Tenant! } -type Tenant { +type TenantEndpoint { + type: TenantEndpointType! + value: String! +} + +type Tenant implements Model { "Tenant ID that is used in subsequent resources" id: ID! "Kratos identity ID" @@ -1380,6 +1395,8 @@ type Tenant { createdAt: String! "Date-time of the update" updatedAt: String! + "List of tenant endpoints associated with this tenant" + endpoints: [TenantEndpoint!]! } type LiquidityMutationResponse { diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 52d64e9d4a..a855dc30cf 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -68,6 +68,7 @@ import { import { onError } from '@apollo/client/link/error' import { setContext } from '@apollo/client/link/context' import { canonicalize } from 'json-canonicalize' +import { createTenantEndpointService } from './tenant/endpoints/service' BigInt.prototype.toJSON = function () { return this.toString() @@ -524,15 +525,32 @@ export function initIocContainer( return client }) - container.singleton('tenantService', async (deps) => { - const [logger, knex, config, apolloClient] = await Promise.all([ + container.singleton('tenantEndpointService', async (deps) => { + const [logger, knex] = await Promise.all([ deps.use('logger'), - deps.use('knex'), - deps.use('config'), - deps.use('apolloClient') + deps.use('knex') ]) - return createTenantService({ logger, knex, config, apolloClient }) + return createTenantEndpointService({ knex, logger }) + }) + + container.singleton('tenantService', async (deps) => { + const [logger, knex, config, apolloClient, tenantEndpointService] = + await Promise.all([ + deps.use('logger'), + deps.use('knex'), + deps.use('config'), + deps.use('apolloClient'), + deps.use('tenantEndpointService') + ]) + + return createTenantService({ + logger, + knex, + config, + apolloClient, + tenantEndpointService + }) }) container.singleton('paymentMethodHandlerService', async (deps) => { diff --git a/packages/backend/src/tenant/endpoints/errors.ts b/packages/backend/src/tenant/endpoints/errors.ts new file mode 100644 index 0000000000..92aab4e4dc --- /dev/null +++ b/packages/backend/src/tenant/endpoints/errors.ts @@ -0,0 +1,21 @@ +import { GraphQLErrorCode } from '../../graphql/errors' + +export enum TenantEndpointError { + UnknownError = 'UnknownError' +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isTenantError = (t: any): t is TenantEndpointError => + Object.values(TenantEndpointError).includes(t) + +export const errorToCode: { + [key in TenantEndpointError]: GraphQLErrorCode +} = { + [TenantEndpointError.UnknownError]: GraphQLErrorCode.InternalServerError +} + +export const errorToMessage: { + [key in TenantEndpointError]: string +} = { + [TenantEndpointError.UnknownError]: 'Unknown error' +} diff --git a/packages/backend/src/tenant/endpoints/model.ts b/packages/backend/src/tenant/endpoints/model.ts new file mode 100644 index 0000000000..53860b770c --- /dev/null +++ b/packages/backend/src/tenant/endpoints/model.ts @@ -0,0 +1,16 @@ +import { WeakModel } from '../../shared/baseModel' + +export enum EndpointType { + WebhookBaseUrl = 'WebhookBaseUrl', + RatesUrl = 'RatesUrl' +} + +export class TenantEndpoint extends WeakModel { + public static get tableName(): string { + return 'tenantEndpoints' + } + + public type!: EndpointType + public value!: string + public tenantId!: string +} diff --git a/packages/backend/src/tenant/endpoints/service.ts b/packages/backend/src/tenant/endpoints/service.ts new file mode 100644 index 0000000000..83c314e761 --- /dev/null +++ b/packages/backend/src/tenant/endpoints/service.ts @@ -0,0 +1,91 @@ +import { TransactionOrKnex } from 'objection' +import { BaseService } from '../../shared/baseService' +import { TenantEndpointError } from './errors' +import { EndpointType, TenantEndpoint } from './model' +import { Pagination, SortOrder } from '../../shared/baseModel' + +export interface EndpointOptions { + value: string + type: EndpointType +} + +export interface CreateOptions { + endpoints: EndpointOptions[] + tenantId: string + trx?: TransactionOrKnex +} + +export interface TenantEndpointService { + create( + createOptions: CreateOptions + ): Promise + getForTenant(tenantId: string): Promise + getPage( + pagination?: Pagination, + sortOrder?: SortOrder + ): Promise +} + +export interface ServiceDependencies extends BaseService { + knex: TransactionOrKnex +} + +export async function createTenantEndpointService( + deps_: ServiceDependencies +): Promise { + const deps: ServiceDependencies = { + logger: deps_.logger.child({ + service: 'TenantEndpointService' + }), + knex: deps_.knex + } + + return { + create: (createOptions: CreateOptions) => { + if (!createOptions.trx) { + createOptions.trx = deps.knex + } + return createTenantEndpoint(deps, createOptions) + }, + getForTenant: (tenantId: string) => getEndpointsForTenant(deps, tenantId), + getPage: (pagination?, sortOrder?) => + getTenantEndpointsPage(deps, pagination, sortOrder) + } +} + +async function getTenantEndpointsPage( + deps: ServiceDependencies, + pagination?: Pagination, + sortOrder?: SortOrder +) { + console.log('GET TENANT ENDPOINTS PAGE') + const data = await TenantEndpoint.query(deps.knex) + .returning(['type', 'value', 'createdAt', 'updatedAt']) + .getPage(pagination, sortOrder) + console.log('DATA: ', data) + return data +} + +async function createTenantEndpoint( + deps: ServiceDependencies, + createOptions: CreateOptions +): Promise { + const tenantEndpointsData = createOptions.endpoints.map((endpoint) => ({ + type: endpoint.type, + value: endpoint.value, + tenantId: createOptions.tenantId + })) + + return await TenantEndpoint.query(createOptions.trx) + .insert(tenantEndpointsData) + .returning(['type', 'value', 'createdAt', 'updatedAt']) +} + +async function getEndpointsForTenant( + deps: ServiceDependencies, + tenantId: string +): Promise { + return TenantEndpoint.query(deps.knex) + .where('tenantId', tenantId) + .returning(['type', 'value', 'createdAt', 'updatedAt']) +} diff --git a/packages/backend/src/tenant/model.ts b/packages/backend/src/tenant/model.ts index 4c3a76c756..6f86ec70f9 100644 --- a/packages/backend/src/tenant/model.ts +++ b/packages/backend/src/tenant/model.ts @@ -1,39 +1,26 @@ +import { Model } from 'objection' import { BaseModel, WeakModel } from '../shared/baseModel' - -// export type EndpointType = 'WebhookBaseUrl' | 'RatesUrl' -export enum EndpointType { - WebhookBaseUrl = 'WebhookBaseUrl', - RatesUrl = 'RatesUrl' -} +import { TenantEndpoint } from './endpoints/model' export class Tenant extends BaseModel { public static get tableName(): string { return 'tenants' } - public kratosIdentityId!: string - public deletedAt?: Date -} - -export class TenantEndpoints extends WeakModel { - public static get tableName(): string { - return 'tenantEndpoints' - } - public static get relationMappings() { return { - tenant: { - relation: WeakModel.HasOneRelation, - modelClass: Tenant, + endpoints: { + relation: Model.HasManyRelation, + modelClass: TenantEndpoint, join: { - from: 'tenantEndpoints.tenantId', - to: 'tenants.id' + from: 'tenants.id', + to: 'tenantEndpoints.tenantId' } } } } - public type!: EndpointType - public value!: string - public tenantId!: string + public kratosIdentityId!: string + public deletedAt?: Date + public endpoints!: TenantEndpoint[] } diff --git a/packages/backend/src/tenant/service.ts b/packages/backend/src/tenant/service.ts index 099be87e33..24e9f1bfb8 100644 --- a/packages/backend/src/tenant/service.ts +++ b/packages/backend/src/tenant/service.ts @@ -1,7 +1,7 @@ import { TransactionOrKnex } from 'objection' import { BaseService } from '../shared/baseService' import { TenantError } from './errors' -import { EndpointType, Tenant, TenantEndpoints } from './model' +import { Tenant } from './model' import { IAppConfig } from '../config/app' import { ApolloClient, gql, NormalizedCacheObject } from '@apollo/client' import { @@ -10,11 +10,8 @@ import { } from '../generated/graphql' import { v4 as uuidv4 } from 'uuid' import { Pagination, SortOrder } from '../shared/baseModel' - -export interface EndpointOptions { - value: string - type: EndpointType -} +import { EndpointOptions, TenantEndpointService } from './endpoints/service' +import { TenantEndpoint } from './endpoints/model' export interface CreateTenantOptions { idpConsentEndpoint: string @@ -25,13 +22,14 @@ export interface CreateTenantOptions { export interface TenantService { get(id: string): Promise getPage(pagination?: Pagination, sortOrder?: SortOrder): Promise - create(CreateOptions: CreateTenantOptions): Promise + create(createOptions: CreateTenantOptions): Promise } export interface ServiceDependencies extends BaseService { knex: TransactionOrKnex config: IAppConfig apolloClient: ApolloClient + tenantEndpointService: TenantEndpointService } export async function createTenantService( @@ -43,7 +41,8 @@ export async function createTenantService( }), knex: deps_.knex, config: deps_.config, - apolloClient: deps_.apolloClient + apolloClient: deps_.apolloClient, + tenantEndpointService: deps_.tenantEndpointService } return { @@ -59,7 +58,8 @@ async function getTenantsPage( pagination?: Pagination, sortOrder?: SortOrder ): Promise { - return await Tenant.query(deps.knex).getPage(pagination, sortOrder) + return await Tenant.query(deps.knex) + .getPage(pagination, sortOrder) } async function getTenant( @@ -67,6 +67,7 @@ async function getTenant( id: string ): Promise { return Tenant.query(deps.knex) + .withGraphFetched('endpoints') .findById(id) } @@ -89,15 +90,11 @@ async function createTenant( kratosIdentityId: uuidv4() }) - const tenantEndpointsData = options.endpoints.map((endpoint) => ({ - type: endpoint.type, - value: endpoint.value, - tenantId: tenant.id - })) - - await TenantEndpoints.query(trx) - .insert(tenantEndpointsData) - .returning(['type', 'value']) + await deps.tenantEndpointService.create({ + endpoints: options.endpoints, + tenantId: tenant.id, + trx + }) // call auth admin api const mutation = gql` diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index e7ab79670a..eaec30b692 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -1305,10 +1305,12 @@ export enum SortOrder { Desc = 'DESC' } -export type Tenant = { +export type Tenant = Model & { __typename?: 'Tenant'; /** Date-time of creation */ createdAt: Scalars['String']['output']; + /** List of tenant endpoints associated with this tenant */ + endpoints: Array; /** Tenant ID that is used in subsequent resources */ id: Scalars['ID']['output']; /** Kratos identity ID */ @@ -1323,11 +1325,29 @@ export type TenantEdge = { node: Tenant; }; +export type TenantEndpoint = { + __typename?: 'TenantEndpoint'; + type: TenantEndpointType; + value: Scalars['String']['output']; +}; + +export type TenantEndpointEdge = { + __typename?: 'TenantEndpointEdge'; + cursor: Scalars['String']['output']; + node?: Maybe; +}; + export enum TenantEndpointType { RatesUrl = 'RatesUrl', WebhookBaseUrl = 'WebhookBaseUrl' } +export type TenantEndpointsConnection = { + __typename?: 'TenantEndpointsConnection'; + edges: Array; + pageInfo: PageInfo; +}; + export type TenantsConnection = { __typename?: 'TenantsConnection'; edges: Array; @@ -1644,7 +1664,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,7 +1776,10 @@ export type ResolversTypes = { String: ResolverTypeWrapper>; Tenant: ResolverTypeWrapper>; TenantEdge: ResolverTypeWrapper>; + TenantEndpoint: ResolverTypeWrapper>; + TenantEndpointEdge: ResolverTypeWrapper>; TenantEndpointType: ResolverTypeWrapper>; + TenantEndpointsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1885,6 +1908,9 @@ export type ResolversParentTypes = { String: Partial; Tenant: Partial; TenantEdge: Partial; + TenantEndpoint: Partial; + TenantEndpointEdge: Partial; + TenantEndpointsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2115,7 +2141,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2323,6 +2349,7 @@ export type SetFeeResponseResolvers = { createdAt?: Resolver; + endpoints?: Resolver, ParentType, ContextType>; id?: Resolver; kratosIdentityId?: Resolver; updatedAt?: Resolver; @@ -2335,6 +2362,24 @@ export type TenantEdgeResolvers; }; +export type TenantEndpointResolvers = { + type?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2500,6 +2545,9 @@ export type Resolvers = { SetFeeResponse?: SetFeeResponseResolvers; Tenant?: TenantResolvers; TenantEdge?: TenantEdgeResolvers; + TenantEndpoint?: TenantEndpointResolvers; + TenantEndpointEdge?: TenantEndpointEdgeResolvers; + TenantEndpointsConnection?: TenantEndpointsConnectionResolvers; 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 7eeceba0a4..d2f1d62ed5 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -1305,10 +1305,12 @@ export enum SortOrder { Desc = 'DESC' } -export type Tenant = { +export type Tenant = Model & { __typename?: 'Tenant'; /** Date-time of creation */ createdAt: Scalars['String']['output']; + /** List of tenant endpoints associated with this tenant */ + endpoints: Array; /** Tenant ID that is used in subsequent resources */ id: Scalars['ID']['output']; /** Kratos identity ID */ @@ -1323,11 +1325,29 @@ export type TenantEdge = { node: Tenant; }; +export type TenantEndpoint = { + __typename?: 'TenantEndpoint'; + type: TenantEndpointType; + value: Scalars['String']['output']; +}; + +export type TenantEndpointEdge = { + __typename?: 'TenantEndpointEdge'; + cursor: Scalars['String']['output']; + node?: Maybe; +}; + export enum TenantEndpointType { RatesUrl = 'RatesUrl', WebhookBaseUrl = 'WebhookBaseUrl' } +export type TenantEndpointsConnection = { + __typename?: 'TenantEndpointsConnection'; + edges: Array; + pageInfo: PageInfo; +}; + export type TenantsConnection = { __typename?: 'TenantsConnection'; edges: Array; @@ -1644,7 +1664,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,7 +1776,10 @@ export type ResolversTypes = { String: ResolverTypeWrapper>; Tenant: ResolverTypeWrapper>; TenantEdge: ResolverTypeWrapper>; + TenantEndpoint: ResolverTypeWrapper>; + TenantEndpointEdge: ResolverTypeWrapper>; TenantEndpointType: ResolverTypeWrapper>; + TenantEndpointsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1885,6 +1908,9 @@ export type ResolversParentTypes = { String: Partial; Tenant: Partial; TenantEdge: Partial; + TenantEndpoint: Partial; + TenantEndpointEdge: Partial; + TenantEndpointsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2115,7 +2141,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2323,6 +2349,7 @@ export type SetFeeResponseResolvers = { createdAt?: Resolver; + endpoints?: Resolver, ParentType, ContextType>; id?: Resolver; kratosIdentityId?: Resolver; updatedAt?: Resolver; @@ -2335,6 +2362,24 @@ export type TenantEdgeResolvers; }; +export type TenantEndpointResolvers = { + type?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2500,6 +2545,9 @@ export type Resolvers = { SetFeeResponse?: SetFeeResponseResolvers; Tenant?: TenantResolvers; TenantEdge?: TenantEdgeResolvers; + TenantEndpoint?: TenantEndpointResolvers; + TenantEndpointEdge?: TenantEndpointEdgeResolvers; + TenantEndpointsConnection?: TenantEndpointsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 7eeceba0a4..d2f1d62ed5 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -1305,10 +1305,12 @@ export enum SortOrder { Desc = 'DESC' } -export type Tenant = { +export type Tenant = Model & { __typename?: 'Tenant'; /** Date-time of creation */ createdAt: Scalars['String']['output']; + /** List of tenant endpoints associated with this tenant */ + endpoints: Array; /** Tenant ID that is used in subsequent resources */ id: Scalars['ID']['output']; /** Kratos identity ID */ @@ -1323,11 +1325,29 @@ export type TenantEdge = { node: Tenant; }; +export type TenantEndpoint = { + __typename?: 'TenantEndpoint'; + type: TenantEndpointType; + value: Scalars['String']['output']; +}; + +export type TenantEndpointEdge = { + __typename?: 'TenantEndpointEdge'; + cursor: Scalars['String']['output']; + node?: Maybe; +}; + export enum TenantEndpointType { RatesUrl = 'RatesUrl', WebhookBaseUrl = 'WebhookBaseUrl' } +export type TenantEndpointsConnection = { + __typename?: 'TenantEndpointsConnection'; + edges: Array; + pageInfo: PageInfo; +}; + export type TenantsConnection = { __typename?: 'TenantsConnection'; edges: Array; @@ -1644,7 +1664,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,7 +1776,10 @@ export type ResolversTypes = { String: ResolverTypeWrapper>; Tenant: ResolverTypeWrapper>; TenantEdge: ResolverTypeWrapper>; + TenantEndpoint: ResolverTypeWrapper>; + TenantEndpointEdge: ResolverTypeWrapper>; TenantEndpointType: ResolverTypeWrapper>; + TenantEndpointsConnection: ResolverTypeWrapper>; TenantsConnection: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1885,6 +1908,9 @@ export type ResolversParentTypes = { String: Partial; Tenant: Partial; TenantEdge: Partial; + TenantEndpoint: Partial; + TenantEndpointEdge: Partial; + TenantEndpointsConnection: Partial; TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; @@ -2115,7 +2141,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2323,6 +2349,7 @@ export type SetFeeResponseResolvers = { createdAt?: Resolver; + endpoints?: Resolver, ParentType, ContextType>; id?: Resolver; kratosIdentityId?: Resolver; updatedAt?: Resolver; @@ -2335,6 +2362,24 @@ export type TenantEdgeResolvers; }; +export type TenantEndpointResolvers = { + type?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEndpointsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TenantsConnectionResolvers = { edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -2500,6 +2545,9 @@ export type Resolvers = { SetFeeResponse?: SetFeeResponseResolvers; Tenant?: TenantResolvers; TenantEdge?: TenantEdgeResolvers; + TenantEndpoint?: TenantEndpointResolvers; + TenantEndpointEdge?: TenantEndpointEdgeResolvers; + TenantEndpointsConnection?: TenantEndpointsConnectionResolvers; TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType;