Skip to content

Commit

Permalink
Restructure backend
Browse files Browse the repository at this point in the history
  • Loading branch information
actuallymentor committed Oct 17, 2023
1 parent ea17d79 commit 621a885
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 41 deletions.
20 changes: 19 additions & 1 deletion firestore.indexes.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,25 @@
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "static_drop_claims",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "is_mock_claim",
"order": "ASCENDING"
},
{
"fieldPath": "expires",
"order": "ASCENDING"
},
{
"fieldPath": "__name__",
"order": "ASCENDING"
}
]
}
],
"fieldOverrides": []
}
}
60 changes: 27 additions & 33 deletions functions/index.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,67 @@
// V1 Dependencies
const functions = require( "firebase-functions" )

// V2 Dependencies
const { onRequest, onCall } = require( "firebase-functions/v2/https" )

// V2 Runtime config
const protected_runtime = {
enforceAppCheck: true,
}

// V1 Runtime config
const generousRuntime = {
timeoutSeconds: 540,
memory: '4GB'
}
const keepWarmRuntime = {
minInstances: 1,
}

const { log, dev } = require( './modules/helpers' )
log( `⚠️ Verbose mode on, ${ dev ? '⚙️ dev mode on' : '🚀 production mode on' }` )

// Runtime config
const { v1_oncall, v2_oncall } = require( './runtime/on_call_runtimes' )
const { v1_onrequest, v2_onrequest } = require( './runtime/on_request_runtimes' )

// ///////////////////////////////
// Code status managers
// ///////////////////////////////

const { refreshScannedCodesStatuses, refresh_unknown_and_unscanned_codes, getEventDataFromCode, check_code_status } = require( './modules/codes' )

// Get event data of a code
exports.getEventDataFromCode = functions.https.onCall( getEventDataFromCode )
exports.getEventDataFromCode = v1_oncall( getEventDataFromCode )

// Get all data of a code
exports.check_code_status = functions.https.onCall( check_code_status )
exports.check_code_status = v1_oncall( check_code_status )

// Refresh all codes ( trigger from frontend on page mount of EventView )
exports.requestManualCodeRefresh = functions.runWith( generousRuntime ).https.onCall( refresh_unknown_and_unscanned_codes )
exports.requestManualCodeRefresh = v2_oncall( [ 'high_memory', 'long_timeout' ], refresh_unknown_and_unscanned_codes )

// Allow frontend to trigger updates for scanned codes, ( triggers on a periodic interval from EventView ), is lighter than requestManualCodeRefresh as it checks only scanned and claimed == true codes
exports.refreshScannedCodesStatuses = functions.runWith( generousRuntime ).https.onCall( refreshScannedCodesStatuses )
exports.refreshScannedCodesStatuses = v1_oncall( [ 'high_memory', 'long_timeout' ], refreshScannedCodesStatuses )

// Directly mint a code to an address
const { mint_code_to_address } = require( './modules/minting' )
exports.mint_code_to_address = onCall( protected_runtime, mint_code_to_address )
exports.mint_code_to_address = v2_oncall( [ 'high_memory', 'long_timeout' ], mint_code_to_address )

// Let admins recalculate available codes
const { recalculate_available_codes_admin } = require( './modules/codes' )
exports.recalculate_available_codes = onCall( protected_runtime, recalculate_available_codes_admin )
exports.recalculate_available_codes = v2_oncall( recalculate_available_codes_admin )

// ///////////////////////////////
// Event data
// ///////////////////////////////

const { registerEvent, deleteEvent, getUniqueOrganiserEmails } = require( './modules/events' )
exports.registerEvent = functions.runWith( generousRuntime ).https.onCall( registerEvent )
exports.deleteEvent = functions.https.onCall( deleteEvent )
exports.registerEvent = v1_oncall( [ 'high_memory', 'long_timeout' ], registerEvent )
exports.deleteEvent = v1_oncall( deleteEvent )

// Email export to update event organisers
exports.getUniqueOrganiserEmails = functions.https.onCall( getUniqueOrganiserEmails )
exports.getUniqueOrganiserEmails = v1_oncall( getUniqueOrganiserEmails )

// ///////////////////////////////
// QR Middleware API
// ///////////////////////////////
const claimMiddleware = require( './modules/claim' )
exports.claimMiddleware = onRequest( { cors: true, ...keepWarmRuntime }, claimMiddleware )
exports.claimMiddleware = v2_onrequest( [ 'max_concurrency', 'keep_warm', 'memory' ], claimMiddleware )

/* ///////////////////////////////
// Kiosk generator middleware API
// /////////////////////////////*/
const generate_kiosk = require( './modules/kiosk_generator' )
exports.generate_kiosk = functions.https.onRequest( generate_kiosk )
exports.generate_kiosk = v1_onrequest( generate_kiosk )

