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

Endpoints for Usage metrics #32

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions packages/models/src/billing/ap/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export enum BillingMode {
STRIPE = 'stripe',
}

const GiB = 1024 * 1024 * 1024;

const freeQuotas = {
[AccountPlan.BASIC]: GiB, // 1 GiB
[AccountPlan.PRO]: 100 * GiB, // 100 GiB
};

export interface Account {
id: string;
credits: number;
Expand All @@ -29,6 +36,7 @@ export interface Account {
billingPeriodEnd?: string;
stripeSubscriptionId?: string;
billingMode?: BillingMode;
freeUsageQuota?: number;
}

interface RawInfo {
Expand All @@ -45,6 +53,7 @@ interface RawInfo {
billingPeriodEnd?: string;
stripeSubscriptionId?: string;
billingMode?: BillingMode;
freeUsageQuota?: number;
}

interface CreateAccountInput {
Expand Down Expand Up @@ -90,6 +99,7 @@ const rawToObject = (raw: RawInfo): Account => {
return {
id: pk,
...rest,
freeUsageQuota: raw.freeUsageQuota || freeQuotas[raw.plan],
};
};

Expand Down
3 changes: 3 additions & 0 deletions services/billing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
"dependencies": {
"@packages/apitools": "^0.0.1",
"@packages/models": "^0.0.1",
"@textile/context": "^0.9.2",
"@textile/users": "^4.0.0",
"@types/coinbase-commerce-node": "^1.0.5",
"aws-lambda": "^1.0.6",
"aws-sdk": "^2.775.0",
"coinbase-commerce-node": "^1.0.4",
"lodash": "^4.17.20",
"multibase": "^3.1.0",
"stripe": "^8.97.0"
},
"devDependencies": {
Expand Down
43 changes: 36 additions & 7 deletions services/billing/serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,28 @@ provider:
Resource:
- Fn::ImportValue: 'AppDynamoDbTableArn-${self:provider.stage}'
- Fn::Join:
- ""
- - Fn::ImportValue: 'AppDynamoDbTableArn-${self:provider.stage}'
- "/*"
- ''
- - Fn::ImportValue: 'AppDynamoDbTableArn-${self:provider.stage}'
- '/*'
- Effect: Allow
Action:
- execute-api:ManageConnections
Resource:
- "arn:aws:execute-api:${self:provider.region}:${ssm:org-arn}:${self:provider.executeApi.${self:provider.stage}}/${opt:stage}/*"
- 'arn:aws:execute-api:${self:provider.region}:${ssm:org-arn}:${self:provider.executeApi.${self:provider.stage}}/${opt:stage}/*'
executeApi:
dev: 413h5xnqp8
prd: 413h5xnqp8

plugins:
- serverless-jetpack

custom:
jetpack:
base: "../../"
base: '../../'

package:
exclude:
- "**/node_modules/aws-sdk/**" # included on Lambda.
- '**/node_modules/aws-sdk/**' # included on Lambda.

functions:
createStripeSubscription:
Expand Down Expand Up @@ -86,4 +87,32 @@ functions:
method: get
path: /account
cors: true
authorizer: arn:aws:lambda:${self:provider.region}:${ssm:org-arn}:function:space-rest-${self:provider.stage}-authorizer
authorizer: arn:aws:lambda:${self:provider.region}:${ssm:org-arn}:function:space-rest-${self:provider.stage}-authorizer

getUsage:
handler: dist/usage.handler
environment:
ENV: ${self:provider.stage}
TXL_USER_KEY: ${ssm:txl-user-key-${self:provider.stage}~true}
TXL_USER_SECRET: ${ssm:txl-user-secret-${self:provider.stage}~true}
TXL_HUB_URL: ${ssm:txl-hub-url-${self:provider.stage}~true}
events:
- http:
method: get
path: /account/usage
cors: true
authorizer: arn:aws:lambda:${self:provider.region}:${ssm:org-arn}:function:space-rest-${self:provider.stage}-authorizer

getUsageHistory:
handler: dist/usageHistory.handler
environment:
ENV: ${self:provider.stage}
TXL_USER_KEY: ${ssm:txl-user-key-${self:provider.stage}~true}
TXL_USER_SECRET: ${ssm:txl-user-secret-${self:provider.stage}~true}
TXL_HUB_URL: ${ssm:txl-hub-url-${self:provider.stage}~true}
events:
- http:
method: get
path: /account/usage-history
cors: true
authorizer: arn:aws:lambda:${self:provider.region}:${ssm:org-arn}:function:space-rest-${self:provider.stage}-authorizer
41 changes: 41 additions & 0 deletions services/billing/src/usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { APIGatewayProxyResult, APIGatewayProxyEventBase } from 'aws-lambda';
import { getAggregatedUsage } from './utils/usage';

if (!process?.env?.ENV) {
throw new Error('ENV variable not set');
}

// const STAGE = process.env.ENV;
// const dbModel = createDbModel(STAGE);

export interface AuthContext {
uuid: string;
pubkey: string;
}

// eslint-disable-next-line
export const handler = async (
event: APIGatewayProxyEventBase<AuthContext>
): Promise<APIGatewayProxyResult> => {
const { pubkey } = event.requestContext.authorizer;

let storage = 0;
let bandwidth = 0;

try {
const aggregatedUsage = await getAggregatedUsage(pubkey);
bandwidth = aggregatedUsage.network_egress || 0;
storage = aggregatedUsage.stored_data || 0;
} catch (e) {
console.log('usage error', e);
// zero usage?
}

return {
statusCode: 200,
body: JSON.stringify({
storage,
bandwidth,
}),
};
};
39 changes: 39 additions & 0 deletions services/billing/src/usageHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { APIGatewayProxyResult, APIGatewayProxyEventBase } from 'aws-lambda';
// import createDbModel from '@packages/models/dist/billing/dbModel';

if (!process?.env?.ENV) {
throw new Error('ENV variable not set');
}

// const STAGE = process.env.ENV;
// const dbModel = createDbModel(STAGE);

export interface AuthContext {
uuid: string;
pubkey: string;
}

// eslint-disable-next-line
export const handler = async (
event: APIGatewayProxyEventBase<AuthContext>
): Promise<APIGatewayProxyResult> => {
const { uuid } = event.requestContext.authorizer;

console.log(uuid);

const mockData = [];
const now = Date.now();
const day = 24 * 60 * 60 * 1000;

for (let i = 0; i < 30; i += 1) {
mockData.push({
date: new Date(now - i * day).toISOString(),
usage: Math.ceil(Math.random() * 1000000000),
});
}

return {
statusCode: 200,
body: JSON.stringify(mockData),
};
};
1 change: 1 addition & 0 deletions services/billing/src/utils/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export const accountResponse = (accountWithBilling: AccountWithBilling) =>
'billingPeriodStart',
'billingPeriodEnd',
'billingMode',
'freeUsageQuota',
]);
55 changes: 55 additions & 0 deletions services/billing/src/utils/usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Usage, Users, GetUsageResponse } from '@textile/users';
import _ from 'lodash';
import multibase from 'multibase';

import { Context } from '@textile/context';

const hexToBase32 = (hex: string): string =>
Buffer.from(
multibase.encode('base32', Buffer.from(`08011220${hex}`, 'hex'))
).toString();

export const getUsage = async (pubkey: string): Promise<GetUsageResponse> => {
const ctx = new Context(process.env.TXL_HUB_URL);

const users = await Users.withKeyInfo(
{
key: process.env.TXL_USER_KEY,
secret: process.env.TXL_USER_SECRET,
},
ctx
);

const dependentUserKey = hexToBase32(pubkey);

return users.getUsage({
dependentUserKey,
});
};

export const aggregateUsageMap = (
usageMap: Array<[string, Usage]>
): Record<string, number> => {
const aggregated = _.reduce(
usageMap,
(acc: Record<string, number>, [str, usage]) => {
if (!acc[str]) {
acc[str] = 0;
}

acc[str] = usage.total;

return acc;
},
{}
);

return aggregated;
};

export const getAggregatedUsage = async (
pubkey: string
): Promise<Record<string, number>> => {
const usage = await getUsage(pubkey);
return aggregateUsageMap(usage.customer.dailyUsageMap);
};
Loading