Triggering & scheduling tasks per user?

I’m trying to understand the right way to connect and schedule tasks – per user.

The specific use case I’d like to support is -

  1. user connects their 3rd party CRM account to my application via OAuth
  2. I save an auth token for them that is good for a limited amount of time (e.g. 6 hours)
  3. when I save a token for a user, I also setup a function to run later to renew this token (e.g. less than 6 hours)
  4. when new token is saved, a new function is scheduled to renew later, and process repeats to keep active auth tokens available to perform auth’d calls to user’s CRM account

Does anyone have suggestions on a smart way to connect 8base trigger and/or task functions to accomplish something like this?

Thanks

Hello Clayton!

Our developer has been considering your issue. I’ll give you a full answer soon.

Lada Kokotova | Technical Support Engineer

1 Like

Hello Clayton!

8base.yml

functions:
      dispatchRenew:
        type: task
        schedule: cron(1 0 * * ? *)
        handler:
          code: src/cron/dispatchRenew/handler.ts
      renewToken:
        type: task
        handler:
          code: src/tasks/renewToken/handler.ts

dispatchRenew

import { FunctionContext, FunctionEvent } from '8base-cli-types';

export default async (
  event: FunctionEvent<unknown>,
  ctx: FunctionContext,
): Promise<void> => {
  // choose users to dispatch renew token
  const users = await getUsersByFilter(ctx, {
     filter: { any: { conditions: ... } },
  });

  for (const user of users) {
    await ctx.invokeFunction(
      'renewToken',
      { data: { userId: user.id } },
      { waitForResponse: false }, // dispatch in async mode
    );
  }
};

renewToken

import { FunctionContext, FunctionEvent } from '8base-cli-types';

type EventData = {
  userId: string;
};

export default async (
  event: FunctionEvent<EventData>,
  ctx: FunctionContext,
): Promise<void> => {
  const { userId } = event.data;

  const user = await getUsersById(ctx, userId);
  
  const accessToken = await service.renewToken(ctx, user.refreshToken);

  await updateUser({
    ...user,
    accessToken,
  });
};

Lada Kokotova | Technical Support Engineer

1 Like

Lada, thank you and the team for this level of detail, I truly appreciate it. :raised_hands:

I’ll review this more closely with the documenation and follow up if anything new comes up.

Thanks again.

We so appreciate you taking the time to provide feedback!
Feel welcome to reach out to us with any questions you may have and we would be more than happy to help you again.

Best,
Lada Kokotova | Technical Support Engineer

Hi Lada, I’m trying to better understand a couple of pieces of code from the ‘renewToken’ function example –

const user = await getUsersById(ctx, userId);

...

await updateUser({
    ...user,
    accessToken,
  });

Where are getUsersById and updateUser defined? Are these functions/methods provided already by the platform (e.g. built in graphQL query methods) or would one need to create and connect these manually elsewhere?

const accessToken = await service.renewToken(ctx, user.refreshToken); 

It looks like service.renewToken and user.refreshToken are both methods from the Auth Module, right? Would this still be the right approach/tool to use if I’m having to manually connect my app with a user’s CRM account via OAuth (i.e. they are not manually creating an account or signing in to my app directly.) I believe my process will look like this -

  1. user starts OAuth process at CRM
  2. CRM passes the user to my webhook with a code
  3. I exchange this code for auth tokens to use for CRM API calls
  4. I perform the token renewal process to refresh tokens before they expire X hours later

Hello!

Nice to see you again, Clayton!
I need to discuss you issue with our developers. I’ll let you know soon.

Lada Kokotova | Technical Support Engineer

Hello Clayton!

GetUsersById and updateUser - is an example of your abstraction over GQL, here is an example

export async function getUserById(ctx: FunctionContext, id: string): Promise<User | null> {
  const response = await ctx.api.gqlRequest(
    USER_ENTITY_QUERY, // gql `query User { user { id, name } }`
    {
      id,
    },
    {
      checkPermissions: false,
    },
  );

  return R.pathOr(null, ['user'], response);
}

The same thing for updateUser - it’s a mutation
If you need integration for different users:

  1. the user starts auth0
  2. in webhook you create new integration with accessToken and other tokens (if need)
  3. for all API calls you to use this token
  • token refresh running by cron, you select tokens for renewing by expire filter

Lada Kokotova | Technical Support Engineer

1 Like

Thank you Lada.

So just to be clear, these abstractions are functions I’d create manually? The example you show is not something that is automatically being created by the platform as part of the auto-generated GraphQL queries and mutations, right?

Yes, you’re right.

Lada Kokotova | Technical Support Engineer

1 Like