|
|
|
@ -1,41 +1,9 @@ |
|
|
|
const env = require("../environment") |
|
|
|
const { apiKeyTable } = require("../db/dynamoClient") |
|
|
|
|
|
|
|
const DEFAULT_USAGE = { |
|
|
|
rows: 0, |
|
|
|
storage: 0, |
|
|
|
views: 0, |
|
|
|
automationRuns: 0, |
|
|
|
users: 0, |
|
|
|
} |
|
|
|
|
|
|
|
const DEFAULT_PLAN = { |
|
|
|
rows: 1000, |
|
|
|
// 1 GB
|
|
|
|
storage: 8589934592, |
|
|
|
views: 10, |
|
|
|
automationRuns: 100, |
|
|
|
users: 10000, |
|
|
|
} |
|
|
|
|
|
|
|
function buildUpdateParams(key, property, usage) { |
|
|
|
return { |
|
|
|
primary: key, |
|
|
|
condition: |
|
|
|
"attribute_exists(#quota) AND attribute_exists(#limits) AND #quota.#prop < #limits.#prop AND #quotaReset > :now", |
|
|
|
expression: "ADD #quota.#prop :usage", |
|
|
|
names: { |
|
|
|
"#quota": "usageQuota", |
|
|
|
"#prop": property, |
|
|
|
"#limits": "usageLimits", |
|
|
|
"#quotaReset": "quotaReset", |
|
|
|
}, |
|
|
|
values: { |
|
|
|
":usage": usage, |
|
|
|
":now": Date.now(), |
|
|
|
}, |
|
|
|
} |
|
|
|
} |
|
|
|
const { getGlobalDB } = require("@budibase/auth/tenancy") |
|
|
|
const { |
|
|
|
StaticDatabases, |
|
|
|
generateNewUsageQuotaDoc, |
|
|
|
} = require("@budibase/auth/db") |
|
|
|
|
|
|
|
function getNewQuotaReset() { |
|
|
|
return Date.now() + 2592000000 |
|
|
|
@ -47,59 +15,59 @@ exports.Properties = { |
|
|
|
VIEW: "views", |
|
|
|
USER: "users", |
|
|
|
AUTOMATION: "automationRuns", |
|
|
|
APPS: "apps", |
|
|
|
EMAILS: "emails", |
|
|
|
} |
|
|
|
|
|
|
|
exports.getAPIKey = async appId => { |
|
|
|
if (!env.USE_QUOTAS) { |
|
|
|
return { apiKey: null } |
|
|
|
async function getUsageQuotaDoc(db) { |
|
|
|
let quota |
|
|
|
try { |
|
|
|
quota = await db.get(StaticDatabases.PLATFORM_INFO.docs.usageQuota) |
|
|
|
} catch (err) { |
|
|
|
// doc doesn't exist. Create it
|
|
|
|
quota = await db.post(generateNewUsageQuotaDoc()) |
|
|
|
} |
|
|
|
return apiKeyTable.get({ primary: appId }) |
|
|
|
|
|
|
|
return quota |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Given a specified API key this will add to the usage object for the specified property. |
|
|
|
* @param {string} apiKey The API key which is to be updated. |
|
|
|
* Given a specified tenantId this will add to the usage object for the specified property. |
|
|
|
* @param {string} property The property which is to be added to (within the nested usageQuota object). |
|
|
|
* @param {number} usage The amount (this can be negative) to adjust the number by. |
|
|
|
* @returns {Promise<void>} When this completes the API key will now be up to date - the quota period may have |
|
|
|
* also been reset after this call. |
|
|
|
*/ |
|
|
|
exports.update = async (apiKey, property, usage) => { |
|
|
|
exports.update = async (property, usage) => { |
|
|
|
if (!env.USE_QUOTAS) { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
await apiKeyTable.update(buildUpdateParams(apiKey, property, usage)) |
|
|
|
} catch (err) { |
|
|
|
// conditional check means the condition failed, need to check why
|
|
|
|
if (err.code === "ConditionalCheckFailedException") { |
|
|
|
// get the API key so we can check it
|
|
|
|
const keyObj = await apiKeyTable.get({ primary: apiKey }) |
|
|
|
// the usage quota or usage limits didn't exist
|
|
|
|
if (keyObj && (keyObj.usageQuota == null || keyObj.usageLimits == null)) { |
|
|
|
keyObj.usageQuota = |
|
|
|
keyObj.usageQuota == null ? DEFAULT_USAGE : keyObj.usageQuota |
|
|
|
keyObj.usageLimits = |
|
|
|
keyObj.usageLimits == null ? DEFAULT_PLAN : keyObj.usageLimits |
|
|
|
keyObj.quotaReset = getNewQuotaReset() |
|
|
|
await apiKeyTable.put({ item: keyObj }) |
|
|
|
return |
|
|
|
} |
|
|
|
// we have in fact breached the reset period
|
|
|
|
else if (keyObj && keyObj.quotaReset <= Date.now()) { |
|
|
|
// update the quota reset period and reset the values for all properties
|
|
|
|
keyObj.quotaReset = getNewQuotaReset() |
|
|
|
for (let prop of Object.keys(keyObj.usageQuota)) { |
|
|
|
if (prop === property) { |
|
|
|
keyObj.usageQuota[prop] = usage > 0 ? usage : 0 |
|
|
|
} else { |
|
|
|
keyObj.usageQuota[prop] = 0 |
|
|
|
} |
|
|
|
} |
|
|
|
await apiKeyTable.put({ item: keyObj }) |
|
|
|
return |
|
|
|
const db = getGlobalDB() |
|
|
|
const quota = await getUsageQuotaDoc(db) |
|
|
|
|
|
|
|
// Check if the quota needs reset
|
|
|
|
if (Date.now() >= quota.quotaReset) { |
|
|
|
quota.quotaReset = getNewQuotaReset() |
|
|
|
for (let prop of Object.keys(quota.usageQuota)) { |
|
|
|
quota.usageQuota[prop] = 0 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// increment the quota
|
|
|
|
quota.usageQuota[property] += usage |
|
|
|
|
|
|
|
if (quota.usageQuota[property] >= quota.usageLimits[property]) { |
|
|
|
throw new Error( |
|
|
|
`You have exceeded your usage quota of ${quota.usageLimits[property]} ${property}.` |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// update the usage quotas
|
|
|
|
await db.put(quota) |
|
|
|
} catch (err) { |
|
|
|
console.error(`Error updating usage quotas for ${property}`, err) |
|
|
|
throw err |
|
|
|
} |
|
|
|
} |
|
|
|
|