import { Map } from "immutable" ; import { isEqual } from "lodash" ; import { PostgreSQL } from "../types" ; import { query } from "../query" ; import { TypedMap } from "@cocalc/util/types/typed-map" ; import { is_valid_uuid_string , len } from "@cocalc/util/misc" ; import { callback2 } from "@cocalc/util/async-utils" ; import { number_of_running_projects_using_license } from "./analytics" ; import { Quota } from "@cocalc/util/db-schema/site-licenses" ; type QuotaMap = TypedMap < Quota > ; import { quota as compute_total_quota , SiteLicenseQuotaSetting , QuotaSetting , SiteLicenses , } from "@cocalc/util/upgrades/quota" ; let licenses = undefined ; interface License { id : string ; title ? : string ; expires ? : Date ; activates ? : Date ; upgrades ? : Map < string , number > ; quota ? : QuotaMap ; run_limit ? : number ; } type LicenseMap = TypedMap < License > ; async function get_valid_licenses ( db ) { if ( licenses == null ) { licenses = await callback2 ( db . synctable . bind ( db ) , { table : "site_licenses" , columns : [ "title" , "expires" , "activates" , "upgrades" , "quota" , "run_limit" , ] , } ) ; } return licenses . get ( ) ; } export async function site_license_hook ( db , project_id ) { try { await site_license_hook0 ( db , project_id ) ; } catch ( err ) { db . _dbg ( "site_license_hook" ) ( `ERROR -- ${ err }` ) ; throw err ; } } async function site_license_hook0 ( db , project_id ) { const dbg = db . _dbg ( `site_license_hook("${ project_id }")` ) ; dbg ( "site_license_hook -- checking for site license" ) ; const project = await query ( { db , select : [ "site_license" , "settings" , "users" ] , table : "projects" , where : { project_id } , one : true , } ) ; dbg ( `project=${ JSON . stringify ( project ) }` ) ; if ( project . site_license == null || typeof project . site_license != "object" ) { dbg ( "no site licenses set for this project." ) ; return ; } const site_license = project . site_license ; const new_site_license = { } ; const licenses = await get_valid_licenses ( db ) ; for ( const license_id in site_license ) { if ( ! is_valid_uuid_string ( license_id ) ) { dbg ( `skipping invalid license ${ license_id } -- invalid UUID` ) ; continue ; } const license = licenses . get ( license_id ) ; dbg ( `considering license ${ license_id }: ${ JSON . stringify ( license ?. toJS ( ) ) }` ) ; let is_valid ; if ( license == null ) { dbg ( `License "${ license_id }" does not exist.` ) ; is_valid = false ; } else { const expires = license . get ( "expires" ) ; const activates = license . get ( "activates" ) ; const run_limit = license . get ( "run_limit" ) ; if ( expires != null && expires <= new Date ( ) ) { dbg ( `License "${ license_id }" expired ${ expires }.` ) ; is_valid = false ; } else if ( activates == null || activates > new Date ( ) ) { dbg ( `License "${ license_id }" has not been explicitly activated yet ${ activates }.` ) ; is_valid = false ; } else if ( run_limit && ( await number_of_running_projects_using_license ( db , license_id ) ) >= run_limit ) { dbg ( `License "${ license_id }" won't be applied since it would exceed the run limit ${ run_limit }.` ) ; is_valid = false ; } else { dbg ( `license ${ license_id } is valid` ) ; is_valid = true ; } } if ( is_valid ) { if ( license == null ) throw Error ( "bug" ) ; const upgrades = ( license . get ( "upgrades" ) ?. toJS ( ) ?? { } ) as QuotaSetting ; if ( upgrades == null ) { throw Error ( "bug" ) ; } const quota = license . get ( "quota" ) ; if ( quota ) { upgrades [ "quota" ] = quota . toJS ( ) as SiteLicenseQuotaSetting ; } for ( const field in upgrades ) { if ( ! upgrades [ field ] ) { delete upgrades [ field ] ; } } dbg ( "computing run quotas..." ) ; const run_quota = compute_total_quota ( project . settings , project . users , new_site_license ) ; const run_quota_with_license = compute_total_quota ( project . settings , project . users , { ... new_site_license , ... { [ license_id ] : upgrades } , } ) ; dbg ( `run_quota=${ JSON . stringify ( run_quota ) }` ) ; dbg ( `run_quota_with_license=${ JSON . stringify ( run_quota_with_license ) }` ) ; if ( ! isEqual ( run_quota , run_quota_with_license ) ) { dbg ( `Found a valid license "${ license_id }".  Upgrade using it to ${ JSON . stringify ( upgrades ) }.` ) ; new_site_license [ license_id ] = upgrades ; } else { dbg ( `Found a valid license "${ license_id }", but it provides nothing new so not using it.` ) ; } } else { dbg ( `Not currently valid license -- "${ license_id }".  So no upgrade contributions from this license.` ) ; new_site_license [ license_id ] = { } as any ; } } if ( ! isEqual ( site_license , new_site_license ) ) { dbg ( `setup site license=${ JSON . stringify ( new_site_license ) }` ) ; await query ( { db , query : "UPDATE projects" , where : { project_id } , jsonb_set : { site_license : new_site_license } , } ) ; } else { dbg ( "no change" ) ; } for ( const license_id in new_site_license ) { if ( len ( new_site_license [ license_id ] ) > 0 ) { await update_last_used ( db , license_id , dbg ) ; } } } const last_used = { } ; async function update_last_used ( db , license_id , dbg ) { dbg ( `update_last_used("${ license_id }")` ) ; const now = new Date ( ) . valueOf ( ) ; if ( last_used [ license_id ] != null && now - last_used [ license_id ] <= 60 * 1000 ) { dbg ( "recently updated so waiting" ) ; return ; } last_used [ license_id ] = now ; dbg ( "did NOT recently update, so updating in database" ) ; await callback2 ( db . _query . bind ( db ) , { query : "UPDATE site_licenses" , set : { last_used : "NOW()" } , where : { id : license_id } , } ) ; }