diff --git a/src/backend/src/services/drivers/DriverService.js b/src/backend/src/services/drivers/DriverService.js index 5e9ef348..f2f15eac 100644 --- a/src/backend/src/services/drivers/DriverService.js +++ b/src/backend/src/services/drivers/DriverService.js @@ -278,6 +278,8 @@ class DriverService extends BaseService { console.log('EFFECTIVE', JSON.stringify(effective_policy, undefined, ' ')); + const method_key = `V1:${service_name}:${iface}:${method}`; + const invoker = Invoker.create({ decorators: [ { @@ -296,6 +298,35 @@ class DriverService extends BaseService { return args; }, }, + { + name: 'enforce monthly usage limit', + on_call: async args => { + if ( ! effective_policy['monthly-limit'] ) return args; + const svc_monthlyUsage = services.get('monthly-usage'); + const count = await svc_monthlyUsage.check_2( + actor, method_key + ); + if ( count >= effective_policy['monthly-limit'] ) { + throw APIError.create('monthly_limit_exceeded', null, { + method_key, + limit: effective_policy['monthly-limit'], + }); + } + return args; + }, + on_return: async result => { + console.log('monthly usage is returning'); + const svc_monthlyUsage = services.get('monthly-usage'); + const extra = { + 'driver.interface': iface, + 'driver.implementation': service_name, + 'driver.method': method, + }; + console.log('calling the increment method') + await svc_monthlyUsage.increment(actor, method_key, extra); + return result; + }, + }, { name: 'add metadata', on_return: async result => { diff --git a/src/backend/src/services/sla/MonthlyUsageService.js b/src/backend/src/services/sla/MonthlyUsageService.js index 5705ff89..1718039a 100644 --- a/src/backend/src/services/sla/MonthlyUsageService.js +++ b/src/backend/src/services/sla/MonthlyUsageService.js @@ -34,47 +34,21 @@ class MonthlyUsageService extends BaseService { const maybe_app_id = actor.type.app?.id; - if ( this.db.case({ sqlite: true, otherwise: false }) ) { - return; - } - - const vals = - [ - year, month, key, actor.type.user.id, maybe_app_id, JSON.stringify(extra), - ...this.db.case({ mysql: [JSON.stringify(extra)], otherwise: [] }), - ] - // UPSERT increment count - try { - await this.db.write( - 'INSERT INTO `service_usage_monthly` (`year`, `month`, `key`, `count`, `user_id`, `app_id`, `extra`) ' + - 'VALUES (?, ?, ?, 1, ?, ?, ?) ' + - this.db.case({ - mysql: 'ON DUPLICATE KEY UPDATE `count` = `count` + 1, `extra` = ?', - sqlite: ' ', - // sqlite: 'ON CONFLICT(`year`, `month`, `key`, `user_id`, `app_id`) ' + - // 'DO UPDATE SET `count` = `count` + 1 AND `extra` = ?', - }), - [ - year, month, key, actor.type.user.id, maybe_app_id ?? null, JSON.stringify(extra), - ...this.db.case({ mysql: [JSON.stringify(extra)], otherwise: [] }), - ] - ); - } catch (e) { - // if ( e.code !== 'SQLITE_ERROR' && e.code !== 'SQLITE_CONSTRAINT_PRIMARYKEY' ) throw e; - // The "ON CONFLICT" clause isn't currently working. - await this.db.write( - 'UPDATE `service_usage_monthly` ' + - 'SET `count` = `count` + 1, `extra` = ? ' + - 'WHERE `year` = ? AND `month` = ? AND `key` = ? ' + - 'AND `user_id` = ? AND `app_id` = ?', - [ - JSON.stringify(extra), - year, month, key, actor.type.user.id, maybe_app_id, - ] - ); - - } + await this.db.write( + 'INSERT INTO `service_usage_monthly` (`year`, `month`, `key`, `count`, `user_id`, `app_id`, `extra`) ' + + 'VALUES (?, ?, ?, 1, ?, ?, ?) ' + + this.db.case({ + mysql: 'ON DUPLICATE KEY UPDATE `count` = `count` + 1, `extra` = ?', + // sqlite: ' ', + otherwise: 'ON CONFLICT(`year`, `month`, `key`) ' + + 'DO UPDATE SET `count` = `count` + 1, `extra` = ?', + }), + [ + year, month, key, actor.type.user.id, maybe_app_id ?? null, JSON.stringify(extra), + ...this.db.case({ mysql: [JSON.stringify(extra)], otherwise: [JSON.stringify({ a: 1 })] }), + ] + ); } async check (actor, specifiers) { @@ -88,6 +62,18 @@ class MonthlyUsageService extends BaseService { } + async check_2 (actor, key) { + key = `${actor.uid}:${key}`; + if ( actor.type instanceof UserActorType ) { + return await this._user_check_2(actor, key); + } + + if ( actor.type instanceof AppUnderUserActorType ) { + return await this._app_under_user_check_2(actor, key); + } + + } + async _user_check (actor, specifiers) { const year = new Date().getUTCFullYear(); // months are zero-indexed by getUTCMonth, which could be confusing @@ -106,6 +92,36 @@ class MonthlyUsageService extends BaseService { return rows[0]?.sum || 0; } + async _user_check_2 (actor, key) { + const year = new Date().getUTCFullYear(); + // months are zero-indexed by getUTCMonth, which could be confusing + const month = new Date().getUTCMonth() + 1; + + console.log( + 'what check query?', + 'SELECT SUM(`count`) AS sum FROM `service_usage_monthly` ' + + 'WHERE `year` = ? AND `month` = ? AND `user_id` = ? ' + + 'AND `key` = ?', + [ + year, month, actor.type.user.id, + key, + ] + ); + const rows = await this.db.read( + 'SELECT SUM(`count`) AS sum FROM `service_usage_monthly` ' + + 'WHERE `year` = ? AND `month` = ? AND `user_id` = ? ' + + 'AND `key` = ?', + [ + year, month, actor.type.user.id, + key, + ] + ); + + console.log('what rows?', rows); + + return rows[0]?.sum || 0; + } + async _app_under_user_check (actor, specifiers) { const year = new Date().getUTCFullYear(); // months are zero-indexed by getUTCMonth, which could be confusing @@ -128,6 +144,27 @@ class MonthlyUsageService extends BaseService { return rows[0]?.count || 0; } + + async _app_under_user_check_2 (actor, key) { + const year = new Date().getUTCFullYear(); + // months are zero-indexed by getUTCMonth, which could be confusing + const month = new Date().getUTCMonth() + 1; + + // SELECT count + const rows = await this.db.read( + 'SELECT `count` FROM `service_usage_monthly` ' + + 'WHERE `year` = ? AND `month` = ? AND `user_id` = ? ' + + 'AND `app_id` = ? ' + + 'AND `key` = ?', + [ + year, month, actor.type.user.id, + actor.type.app.id, + key, + ] + ); + + return rows[0]?.count || 0; + } } module.exports = {