// ///////////////////////////////
// Housekeeping
Expand All @@ -90,36 +84,36 @@ exports.updateEventAvailableCodes = functions.firestore.document( `codes/{codeId
// Security
// /////////////////////////////*/
const { validateCallerDevice, validateCallerCaptcha } = require( './modules/security' )
exports.validateCallerDevice = onCall( { ...protected_runtime, ...keepWarmRuntime, }, validateCallerDevice )
exports.validateCallerCaptcha = functions.https.onCall( validateCallerCaptcha )
exports.validateCallerDevice = v2_oncall( [ 'high_memory', 'long_timeout', 'keep_warm' ], validateCallerDevice )
exports.validateCallerCaptcha = v1_oncall( validateCallerCaptcha )

// Log kiosk opens
const { log_kiosk_open } = require( './modules/security' )
exports.log_kiosk_open = onCall( protected_runtime, log_kiosk_open )
exports.log_kiosk_open = v2_oncall( log_kiosk_open )

/* ///////////////////////////////
// Code claiming
// /////////////////////////////*/
const { get_code_by_challenge } = require( './modules/codes' )
exports.get_code_by_challenge = functions.https.onCall( get_code_by_challenge )
exports.get_code_by_challenge = v1_oncall( get_code_by_challenge )

/* ///////////////////////////////
// Health check
// /////////////////////////////*/
const { health_check, public_health_check } = require( './modules/health' )
exports.health_check = functions.https.onCall( health_check )
exports.ping = functions.https.onCall( ping => 'pong' )
exports.health_check = v1_oncall( health_check )
exports.ping = v2_oncall( [ 'max_concurrency' ],ping => 'pong' )

/* ///////////////////////////////
// Static QR system
// /////////////////////////////*/
const { claim_code_by_email } = require( './modules/codes' )
const { export_emails_of_static_drop, create_static_drop, update_public_static_drop_data, delete_emails_of_static_drop } = require( './modules/static_qr_drop' )
exports.export_emails_of_static_drop = functions.https.onCall( export_emails_of_static_drop )
exports.delete_emails_of_static_drop = functions.https.onCall( delete_emails_of_static_drop )
exports.claim_code_by_email = functions.https.onCall( claim_code_by_email )
exports.create_static_drop = functions.https.onCall( create_static_drop )
exports.export_emails_of_static_drop = v1_oncall( export_emails_of_static_drop )
exports.delete_emails_of_static_drop = v1_oncall( delete_emails_of_static_drop )
exports.claim_code_by_email = v1_oncall( claim_code_by_email )
exports.create_static_drop = v1_oncall( create_static_drop )
exports.update_public_static_drop_data = functions.firestore.document( `static_drop_private/{drop_id}` ).onWrite( update_public_static_drop_data )

// Public health check
exports.public_health_check = functions.https.onRequest( public_health_check )
exports.public_health_check = v1_onrequest( public_health_check )
23 changes: 16 additions & 7 deletions functions/modules/health.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const { log } = require( './helpers' )
const { db } = require( './firebase' )

let cached_api_health = false
let cached_api_health_timestamp = 0
const cached_api_health_duration_ms = 10_000
const health_check = async () => {

// Function dependencies
Expand All @@ -20,15 +23,21 @@ const health_check = async () => {
return false
} )

// Check the self-reported health of the POAP api
const api_health = await call_poap_endpoint( `/health-check` ).catch( e => {
log( e )
return false
} )
// if the cached api health is stale, check the api health
const api_health_cache_stale = Date.now() - cached_api_health_timestamp > cached_api_health_duration_ms
if( api_health_cache_stale ) {

// Check the self-reported health of the POAP api
cached_api_health = await call_poap_endpoint( `/health-check` ).catch( e => {
log( e )
return false
} )
}


// Update status object to reflect new data
status.healthy = !!( has_token && api_health )
status.poap_api = !!api_health
status.healthy = !!( has_token && cached_api_health )
status.poap_api = !!cached_api_health
status.poap_api_auth = !!has_token

return status
Expand Down
52 changes: 52 additions & 0 deletions functions/runtime/on_call_runtimes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Impport dev and log helpers
const { log } = require( '../modules/helpers' )
const debug = false

/**
* Return a V1 oncall with runtimes. NOTE: v1 appcheck is enforced through code and not config
* @param {Array.<"high_memory"|"long_timeout"|"keep_warm">} [runtimes] - Array of runtime keys to use
* @param {Function} handler - Function to run
* @returns {Function} - Firebase function
*/
exports.v1_oncall = ( runtimes=[], handler ) => {

if( debug ) log( `Creating handler with: `, typeof runtimes, runtimes, typeof handler, handler )

const functions = require( "firebase-functions" )
const { v1_runtimes } = require( './runtimes_settings' )

// If the first parameter was a function, return the undecorated handler
if( typeof runtimes === 'function' ) {
if( debug ) log( 'v1_oncall: no runtimes specified, returning undecorated handler' )
return functions.https.onCall( runtimes )
}

// Config the runtimes for this function
const runtime = runtimes.reduce( ( acc, runtime_key ) => ( { ...acc, ...v1_runtimes[ runtime_key ] } ), {} )
if( debug ) log( 'v1_oncall: returning decorated handler with runtime: ', runtime )
return functions.runWith( runtime ).https.onCall( handler )
}

/**
* Return a V2 oncall with runtimes
* @param {Array.<"long_timeout"|"high_memory"|"keep_warm"|"max_concurrency">} [runtimes] - Array of runtime keys to use, the protected runtime is ALWAYS ADDED
* @param {Function} handler - Firebase function handler
* @returns {Function} - Firebase function
*/
exports.v2_oncall = ( runtimes=[], handler ) => {

if( debug ) log( `Creating handler with: `, typeof runtimes, runtimes, typeof handler, handler )

const { onCall } = require( "firebase-functions/v2/https" )
const { v2_runtimes } = require( './runtimes_settings' )

// If the first parameter was a function, return the handler as 'protected' firebase oncall
if( typeof runtimes === 'function' ) {
if( debug ) log( 'v2_oncall: no runtimes specified, returning undecorated handler' )
return onCall( { ...v2_runtimes.protected }, runtimes )
}

const runtime = runtimes.reduce( ( acc, runtime_key ) => ( { ...acc, ...v2_runtimes[ runtime_key ] } ), { ...v2_runtimes.protected } )
if( debug ) log( 'v2_oncall: returning decorated handler with runtime: ', runtime )
return onCall( runtime, handler )
}
37 changes: 37 additions & 0 deletions functions/runtime/on_request_runtimes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Return a V1 onRequest with runtimes. NOTE: v1 appcheck is enforced through code and not config
* @param {Array.<"high_memory"|"long_timeout"|"keep_warm">} [runtimes] - Array of runtime keys to use
* @param {Function} handler - Function to run
* @returns {Function} - Firebase function
*/
exports.v1_onrequest = ( runtimes=[], handler ) => {

const functions = require( "firebase-functions" )
const { v1_runtimes } = require( './runtimes_settings' )

// If the first parameter was a function, return the undecorated handler
if( typeof runtimes === 'function' ) return functions.https.onRequest( runtimes )

// Config the runtimes for this function
const runtime = runtimes.reduce( ( acc, runtime_key ) => ( { ...acc, ...v1_runtimes[ runtime_key ] } ), {} )
return functions.runWith( runtime ).https.onRequest( handler )
}

/**
* Return a V2 onRequest with runtimes
* @param {Array.<"long_timeout"|"high_memory"|"keep_warm"|"max_concurrency">} [runtimes] - Array of runtime keys to use, CORS support is ALWAYS ADDED
* @param {Function} handler - Firebase function handler
* @returns {Function} - Firebase function
*/
exports.v2_onrequest = ( runtimes=[], handler ) => {

const { onRequest } = require( "firebase-functions/v2/https" )
const { v2_runtimes } = require( './runtimes_settings' )
const runtime_basis = { cors: true }

// If the first parameter was a function, return the handler as 'protected' firebase oncall
if( typeof runtimes === 'function' ) return onRequest( runtime_basis, runtimes )

const runtime = runtimes.reduce( ( acc, runtime_key ) => ( { ...acc, ...v2_runtimes[ runtime_key ] } ), runtime_basis )
return onRequest( runtime, handler )
}
27 changes: 27 additions & 0 deletions functions/runtime/runtimes_settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @typedef {Object} V1runtimes
* @property {string} high_memory - Allocate high memory to function
* @property {string} long_timeout - Set long timeout to function
* @property {string} keep_warm - Keep function warm
*/
exports.v1_runtimes = {
high_memory: { memory: '4GB' },
long_timeout: { timeoutSeconds: 540 },
keep_warm: { minInstances: 1 },
}

/**
* @typedef {Object} V2runtimes
* @property {string} protected - Enforce appcheck
* @property {string} long_timeout - Set long timeout to function
* @property {string} high_memory - Allocate high memory to function
* @property {string} keep_warm - Keep function warm with min instances
* @property {string} max_concurrency - Set max concurrency
*/
exports.v2_runtimes = {
protected: { enforceAppCheck: true },
long_timeout: { timeoutSeconds: 540 },
high_memory: { memory: '4GiB' }, // Note: memory also increases compute power
keep_warm: { minInstances: 1 },
max_concurrency: { concurrency: 1000 }
}

0 comments on commit 621a885

Please sign in to comment.