Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): tenants service (#3123) #3140

Merged
merged 10 commits into from
Dec 9, 2024

Conversation

njlie
Copy link
Contributor

@njlie njlie commented Nov 27, 2024

Changes proposed in this pull request

  • Adds a TenantService to the backend IoC container
  • Adds updatedAt and createdAt in the backend tenants table.

Context

Closes #3123.

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Make sure that all checks pass
  • Bruno collection updated (if necessary)
  • Documentation issue created with user-docs label (if necessary)
  • OpenAPI specs updated (if necessary)

@github-actions github-actions bot added type: tests Testing related pkg: backend Changes in the backend package. type: source Changes business logic labels Nov 27, 2024
Copy link
Collaborator

@golobitch golobitch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks ok. Me personally, I would like to see method getPage to be implemented, that would be used for getting list of tenants

@@ -8,6 +8,9 @@ exports.up = function (knex) {
table.string('email').notNullable()
table.string('publicName')
table.string('apiSecret')

table.timestamp('createdAt').defaultTo(knex.fn.now())
table.timestamp('updatedAt').defaultTo(knex.fn.now())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also deletedAt ? So we would leverage soft delete. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me

const apolloSpy = jest.spyOn(apolloClient, 'query')
const tenant = await tenantService.get(createdTenant.id)
assert.ok(tenant)
expect(tenant.id).toEqual(createdTenant.id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just

expect(tenant).toEqual(createdTenant)

? Don't know if this works :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks because the updatedAt for one will always be different from the other, sadly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would updatedAt field be different ? You are creating a tenant and getting back data, and then you are querying the exact same entity ... so it should be the same one? Or am I missing something here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind that, I conflated that result with a different test. This will work, but createdTenant just needs to be augmented with the idpSecret and idpConsentUrl.

packages/backend/src/tenants/service.test.ts Show resolved Hide resolved
}

// TODO: add type to this in https://github.com/interledger/rafiki/issues/3125
await deps.apolloClient.mutate({ mutation, variables })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does mutate method throw error in case of 4xx / 5xx errors ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@BlairCurrey BlairCurrey Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is this will be a new gql server for service-to-service communication so I guess maybe we can simply return a 400 from that server like this expects, but I just wanted to note that our current resolvers dont behave this way.

For errors we do something like throw new GraphQLError('tenant not found', { extensions: { code: 'NOT_FOUND' } }). I tested an existing one of these on Bruno and see the gql server replies with a 200 and error details in the body like:

{
  "errors": [
    {
      "message": "No grant",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "grant"
      ],
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ],
  "data": null
}

Im not necessarily against doing it a different way, if that seems better. I just wanted to note this.

My assumption is the apolloClient doesnt parse this and throw. Even if it did the behavior should be
correct but the mock would need to be updated still. If the client doesnt do this I think the test would incorrectly pass if we updated the mock to something like the above response because the expects in the catch would never be called. We safeguard against this sort of incorrect pass elsewhere by doing expect.assertions(n). I also wondered if apolloClient parses that sort of 200 response and throws but in any case the test mock would need to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out. I updated the nocks to return proper error structure. Mercifully, the client still parses that as an error and throws, but it's a better test to have the correct response from the API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mercifully, the client still parses that as an error and throws

Good to know, was curious about that

Comment on lines 24 to 29
export type TenantWithIdpConfig = Pick<
Tenant,
'id' | 'email' | 'apiSecret' | 'publicName' | 'createdAt' | 'updatedAt'
> & {
idpConsentUrl: string
idpSecret: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need idpConsentUrl and idpSecret in backend?

Copy link
Contributor

@mkurapov mkurapov Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this in backend, so that with one call, we add tenant data on both backend, and auth service.

I'm guessing you are referring to this comment @golobitch?

Also, I see, we are not storing the idpConsentUrl and idpSecret in the tenants table, (TenantWithIdpConfig is just a type). At first, not storing the data makes sense to me (we shouldn't care about IDP stuff in backend). However, how would that work for doing pagination over tenants? If we don't store these, then we would need to call getTenant in the auth service for every tenant result

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, @njlie, @mkurapov has a point here :)

Copy link
Contributor Author

@njlie njlie Dec 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could treat the backend's idpConsentUrl and idpSecret as some sort of cache-like thing. As in for pagination, we return the backend's entries for each tenant row, but in the query for an individual tenant, make the call to the authorization server. Whenever a tenant is mutated such that the IDP fields are changed, it would update the columns in both backend and auth.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but in the query for an individual tenant, make the call to the authorization server

The fields should basically always be the same, so I think calling to the AS isn't needed. The updates shouldn't happen frequently, and if they do, they'll be stored in the backend tenants table anyway as you describe

@@ -31,6 +31,8 @@ services:
PRIVATE_KEY_FILE: /workspace/private-key.pem
AUTH_SERVER_INTROSPECTION_URL: http://cloud-nine-wallet-test-auth:3107
AUTH_SERVER_GRANT_URL: http://cloud-nine-wallet-test-auth:3106
AUTH_ADMIN_API_URL: 'http://cloud-nine-wallet-test-auth:3003/graphql'
AUTH_ADMIN_API_SECRET: 'test-secret'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to correspond to auth's ADMIN_API_SECRET? Or will this be a separate API altogether?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mkurapov I would say that on both of your comments the answer is affirmitive. We need this in backend, so that with one call, we add tenant data on both backend, and auth service.

@golobitch golobitch requested a review from mkurapov November 29, 2024 20:25
@njlie njlie force-pushed the nl/3123/backend-tenant-service branch from 68f523a to 3945c35 Compare December 2, 2024 18:24
@njlie njlie changed the title feat(backend): tenants table v1 (#3132) feat(backend): tenants service (#3132) Dec 2, 2024
@njlie njlie changed the title feat(backend): tenants service (#3132) feat(backend): tenants service (#3123) Dec 2, 2024
@njlie njlie linked an issue Dec 2, 2024 that may be closed by this pull request
5 tasks
@njlie njlie force-pushed the nl/3123/backend-tenant-service branch from 3945c35 to fb3d702 Compare December 2, 2024 19:35
Comment on lines 191 to 193
await Tenant.query(trx).patchAndFetchById(id, {
deletedAt: new Date(Date.now())
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can pass through the deletedAt to the mutation, so both tables in backend and auth have the same value

const mutation = gql`
mutation DeleteAuthTenantMutation($input: DeleteTenantInput!) {
deleteTenant(input: $input) {
sucess
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this must be success

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a basic response, but this could be changed in the PR for the auth graphql API if it is necessary.

@njlie njlie requested review from sanducb and golobitch December 3, 2024 22:37
@njlie njlie requested a review from mkurapov December 3, 2024 22:37
Copy link
Collaborator

@koekiebox koekiebox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 🟢

@@ -6,8 +6,14 @@ exports.up = function (knex) {
return knex.schema.createTable('tenants', function (table) {
table.uuid('id').notNullable().primary()
table.string('email').notNullable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets have email nullable, based on our slack convo?

Comment on lines 10 to 11
table.string('idpConsentUrl').notNullable()
table.string('idpSecret').notNullable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think idpConsentUrl and idpSecret can be null, integrators may not actually be integrated with an IDP anyway. (echoing @tadejgolobic on our Tues call)

@njlie njlie requested review from mkurapov and BlairCurrey December 6, 2024 22:53
@njlie njlie merged commit 07630c1 into 2893/multi-tenancy-v1 Dec 9, 2024
24 of 36 checks passed
@njlie njlie deleted the nl/3123/backend-tenant-service branch December 9, 2024 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: backend Changes in the backend package. type: source Changes business logic type: tests Testing related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Multi-Tenant] backend tenant service
6 participants