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

Report usage on webhook requests #10

Merged
merged 6 commits into from
Dec 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Report usage on webhook requests
  • Loading branch information
Rafael Leite committed Dec 26, 2022
commit 36b5798fd16595f94d99088bb7b7b340e5a1862f
22 changes: 20 additions & 2 deletions api/app/src/command/connected-user/get-connected-user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import NotFoundError from "../../errors/not-found";
import { ConnectedUser } from "../../models/connected-user";
import { ProviderOptions } from "../../shared/constants";
import { AtLeastOne } from "../../shared/types";

export const getConnectedUser = async ({
id,
Expand Down Expand Up @@ -29,10 +30,27 @@ export const getConnectedUserOrFail = async ({
return connectedUser;
};

export const getProviderDataFromConnectUserOrFail = (connectedUser: ConnectedUser, provider: ProviderOptions) => {
export const getProviderDataFromConnectUserOrFail = (
connectedUser: ConnectedUser,
provider: ProviderOptions
) => {
if (!connectedUser.providerMap) throw new NotFoundError();
const providerData = connectedUser.providerMap[provider];
if (!providerData) throw new NotFoundError();

return providerData;
}
};

export const getConnectedUsers = async ({
ids,
cxId,
}: AtLeastOne<
Pick<ConnectedUser, "cxId"> & { ids: ConnectedUser["id"][] }
>): Promise<ConnectedUser[]> => {
return ConnectedUser.findAll({
where: {
...(ids ? { id: ids } : undefined),
cxId,
},
});
};
21 changes: 21 additions & 0 deletions api/app/src/command/usage/report-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Axios from "axios";
import { Config } from "../../shared/config";

const axios = Axios.create();

export type ReportUsageCommand = {
cxId: string;
cxUserId: string;
};

export const reportUsage = async ({
cxId,
cxUserId,
}: ReportUsageCommand): Promise<void> => {
const url = Config.getUsageUrl();
if (!url) return;

const payload = { cxId, cxUserId };

await axios.post(url, payload, { timeout: 1_000 });
};
15 changes: 15 additions & 0 deletions api/app/src/command/webhook/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import WebhookError from "../../errors/webhook";
import { DataType, TypedData, UserData } from "../../mappings/garmin";
import { Settings, WEBHOOK_STATUS_OK } from "../../models/settings";
import { Util } from "../../shared/util";
import { getConnectedUsers } from "../connected-user/get-connected-user";
import { getUserTokenByUAT } from "../cx-user/get-user-token";
import { getSettingsOrFail } from "../settings/getSettings";
import { updateWebhookStatus } from "../settings/updateSettings";
import { reportUsage as reportUsageCmd } from "../usage/report-usage";
import {
createWebhookRequest,
updateWebhookRequestStatus,
Expand Down Expand Up @@ -111,6 +113,10 @@ export const processData = async <T extends MetriportData>(
// now that we have a all the chunks for one customer, process them
const settings = await getSettingsOrFail({ id: cxId });
await processOneCustomer(cxId, settings, payloads);
await reportUsage(
cxId,
dataAndUserList.map((du) => du.userId)
);
} catch (err) {
const msg = getErrorMessage(err);
log(`Failed to process data of customer ${cxId}: ${msg}`);
Expand All @@ -122,6 +128,15 @@ export const processData = async <T extends MetriportData>(
}
};

const reportUsage = async (cxId: string, userIds: string[]): Promise<void> => {
const users = await getConnectedUsers({ cxId, ids: userIds });
users.forEach(({ cxUserId }) => [
reportUsageCmd({ cxId, cxUserId }).catch((err) => {
log(`Failed to report usage, cxId ${cxId}, cxUserId ${cxUserId}`, err);
}),
]);
};

leite08 marked this conversation as resolved.
Show resolved Hide resolved
// const processOneCustomer = async <T extends MetriportData>(
const processOneCustomer = async (
cxId: string,
Expand Down
41 changes: 12 additions & 29 deletions api/app/src/routes/middlewares/usage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { NextFunction, Request } from "express";
import { Config } from "../../shared/config";
import { reportUsage as reportUsageCmd } from "../../command/usage/report-usage";
import { Util } from "../../shared/util";
import { getCxId, getUserId } from "../util";
import Axios from "axios";

const axios = Axios.create();
const log = Util.log("USAGE");

/**
Expand Down Expand Up @@ -32,34 +30,19 @@ export const reportUsage = async (
*/
const reportIt = async (req: Request): Promise<void> => {
try {
const url = Config.getUsageUrl();
if (!url) return;

const data = await getData(req);
if (!data) return;

await axios.post(url, data, { timeout: 500 });
const cxId = getCxId(req);
if (!cxId) {
log(`Skipped, missing cxId`);
return;
}
const cxUserId = getUserId(req);
if (!cxUserId) {
log(`Skipped, missing cxUserId (cxId ${cxId})`);
return;
}
await reportUsageCmd({ cxId, cxUserId });
} catch (err) {
console.log(err);
// intentionally failing silently
}
};

const getData = async (
req: Request
): Promise<{
cxId: string;
cxUserId: string;
} | void> => {
const cxId = getCxId(req);
if (!cxId) {
log(`Skipped, missing cxId`);
return;
}
const cxUserId = getUserId(req);
if (!cxUserId) {
log(`Skipped, missing cxUserId (cxId ${cxId})`);
return;
}
return { cxId, cxUserId };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Migration } from "..";
import { ConnectedUser } from "../../models/connected-user";

// Use 'Promise.all' when changes are independent of each other
// Docs: https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface
export const up: Migration = async ({ context: queryInterface }) => {
return queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.addIndex(ConnectedUser.NAME, ["cx_id"], {
transaction,
});
leite08 marked this conversation as resolved.
Show resolved Hide resolved
});
};

export const down: Migration = ({ context: queryInterface }) => {
return queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.removeIndex(ConnectedUser.NAME, ["cx_id"], {
transaction,
});
});
};
2 changes: 2 additions & 0 deletions api/app/src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> &
U[keyof U];