2019-05-09 18:54:39 +12:00
< ? php
2019-10-01 17:57:41 +13:00
2021-01-12 10:52:05 +13:00
use Appwrite\Auth\Auth ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Audit ;
2022-06-22 22:51:49 +12:00
use Appwrite\Event\Database as EventDatabase ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Delete ;
2022-04-04 18:30:07 +12:00
use Appwrite\Event\Event ;
2022-11-16 07:13:17 +13:00
use Appwrite\Event\Func ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Mail ;
2022-11-24 20:53:52 +13:00
use Appwrite\Event\Usage ;
2021-06-30 23:36:58 +12:00
use Appwrite\Messaging\Adapter\Realtime ;
2022-05-26 23:51:08 +12:00
use Appwrite\Utopia\Response ;
use Appwrite\Utopia\Request ;
2020-06-29 05:31:21 +12:00
use Utopia\App ;
2022-02-07 01:49:01 +13:00
use Appwrite\Extend\Exception ;
2019-11-30 07:23:29 +13:00
use Utopia\Abuse\Abuse ;
use Utopia\Abuse\Adapters\TimeLimit ;
2022-07-24 05:42:42 +12:00
use Utopia\Cache\Adapter\Filesystem ;
use Utopia\Cache\Cache ;
2022-05-26 23:51:08 +12:00
use Utopia\Database\Database ;
2022-08-31 20:52:55 +12:00
use Utopia\Database\DateTime ;
2021-10-08 04:35:17 +13:00
use Utopia\Database\Document ;
2022-01-19 00:05:04 +13:00
use Utopia\Database\Validator\Authorization ;
2022-12-16 06:00:05 +13:00
use Utopia\Logger\Log ;
use Utopia\Logger\Logger ;
2019-11-30 07:23:29 +13:00
2022-08-17 00:28:30 +12:00
$parseLabel = function ( string $label , array $responsePayload , array $requestParams , Document $user ) {
preg_match_all ( '/{(.*?)}/' , $label , $matches );
foreach ( $matches [ 1 ] ? ? [] as $pos => $match ) {
$find = $matches [ 0 ][ $pos ];
$parts = explode ( '.' , $match );
if ( count ( $parts ) !== 2 ) {
throw new Exception ( 'Too less or too many parts' , 400 , Exception :: GENERAL_ARGUMENT_INVALID );
}
$namespace = $parts [ 0 ] ? ? '' ;
$replace = $parts [ 1 ] ? ? '' ;
$params = match ( $namespace ) {
'user' => ( array ) $user ,
'request' => $requestParams ,
default => $responsePayload ,
};
if ( array_key_exists ( $replace , $params )) {
$label = \str_replace ( $find , $params [ $replace ], $label );
}
}
return $label ;
};
2019-11-30 07:23:29 +13:00
2022-12-22 21:48:03 +13:00
$databaseListener = function ( string $event , Document $document , Document $project , Usage $queueForUsage , Database $dbForProject , Logger | null $logger ) {
2022-11-24 20:53:52 +13:00
2022-12-16 06:00:05 +13:00
$value = 1 ;
2022-12-12 06:15:38 +13:00
2022-10-30 18:16:51 +13:00
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
2022-11-24 20:53:52 +13:00
$value = - 1 ;
2022-10-30 18:14:46 +13:00
}
2022-12-12 22:37:10 +13:00
/**
* On Documents that tied by relations like functions > deployments > build || documents > collection > database || buckets > files
* When we remove a parent document we need to deduct his children aggregation from the project scope
*/
2022-12-16 06:00:05 +13:00
try {
switch ( true ) {
case $document -> getCollection () === 'teams' :
$queueForUsage
-> addMetric ( " teams " , $value ); // per project
break ;
case $document -> getCollection () === 'users' :
$queueForUsage
-> addMetric ( " users " , $value ); // per project
//Project level sessions deduction
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
2022-12-26 06:07:03 +13:00
$sessions = count ( $document -> getAttribute ( 'sessions' , 0 ));
2022-12-23 02:02:41 +13:00
if ( ! empty ( $sessions )) {
$queueForUsage
-> addMetric ( " sessions " , ( $sessions * - 1 )); // per project
}
2022-12-12 21:16:09 +13:00
}
2022-12-16 06:00:05 +13:00
break ;
case $document -> getCollection () === 'sessions' : // sessions
$queueForUsage
-> addMetric ( " sessions " , $value ); //per project
break ;
case $document -> getCollection () === 'databases' : // databases
$queueForUsage
-> addMetric ( " databases " , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
//Project level collections/documents deduction
2022-12-23 02:02:41 +13:00
$collections = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .collections " ));
$documents = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .documents " ));
if ( ! empty ( $collections [ 'value' ])) {
$queueForUsage
-> addMetric ( " collections " , ( $collections [ 'value' ] * - 1 ))
-> addMetric ( " documents " , ( $documents [ 'value' ] * - 1 ));
}
2022-12-09 23:41:22 +13:00
}
2022-12-16 06:00:05 +13:00
break ;
case str_starts_with ( $document -> getCollection (), 'database_' ) && ! str_contains ( $document -> getCollection (), 'collection' ) : //collections
2022-12-22 21:48:03 +13:00
$parts = explode ( '_' , $document -> getCollection ());
2022-12-26 06:07:03 +13:00
$databaseId = $parts [ 1 ] ? ? 0 ;
2022-12-16 06:00:05 +13:00
$queueForUsage
-> addMetric ( " collections " , $value ) // per project
2022-12-22 21:48:03 +13:00
-> addMetric ( " { $databaseId } " . " .collections " , $value ) // per database
2022-12-16 06:00:05 +13:00
;
2022-12-12 22:37:10 +13:00
2022-12-16 06:00:05 +13:00
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
//Project documents deduction
2022-12-22 21:48:03 +13:00
$documents = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $databaseId } " . " .documents " ));
2022-12-23 02:02:41 +13:00
if ( ! empty ( $documents [ 'value' ])) {
$queueForUsage
-> addMetric ( " documents " , ( $documents [ 'value' ] * - 1 ));
}
2022-12-11 22:00:00 +13:00
}
2022-12-16 06:00:05 +13:00
break ;
case str_starts_with ( $document -> getCollection (), 'database_' ) && str_contains ( $document -> getCollection (), '_collection_' ) : //documents
2022-12-22 21:48:03 +13:00
$parts = explode ( '_' , $document -> getCollection ());
2022-12-26 06:07:03 +13:00
$databaseId = $parts [ 1 ] ? ? 0 ;
$collectionId = $parts [ 3 ] ? ? 0 ;
2022-12-16 06:00:05 +13:00
$queueForUsage
-> addMetric ( " documents " , $value ) // per project
2022-12-22 21:48:03 +13:00
-> addMetric ( " { $databaseId } " . " .documents " , $value ) // per database
-> addMetric ( " { $databaseId } " . " . " . " { $collectionId } " . " .documents " , $value ) // per collection
2022-12-10 00:01:17 +13:00
;
2022-12-16 06:00:05 +13:00
break ;
case $document -> getCollection () === 'buckets' :
$queueForUsage
-> addMetric ( " buckets " , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
2022-12-23 02:02:41 +13:00
//Project files/files.storage deduction
$files = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .files " ));
$storage = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .files.storage " ));
if ( ! empty ( $files [ 'value' ])) {
$queueForUsage
-> addMetric ( " files " , ( $files [ 'value' ] * - 1 ))
-> addMetric ( " files.storage " , ( $storage [ 'value' ] * - 1 ));
}
2022-12-09 02:55:18 +13:00
}
2022-12-16 06:00:05 +13:00
break ;
case str_starts_with ( $document -> getCollection (), 'bucket_' ) : // files
2022-12-23 02:02:41 +13:00
$parts = explode ( '_' , $document -> getCollection ());
$bucketId = $parts [ 1 ];
2022-12-16 06:00:05 +13:00
$queueForUsage
-> addMetric ( " files " , $value ) // per project
-> addMetric ( " files.storage " , $document -> getAttribute ( 'sizeOriginal' ) * $value ) // per project
2022-12-23 02:02:41 +13:00
-> addMetric ( " { $bucketId } " . " .files " , $value ) // per bucket
-> addMetric ( " { $bucketId } " . " .files.storage " , $document -> getAttribute ( 'sizeOriginal' ) * $value ) // per bucket
2022-12-10 00:01:17 +13:00
;
2022-12-16 06:00:05 +13:00
break ;
case $document -> getCollection () === 'functions' :
$queueForUsage
-> addMetric ( " functions " , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
2022-12-23 02:02:41 +13:00
//Project level function/builds/executions deduction
$deployments = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_function. " . " { $document -> getInternalId () } " . " .deployments " ));
$deploymentsStorage = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_function. " . " { $document -> getInternalId () } " . " .deployments.storage " ));
$builds = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .builds " ));
$buildsStorage = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .builds.storage " ));
$buildsCompute = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .builds.compute " ));
$executions = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .executions " ));
$executionsCompute = $dbForProject -> getDocument ( 'stats' , md5 ( " _inf_ " . " { $document -> getInternalId () } " . " .executions.compute " ));
if ( ! empty ( $deployments [ 'value' ])) {
$queueForUsage
-> addMetric ( " deployments " , ( $deployments [ 'value' ] * - 1 ))
-> addMetric ( " deployments.storage " , ( $deploymentsStorage [ 'value' ] * - 1 ))
;
}
if ( ! empty ( $builds [ 'value' ])) {
$queueForUsage
-> addMetric ( " builds " , ( $builds [ 'value' ] * - 1 ))
-> addMetric ( " builds.storage " , ( $buildsStorage [ 'value' ] * - 1 ))
-> addMetric ( " builds.compute " , ( $buildsCompute [ 'value' ] * - 1 ))
;
}
if ( ! empty ( $executions [ 'value' ])) {
$queueForUsage
-> addMetric ( " executions " , ( $executions [ 'value' ] * - 1 ))
-> addMetric ( " executions.compute " , ( $executionsCompute [ 'value' ] * - 1 ))
;
}
2022-12-09 08:06:55 +13:00
}
2022-12-16 06:00:05 +13:00
break ;
case $document -> getCollection () === 'deployments' :
$queueForUsage
-> addMetric ( " deployments " , $value ) // per project
-> addMetric ( " deployments.storage " , $document -> getAttribute ( 'size' ) * $value ) // per project
2022-12-23 06:18:54 +13:00
-> addMetric ( " { $document -> getAttribute ( 'resourceType' ) } " . " . " . " { $document -> getAttribute ( 'resourceInternalId' ) } " . " .deployments " , $value ) // per function
-> addMetric ( " { $document -> getAttribute ( 'resourceType' ) } " . " . " . " { $document -> getAttribute ( 'resourceInternalId' ) } " . " .deployments.storage " , $document -> getAttribute ( 'size' ) * $value ) // per function
2022-12-10 00:01:17 +13:00
;
2022-12-16 06:00:05 +13:00
break ;
case $document -> getCollection () === 'executions' :
$queueForUsage
-> addMetric ( " executions " , $value ) // per project
2022-12-23 06:18:54 +13:00
-> addMetric ( " { $document -> getAttribute ( 'functionInternalId' ) } " . " .executions " , $value ) // per function
2022-12-10 00:01:17 +13:00
;
2022-12-16 06:00:05 +13:00
break ;
default :
break ;
}
} catch ( Throwable $error ) {
2022-12-19 21:25:49 +13:00
if ( ! empty ( $logger )) {
2022-12-16 06:00:05 +13:00
$log = new Log ();
$isProduction = App :: getEnv ( '_APP_ENV' , 'development' ) === 'production' ;
$log
-> setNamespace ( " appwrite-worker " )
-> setServer ( \gethostname ())
-> setVersion ( App :: getEnv ( '_APP_VERSION' , 'UNKNOWN' ))
-> setType ( Log :: TYPE_ERROR )
-> setMessage ( $error -> getMessage ())
-> setAction ( 'appwrite-queue-usage' )
-> addTag ( 'verboseType' , get_class ( $error ))
-> addTag ( 'code' , $error -> getCode ())
-> addExtra ( 'collection' , $document -> getCollection ())
-> addExtra ( 'file' , $error -> getFile ())
-> addExtra ( 'line' , $error -> getLine ())
-> addExtra ( 'trace' , $error -> getTraceAsString ())
-> addExtra ( 'detailedTrace' , $error -> getTrace ())
-> addExtra ( 'roles' , \Utopia\Database\Validator\Authorization :: $roles )
-> setEnvironment ( $isProduction ? Log :: ENVIRONMENT_PRODUCTION : Log :: ENVIRONMENT_STAGING )
;
$logger -> addLog ( $log );
}
2022-10-30 18:14:46 +13:00
}
};
2022-07-22 18:00:42 +12:00
App :: init ()
2022-08-02 13:10:48 +12:00
-> groups ([ 'api' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'user' )
-> inject ( 'events' )
-> inject ( 'audits' )
-> inject ( 'mails' )
-> inject ( 'deletes' )
-> inject ( 'database' )
-> inject ( 'dbForProject' )
2022-12-05 06:06:23 +13:00
-> inject ( 'queueForUsage' )
2022-12-16 06:00:05 +13:00
-> inject ( 'logger' )
2022-07-22 18:00:42 +12:00
-> inject ( 'mode' )
2022-12-16 06:00:05 +13:00
-> action ( function ( App $utopia , Request $request , Response $response , Document $project , Document $user , Event $events , Audit $audits , Mail $mails , Delete $deletes , EventDatabase $database , Database $dbForProject , Usage $queueForUsage , Logger | null $logger , string $mode ) use ( $databaseListener ) {
2022-07-22 18:00:42 +12:00
$route = $utopia -> match ( $request );
if ( $project -> isEmpty () && $route -> getLabel ( 'abuse-limit' , 0 ) > 0 ) { // Abuse limit requires an active project scope
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: PROJECT_UNKNOWN );
2021-06-24 03:11:23 +12:00
}
2020-06-18 08:08:55 +12:00
2022-08-19 16:20:19 +12:00
/*
2022-07-22 18:00:42 +12:00
* Abuse Check
*/
2022-08-12 11:53:52 +12:00
$abuseKeyLabel = $route -> getLabel ( 'abuse-key' , 'url:{url},ip:{ip}' );
$timeLimitArray = [];
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$abuseKeyLabel = ( ! is_array ( $abuseKeyLabel )) ? [ $abuseKeyLabel ] : $abuseKeyLabel ;
2021-06-07 17:17:29 +12:00
2022-08-12 11:53:52 +12:00
foreach ( $abuseKeyLabel as $abuseKey ) {
$timeLimit = new TimeLimit ( $abuseKey , $route -> getLabel ( 'abuse-limit' , 0 ), $route -> getLabel ( 'abuse-time' , 3600 ), $dbForProject );
$timeLimit
2022-11-24 20:53:52 +13:00
-> setParam ( '{userId}' , $user -> getId ())
-> setParam ( '{userAgent}' , $request -> getUserAgent ( '' ))
-> setParam ( '{ip}' , $request -> getIP ())
-> setParam ( '{url}' , $request -> getHostname () . $route -> getPath ())
-> setParam ( '{method}' , $request -> getMethod ());
2022-08-12 11:53:52 +12:00
$timeLimitArray [] = $timeLimit ;
}
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$closestLimit = null ;
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$roles = Authorization :: getRoles ();
$isPrivilegedUser = Auth :: isPrivilegedUser ( $roles );
$isAppUser = Auth :: isAppUser ( $roles );
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
foreach ( $timeLimitArray as $timeLimit ) {
foreach ( $request -> getParams () as $key => $value ) { // Set request params as potential abuse keys
if ( ! empty ( $value )) {
$timeLimit -> setParam ( '{param-' . $key . '}' , ( \is_array ( $value )) ? \json_encode ( $value ) : $value );
}
2021-11-09 23:21:24 +13:00
}
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$abuse = new Abuse ( $timeLimit );
2022-08-13 15:21:50 +12:00
$remaining = $timeLimit -> remaining ();
$limit = $timeLimit -> limit ();
2022-08-31 20:52:55 +12:00
$time = ( new \DateTime ( $timeLimit -> time ())) -> getTimestamp () + $route -> getLabel ( 'abuse-time' , 3600 );
2022-07-22 18:00:42 +12:00
2022-08-13 15:21:50 +12:00
if ( $limit && ( $remaining < $closestLimit || is_null ( $closestLimit ))) {
$closestLimit = $remaining ;
2022-07-22 18:00:42 +12:00
$response
2022-08-13 15:21:50 +12:00
-> addHeader ( 'X-RateLimit-Limit' , $limit )
-> addHeader ( 'X-RateLimit-Remaining' , $remaining )
-> addHeader ( 'X-RateLimit-Reset' , $time )
2022-07-22 18:00:42 +12:00
;
2021-03-01 07:36:13 +13:00
}
2021-06-07 17:17:29 +12:00
2022-08-31 15:50:53 +12:00
$enabled = App :: getEnv ( '_APP_OPTIONS_ABUSE' , 'enabled' ) !== 'disabled' ;
2022-07-22 18:00:42 +12:00
if (
2022-08-31 15:50:53 +12:00
$enabled // Abuse is enabled
&& ! $isAppUser // User is not API key
&& ! $isPrivilegedUser // User is not an admin
&& $abuse -> check () // Route is rate-limited
) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED );
2021-03-01 07:36:13 +13:00
}
2021-11-09 23:21:24 +13:00
}
2021-01-06 01:22:20 +13:00
2022-11-16 18:30:57 +13:00
/*
* Background Jobs
*/
2022-08-12 11:53:52 +12:00
$events
2022-07-22 18:00:42 +12:00
-> setEvent ( $route -> getLabel ( 'event' , '' ))
-> setProject ( $project )
2022-08-12 01:47:53 +12:00
-> setUser ( $user );
2022-04-04 18:30:07 +12:00
2022-07-22 18:00:42 +12:00
$mails
-> setProject ( $project )
2022-08-12 01:47:53 +12:00
-> setUser ( $user );
2021-03-01 07:36:13 +13:00
2022-07-22 18:00:42 +12:00
$audits
-> setMode ( $mode )
-> setUserAgent ( $request -> getUserAgent ( '' ))
-> setIP ( $request -> getIP ())
2022-09-04 20:23:24 +12:00
-> setEvent ( $route -> getLabel ( 'audits.event' , '' ))
2022-07-22 18:00:42 +12:00
-> setProject ( $project )
2022-08-12 01:47:53 +12:00
-> setUser ( $user );
2021-03-01 07:36:13 +13:00
2022-07-22 18:00:42 +12:00
$deletes -> setProject ( $project );
$database -> setProject ( $project );
2022-08-09 23:57:33 +12:00
2022-11-24 20:53:52 +13:00
$dbForProject
2022-12-22 21:48:03 +13:00
-> on ( Database :: EVENT_DOCUMENT_CREATE , fn ( $event , $document ) => $databaseListener ( $event , $document , $project , $queueForUsage , $dbForProject , $logger ))
-> on ( Database :: EVENT_DOCUMENT_DELETE , fn ( $event , $document ) => $databaseListener ( $event , $document , $project , $queueForUsage , $dbForProject , $logger ))
2022-11-24 20:53:52 +13:00
;
2022-10-17 20:10:18 +13:00
2022-08-09 23:57:33 +12:00
$useCache = $route -> getLabel ( 'cache' , false );
2022-08-10 01:43:37 +12:00
2022-08-09 23:57:33 +12:00
if ( $useCache ) {
2022-11-11 23:41:41 +13:00
$key = md5 ( $request -> getURI () . implode ( '*' , $request -> getParams ())) . '*' . APP_CACHE_BUSTER ;
2022-08-16 01:55:11 +12:00
$cache = new Cache (
new Filesystem ( APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project -> getId ())
);
2022-08-09 23:57:33 +12:00
$timestamp = 60 * 60 * 24 * 30 ;
$data = $cache -> load ( $key , $timestamp );
2022-11-10 23:08:01 +13:00
2022-08-09 23:57:33 +12:00
if ( ! empty ( $data )) {
2022-08-10 01:43:37 +12:00
$data = json_decode ( $data , true );
2022-11-10 23:08:01 +13:00
$parts = explode ( '/' , $data [ 'resourceType' ]);
$type = $parts [ 0 ] ? ? null ;
if ( $type === 'bucket' ) {
$bucketId = $parts [ 1 ] ? ? null ;
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && $mode !== APP_MODE_ADMIN )) {
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
}
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
$validator = new Authorization ( Database :: PERMISSION_READ );
$valid = $validator -> isValid ( $bucket -> getRead ());
if ( ! $fileSecurity && ! $valid ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
$parts = explode ( '/' , $data [ 'resource' ]);
$fileId = $parts [ 1 ] ? ? null ;
if ( $fileSecurity && ! $valid ) {
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
if ( $file -> isEmpty ()) {
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
}
}
2022-08-10 01:43:37 +12:00
$response
-> addHeader ( 'Expires' , \date ( 'D, d M Y H:i:s' , \time () + $timestamp ) . ' GMT' )
-> addHeader ( 'X-Appwrite-Cache' , 'hit' )
2022-11-10 23:08:01 +13:00
-> setContentType ( $data [ 'contentType' ])
2022-08-10 01:43:37 +12:00
-> send ( base64_decode ( $data [ 'payload' ]))
;
2022-08-10 22:13:05 +12:00
$route -> setIsActive ( false );
2022-08-10 01:43:37 +12:00
} else {
$response -> addHeader ( 'X-Appwrite-Cache' , 'miss' );
}
}
2022-07-22 18:00:42 +12:00
});
App :: shutdown ()
2022-08-02 13:10:48 +12:00
-> groups ([ 'api' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'events' )
-> inject ( 'audits' )
-> inject ( 'deletes' )
-> inject ( 'database' )
-> inject ( 'dbForProject' )
2022-11-16 23:33:11 +13:00
-> inject ( 'queueForFunctions' )
2022-12-05 06:06:23 +13:00
-> inject ( 'queueForUsage' )
2022-12-15 20:56:06 +13:00
-> inject ( 'mode' )
-> action ( function ( App $utopia , Request $request , Response $response , Document $project , Event $events , Audit $audits , Delete $deletes , EventDatabase $database , Database $dbForProject , Func $queueForFunctions , Usage $queueForUsage , string $mode ) use ( $parseLabel ) {
2022-08-09 23:57:33 +12:00
2022-08-08 03:09:37 +12:00
$responsePayload = $response -> getPayload ();
2022-07-22 18:00:42 +12:00
if ( ! empty ( $events -> getEvent ())) {
if ( empty ( $events -> getPayload ())) {
2022-08-08 03:09:37 +12:00
$events -> setPayload ( $responsePayload );
2021-03-01 07:36:13 +13:00
}
2022-11-24 20:53:52 +13:00
2022-07-22 18:00:42 +12:00
/**
* Trigger functions .
*/
2022-11-16 23:33:11 +13:00
$queueForFunctions
2022-11-16 18:30:57 +13:00
-> from ( $events )
2022-07-22 18:00:42 +12:00
-> trigger ();
/**
* Trigger webhooks .
*/
$events
-> setClass ( Event :: WEBHOOK_CLASS_NAME )
-> setQueue ( Event :: WEBHOOK_QUEUE_NAME )
-> trigger ();
/**
* Trigger realtime .
*/
if ( $project -> getId () !== 'console' ) {
$allEvents = Event :: generateEvents ( $events -> getEvent (), $events -> getParams ());
$payload = new Document ( $events -> getPayload ());
$db = $events -> getContext ( 'database' );
$collection = $events -> getContext ( 'collection' );
$bucket = $events -> getContext ( 'bucket' );
$target = Realtime :: fromPayload (
2022-08-11 19:02:40 +12:00
// Pass first, most verbose event pattern
2022-07-22 18:00:42 +12:00
event : $allEvents [ 0 ],
payload : $payload ,
project : $project ,
database : $db ,
collection : $collection ,
bucket : $bucket ,
);
Realtime :: send (
projectId : $target [ 'projectId' ] ? ? $project -> getId (),
payload : $events -> getPayload (),
events : $allEvents ,
channels : $target [ 'channels' ],
roles : $target [ 'roles' ],
options : [
'permissionsChanged' => $target [ 'permissionsChanged' ],
'userId' => $events -> getParam ( 'userId' )
]
);
2021-03-01 07:36:13 +13:00
}
2022-04-19 04:21:45 +12:00
}
2021-03-01 07:36:13 +13:00
2022-08-09 00:19:41 +12:00
$route = $utopia -> match ( $request );
2022-08-15 07:27:43 +12:00
$requestParams = $route -> getParamsValues ();
2022-08-13 01:21:32 +12:00
$user = $audits -> getUser ();
2022-08-09 00:19:41 +12:00
2022-08-18 02:29:22 +12:00
/**
* Audit labels
*/
2022-08-17 00:28:30 +12:00
$pattern = $route -> getLabel ( 'audits.resource' , null );
if ( ! empty ( $pattern )) {
$resource = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
if ( ! empty ( $resource ) && $resource !== $pattern ) {
$audits -> setResource ( $resource );
2022-08-08 02:30:47 +12:00
}
2022-08-08 03:49:30 +12:00
}
2022-08-13 20:02:00 +12:00
2022-08-17 00:28:30 +12:00
$pattern = $route -> getLabel ( 'audits.userId' , null );
2022-08-12 01:47:53 +12:00
if ( ! empty ( $pattern )) {
2022-08-17 00:28:30 +12:00
$userId = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
$user = $dbForProject -> getDocument ( 'users' , $userId );
$audits -> setUser ( $user );
2021-03-12 05:28:03 +13:00
}
2021-10-08 04:35:17 +13:00
2022-08-17 00:28:30 +12:00
if ( ! empty ( $audits -> getResource ()) && ! empty ( $audits -> getUser () -> getId ())) {
/**
* audits . payload is switched to default true
* in order to auto audit payload for all endpoints
*/
$pattern = $route -> getLabel ( 'audits.payload' , true );
if ( ! empty ( $pattern )) {
$audits -> setPayload ( $responsePayload );
}
2022-07-22 18:00:42 +12:00
foreach ( $events -> getParams () as $key => $value ) {
$audits -> setParam ( $key , $value );
2021-03-01 07:36:13 +13:00
}
2022-07-22 18:00:42 +12:00
$audits -> trigger ();
2022-04-19 04:21:45 +12:00
}
2021-10-08 04:35:17 +13:00
2022-07-22 18:00:42 +12:00
if ( ! empty ( $deletes -> getType ())) {
$deletes -> trigger ();
2022-04-14 00:39:31 +12:00
}
2021-10-08 04:35:17 +13:00
2022-07-22 18:00:42 +12:00
if ( ! empty ( $database -> getType ())) {
$database -> trigger ();
}
2021-10-08 04:35:17 +13:00
2022-08-18 02:29:22 +12:00
/**
* Cache label
*/
2022-08-17 03:02:17 +12:00
$useCache = $route -> getLabel ( 'cache' , false );
if ( $useCache ) {
2022-11-10 23:08:01 +13:00
$resource = $resourceType = null ;
2022-08-10 01:43:37 +12:00
$data = $response -> getPayload ();
2022-08-31 00:44:37 +12:00
2022-08-17 03:02:17 +12:00
if ( ! empty ( $data [ 'payload' ])) {
$pattern = $route -> getLabel ( 'cache.resource' , null );
if ( ! empty ( $pattern )) {
$resource = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
}
2022-08-15 21:05:41 +12:00
2022-11-10 23:08:01 +13:00
$pattern = $route -> getLabel ( 'cache.resourceType' , null );
if ( ! empty ( $pattern )) {
$resourceType = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
}
2022-11-11 23:41:41 +13:00
$key = md5 ( $request -> getURI () . implode ( '*' , $request -> getParams ())) . '*' . APP_CACHE_BUSTER ;
2022-08-17 03:02:17 +12:00
$data = json_encode ([
2022-12-14 20:34:04 +13:00
'resourceType' => $resourceType ,
'resource' => $resource ,
'contentType' => $response -> getContentType (),
'payload' => base64_encode ( $data [ 'payload' ]),
2022-08-17 03:02:17 +12:00
]) ;
2022-08-15 21:05:41 +12:00
2022-08-17 03:02:17 +12:00
$signature = md5 ( $data );
$cacheLog = $dbForProject -> getDocument ( 'cache' , $key );
2022-08-31 20:52:55 +12:00
$accessedAt = $cacheLog -> getAttribute ( 'accessedAt' , '' );
2022-09-01 01:07:27 +12:00
$now = DateTime :: now ();
2022-08-17 03:02:17 +12:00
if ( $cacheLog -> isEmpty ()) {
Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'cache' , new Document ([
2022-08-10 01:43:37 +12:00
'$id' => $key ,
2022-08-15 21:05:41 +12:00
'resource' => $resource ,
2022-09-01 01:07:27 +12:00
'accessedAt' => $now ,
2022-08-15 21:05:41 +12:00
'signature' => $signature ,
2022-08-10 01:43:37 +12:00
])));
2022-09-01 01:11:23 +12:00
} elseif ( DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), - APP_CACHE_UPDATE )) > $accessedAt ) {
2022-09-01 01:07:27 +12:00
$cacheLog -> setAttribute ( 'accessedAt' , $now );
2022-08-10 01:43:37 +12:00
Authorization :: skip ( fn () => $dbForProject -> updateDocument ( 'cache' , $cacheLog -> getId (), $cacheLog ));
2022-08-17 03:02:17 +12:00
}
2022-08-15 03:01:34 +12:00
2022-08-17 03:02:17 +12:00
if ( $signature !== $cacheLog -> getAttribute ( 'signature' )) {
$cache = new Cache (
new Filesystem ( APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project -> getId ())
);
$cache -> save ( $key , $data );
}
2022-08-16 00:16:32 +12:00
}
2022-07-24 05:42:42 +12:00
}
2022-12-15 20:56:06 +13:00
if (
2022-12-19 21:25:49 +13:00
$project -> getId () !== 'console'
2022-12-26 06:07:03 +13:00
//&& $mode !== APP_MODE_ADMIN
2022-12-15 20:56:06 +13:00
) {
2022-08-17 22:55:01 +12:00
$fileSize = 0 ;
$file = $request -> getFiles ( 'file' );
2022-12-07 00:36:17 +13:00
2022-08-17 23:10:17 +12:00
if ( ! empty ( $file )) {
2022-08-17 22:55:01 +12:00
$fileSize = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
}
2022-12-07 00:36:17 +13:00
$queueForUsage
-> setProject ( $project )
2022-12-12 06:15:38 +13:00
-> addMetric ( 'network.requests' , 1 )
2022-12-08 06:56:38 +13:00
-> addMetric ( " network.inbound " , $request -> getSize () + $fileSize )
-> addMetric ( " network.outbound " , $response -> getSize ())
2022-12-07 00:36:17 +13:00
-> trigger ();
2022-07-22 18:00:42 +12:00
}
});