2020-10-06 20:13:41 +02:00
|
|
|
const CouchDB = require("../db")
|
2020-10-07 18:56:47 +02:00
|
|
|
const usageQuota = require("../utilities/usageQuota")
|
2022-01-17 19:07:26 +01:00
|
|
|
const { getUniqueRows } = require("../utilities/usageQuota/rows")
|
2021-11-05 15:59:28 +01:00
|
|
|
const {
|
|
|
|
isExternalTable,
|
|
|
|
isRowId: isExternalRowId,
|
|
|
|
} = require("../integrations/utils")
|
2022-01-11 19:38:18 +01:00
|
|
|
const quotaMigration = require("../migrations/sync_app_and_reset_rows_quotas")
|
2021-09-30 11:17:25 +02:00
|
|
|
|
2020-10-06 20:13:41 +02:00
|
|
|
// currently only counting new writes and deletes
|
|
|
|
const METHOD_MAP = {
|
|
|
|
POST: 1,
|
|
|
|
DELETE: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
const DOMAIN_MAP = {
|
2022-01-17 13:44:53 +01:00
|
|
|
rows: usageQuota.Properties.ROW,
|
2022-01-11 18:49:42 +01:00
|
|
|
// upload: usageQuota.Properties.UPLOAD, // doesn't work yet
|
|
|
|
// views: usageQuota.Properties.VIEW, // doesn't work yet
|
|
|
|
// users: usageQuota.Properties.USER, // doesn't work yet
|
2021-09-23 23:40:14 +02:00
|
|
|
applications: usageQuota.Properties.APPS,
|
2020-10-07 18:56:47 +02:00
|
|
|
// this will not be updated by endpoint calls
|
2021-09-06 18:53:02 +02:00
|
|
|
// instead it will be updated by triggerInfo
|
2022-01-11 18:49:42 +01:00
|
|
|
// automationRuns: usageQuota.Properties.AUTOMATION, // doesn't work yet
|
2020-10-06 20:13:41 +02:00
|
|
|
}
|
|
|
|
|
2020-10-07 18:56:47 +02:00
|
|
|
function getProperty(url) {
|
|
|
|
for (let domain of Object.keys(DOMAIN_MAP)) {
|
|
|
|
if (url.indexOf(domain) !== -1) {
|
|
|
|
return DOMAIN_MAP[domain]
|
|
|
|
}
|
2020-10-06 20:13:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = async (ctx, next) => {
|
2022-01-17 13:44:53 +01:00
|
|
|
if (!usageQuota.useQuotas()) {
|
2021-09-27 16:03:48 +02:00
|
|
|
return next()
|
|
|
|
}
|
2021-04-13 18:11:55 +02:00
|
|
|
|
2020-10-07 18:56:47 +02:00
|
|
|
let usage = METHOD_MAP[ctx.req.method]
|
|
|
|
const property = getProperty(ctx.req.url)
|
2020-10-06 20:13:41 +02:00
|
|
|
if (usage == null || property == null) {
|
|
|
|
return next()
|
|
|
|
}
|
|
|
|
// post request could be a save of a pre-existing entry
|
2021-04-13 18:11:55 +02:00
|
|
|
if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) {
|
2021-11-05 15:59:28 +01:00
|
|
|
const usageId = ctx.request.body._id
|
2020-10-06 20:13:41 +02:00
|
|
|
try {
|
2021-09-30 18:35:09 +02:00
|
|
|
if (ctx.appId) {
|
|
|
|
const db = new CouchDB(ctx.appId)
|
2021-11-05 15:59:28 +01:00
|
|
|
await db.get(usageId)
|
2021-09-30 18:35:09 +02:00
|
|
|
}
|
2020-10-06 20:13:41 +02:00
|
|
|
return next()
|
|
|
|
} catch (err) {
|
2021-11-05 15:59:28 +01:00
|
|
|
if (
|
|
|
|
isExternalTable(usageId) ||
|
|
|
|
(ctx.request.body.tableId &&
|
|
|
|
isExternalTable(ctx.request.body.tableId)) ||
|
|
|
|
isExternalRowId(usageId)
|
|
|
|
) {
|
|
|
|
return next()
|
|
|
|
} else {
|
|
|
|
ctx.throw(404, `${usageId} does not exist`)
|
|
|
|
}
|
2020-10-06 20:13:41 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-09 16:13:14 +01:00
|
|
|
|
2020-10-07 18:56:47 +02:00
|
|
|
// update usage for uploads to be the total size
|
2021-09-27 15:57:22 +02:00
|
|
|
if (property === usageQuota.Properties.UPLOAD) {
|
|
|
|
const files =
|
|
|
|
ctx.request.files.file.length > 1
|
|
|
|
? Array.from(ctx.request.files.file)
|
|
|
|
: [ctx.request.files.file]
|
|
|
|
usage = files.map(file => file.size).reduce((total, size) => total + size)
|
|
|
|
}
|
2020-10-06 20:13:41 +02:00
|
|
|
try {
|
2022-01-11 19:38:18 +01:00
|
|
|
await quotaMigration.runIfRequired()
|
2022-01-17 19:07:26 +01:00
|
|
|
await performRequest(ctx, next, property, usage)
|
2020-10-06 20:13:41 +02:00
|
|
|
} catch (err) {
|
2021-09-23 23:40:14 +02:00
|
|
|
ctx.throw(400, err)
|
2020-10-06 20:13:41 +02:00
|
|
|
}
|
2020-10-07 18:56:47 +02:00
|
|
|
}
|
2022-01-17 19:07:26 +01:00
|
|
|
|
|
|
|
const performRequest = async (ctx, next, property, usage) => {
|
|
|
|
const usageContext = {
|
|
|
|
skipNext: false,
|
|
|
|
skipUsage: false,
|
|
|
|
[usageQuota.Properties.APPS]: {},
|
|
|
|
}
|
|
|
|
|
|
|
|
if (usage === -1) {
|
|
|
|
if (PRE_DELETE[property]) {
|
|
|
|
await PRE_DELETE[property](ctx, usageContext)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (PRE_CREATE[property]) {
|
|
|
|
await PRE_CREATE[property](ctx, usageContext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run the request
|
|
|
|
if (!usageContext.skipNext) {
|
|
|
|
await usageQuota.update(property, usage, { dryRun: true })
|
|
|
|
await next()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (usage === -1) {
|
|
|
|
if (POST_DELETE[property]) {
|
|
|
|
await POST_DELETE[property](ctx, usageContext)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (POST_CREATE[property]) {
|
|
|
|
await POST_CREATE[property](ctx, usageContext)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the usage
|
|
|
|
if (!usageContext.skipUsage) {
|
|
|
|
await usageQuota.update(property, usage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const appPreDelete = async (ctx, usageContext) => {
|
|
|
|
if (ctx.query.unpublish) {
|
|
|
|
// don't run usage decrement for unpublish
|
|
|
|
usageContext.skipUsage = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// store the row count to delete
|
|
|
|
const rows = await getUniqueRows([ctx.appId])
|
2022-01-18 13:48:54 +01:00
|
|
|
if (rows.length) {
|
|
|
|
usageContext[usageQuota.Properties.APPS] = { rowCount: rows.length }
|
2022-01-17 19:07:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const appPostDelete = async (ctx, usageContext) => {
|
|
|
|
// delete the app rows from usage
|
|
|
|
const rowCount = usageContext[usageQuota.Properties.APPS].rowCount
|
|
|
|
if (rowCount) {
|
|
|
|
await usageQuota.update(usageQuota.Properties.ROW, -rowCount)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const PRE_DELETE = {
|
|
|
|
[usageQuota.Properties.APPS]: appPreDelete,
|
|
|
|
}
|
|
|
|
|
|
|
|
const POST_DELETE = {
|
|
|
|
[usageQuota.Properties.APPS]: appPostDelete,
|
|
|
|
}
|
|
|
|
|
|
|
|
const PRE_CREATE = {}
|
|
|
|
|
|
|
|
const POST_CREATE = {}
|