2020-06-26 09:54:37 +00:00
< ? php
2024-10-08 07:54:40 +00:00
require_once __DIR__ . '/../vendor/autoload.php' ;
2020-06-26 09:54:37 +00:00
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Request ;
2020-10-29 13:07:56 +00:00
use Appwrite\Utopia\Response ;
2024-10-08 07:54:40 +00:00
use Swoole\Constant ;
use Swoole\Http\Request as SwooleRequest ;
use Swoole\Http\Response as SwooleResponse ;
use Swoole\Http\Server ;
use Swoole\Process ;
2024-08-23 17:02:54 +00:00
use Utopia\Abuse\Adapters\Database\TimeLimit ;
2024-10-08 07:54:40 +00:00
use Utopia\App ;
2024-03-06 17:34:21 +00:00
use Utopia\Audit\Audit ;
2020-06-26 12:27:58 +00:00
use Utopia\CLI\Console ;
2021-05-04 10:24:08 +00:00
use Utopia\Config\Config ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Database ;
use Utopia\Database\Document ;
2022-12-14 15:42:25 +00:00
use Utopia\Database\Helpers\ID ;
2022-12-14 16:04:06 +00:00
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2021-07-25 14:51:04 +00:00
use Utopia\Database\Validator\Authorization ;
2024-10-08 07:54:40 +00:00
use Utopia\Logger\Log ;
use Utopia\Logger\Log\User ;
use Utopia\Pools\Group ;
use Utopia\Swoole\Files ;
2024-04-01 11:02:47 +00:00
use Utopia\System\System ;
2020-06-26 09:54:37 +00:00
2024-10-08 07:54:40 +00:00
$http = new Server (
host : " 0.0.0.0 " ,
port : System :: getEnv ( 'PORT' , 80 ),
mode : SWOOLE_PROCESS ,
);
2020-07-01 08:55:14 +00:00
2024-05-09 09:41:02 +00:00
$payloadSize = 12 * ( 1024 * 1024 ); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
2024-04-01 11:02:47 +00:00
$workerNumber = swoole_cpu_num () * intval ( System :: getEnv ( '_APP_WORKER_PER_CORE' , 6 ));
2022-09-30 10:32:58 +00:00
2024-10-08 07:54:40 +00:00
$http
-> set ([
'worker_num' => $workerNumber ,
'open_http2_protocol' => true ,
'http_compression' => true ,
'http_compression_level' => 6 ,
'package_max_length' => $payloadSize ,
'buffer_output_size' => $payloadSize ,
]);
$http -> on ( Constant :: EVENT_WORKER_START , function ( $server , $workerId ) {
Console :: success ( 'Worker ' . ++ $workerId . ' started successfully' );
});
$http -> on ( Constant :: EVENT_BEFORE_RELOAD , function ( $server , $workerId ) {
Console :: success ( 'Starting reload...' );
});
$http -> on ( Constant :: EVENT_AFTER_RELOAD , function ( $server , $workerId ) {
Console :: success ( 'Reload completed...' );
});
include __DIR__ . '/controllers/general.php' ;
$http -> on ( Constant :: EVENT_START , function ( Server $http ) use ( $payloadSize , $register ) {
$app = new App ( 'UTC' );
go ( function () use ( $register , $app ) {
$pools = $register -> get ( 'pools' );
/** @var Group $pools */
App :: setResource ( 'pools' , fn () => $pools );
2022-08-02 18:56:45 +00:00
2024-10-08 07:54:40 +00:00
// wait for database to be ready
$attempts = 0 ;
$max = 10 ;
$sleep = 1 ;
2021-07-20 19:11:54 +00:00
2024-10-08 07:54:40 +00:00
do {
2021-07-20 18:59:58 +00:00
try {
2024-10-08 07:54:40 +00:00
$attempts ++ ;
$dbForConsole = $app -> getResource ( 'dbForConsole' );
/** @var Utopia\Database\Database $dbForConsole */
break ; // leave the do-while if successful
2024-02-08 01:17:54 +00:00
} catch ( \Throwable $e ) {
2024-10-08 07:54:40 +00:00
Console :: warning ( " Database not ready. Retrying connection ( { $attempts } )... " );
if ( $attempts >= $max ) {
throw new \Exception ( 'Failed to connect to database: ' . $e -> getMessage ());
}
sleep ( $sleep );
2022-01-10 06:31:33 +00:00
}
2024-10-08 07:54:40 +00:00
} while ( $attempts < $max );
2022-06-22 21:11:42 +00:00
2024-10-08 07:54:40 +00:00
Console :: success ( '[Setup] - Server database init started...' );
2021-07-04 15:14:39 +00:00
2024-10-08 07:54:40 +00:00
try {
Console :: success ( '[Setup] - Creating database: appwrite...' );
$dbForConsole -> create ();
} catch ( \Throwable $e ) {
Console :: success ( '[Setup] - Skip: metadata table already exists' );
}
2021-07-04 12:05:46 +00:00
2024-10-08 07:54:40 +00:00
if ( $dbForConsole -> getCollection ( Audit :: COLLECTION ) -> isEmpty ()) {
$audit = new Audit ( $dbForConsole );
$audit -> setup ();
}
2022-01-10 06:31:33 +00:00
2024-10-08 07:54:40 +00:00
if ( $dbForConsole -> getCollection ( TimeLimit :: COLLECTION ) -> isEmpty ()) {
$adapter = new TimeLimit ( " " , 0 , 1 , $dbForConsole );
$adapter -> setup ();
}
2020-12-29 18:42:03 +00:00
2024-10-08 07:54:40 +00:00
/** @var array $collections */
$collections = Config :: getParam ( 'collections' , []);
$consoleCollections = $collections [ 'console' ];
foreach ( $consoleCollections as $key => $collection ) {
if (( $collection [ '$collection' ] ? ? '' ) !== Database :: METADATA ) {
continue ;
}
if ( ! $dbForConsole -> getCollection ( $key ) -> isEmpty ()) {
continue ;
}
2022-05-23 14:54:50 +00:00
2024-10-08 07:54:40 +00:00
Console :: success ( '[Setup] - Creating collection: ' . $collection [ '$id' ] . '...' );
$attributes = [];
$indexes = [];
foreach ( $collection [ 'attributes' ] as $attribute ) {
$attributes [] = new Document ([
'$id' => ID :: custom ( $attribute [ '$id' ]),
'type' => $attribute [ 'type' ],
'size' => $attribute [ 'size' ],
'required' => $attribute [ 'required' ],
'signed' => $attribute [ 'signed' ],
'array' => $attribute [ 'array' ],
'filters' => $attribute [ 'filters' ],
'default' => $attribute [ 'default' ] ? ? null ,
'format' => $attribute [ 'format' ] ? ? ''
]);
2021-12-23 09:33:44 +00:00
}
2022-05-23 14:54:50 +00:00
2024-10-08 07:54:40 +00:00
foreach ( $collection [ 'indexes' ] as $index ) {
$indexes [] = new Document ([
'$id' => ID :: custom ( $index [ '$id' ]),
'type' => $index [ 'type' ],
'attributes' => $index [ 'attributes' ],
'lengths' => $index [ 'lengths' ],
'orders' => $index [ 'orders' ],
]);
}
2021-12-27 10:35:51 +00:00
2024-10-08 07:54:40 +00:00
$dbForConsole -> createCollection ( $key , $attributes , $indexes );
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
if ( $dbForConsole -> getDocument ( 'buckets' , 'default' ) -> isEmpty () && ! $dbForConsole -> exists ( $dbForConsole -> getDatabase (), 'bucket_1' )) {
Console :: success ( '[Setup] - Creating default bucket...' );
$dbForConsole -> createDocument ( 'buckets' , new Document ([
'$id' => ID :: custom ( 'default' ),
'$collection' => ID :: custom ( 'buckets' ),
'name' => 'Default' ,
'maximumFileSize' => ( int ) System :: getEnv ( '_APP_STORAGE_LIMIT' , 0 ), // 10MB
'allowedFileExtensions' => [],
'enabled' => true ,
'compression' => 'gzip' ,
'encryption' => true ,
'antivirus' => true ,
'fileSecurity' => true ,
'$permissions' => [
Permission :: create ( Role :: any ()),
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'search' => 'buckets Default' ,
]));
$bucket = $dbForConsole -> getDocument ( 'buckets' , 'default' );
Console :: success ( '[Setup] - Creating files collection for default bucket...' );
$files = $collections [ 'buckets' ][ 'files' ] ? ? [];
if ( empty ( $files )) {
throw new Exception ( 'Files collection is not configured.' );
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
$attributes = [];
$indexes = [];
foreach ( $files [ 'attributes' ] as $attribute ) {
$attributes [] = new Document ([
'$id' => ID :: custom ( $attribute [ '$id' ]),
'type' => $attribute [ 'type' ],
'size' => $attribute [ 'size' ],
'required' => $attribute [ 'required' ],
'signed' => $attribute [ 'signed' ],
'array' => $attribute [ 'array' ],
'filters' => $attribute [ 'filters' ],
'default' => $attribute [ 'default' ] ? ? null ,
'format' => $attribute [ 'format' ] ? ? ''
]);
2021-12-21 15:01:40 +00:00
}
2024-10-08 07:54:40 +00:00
foreach ( $files [ 'indexes' ] as $index ) {
$indexes [] = new Document ([
'$id' => ID :: custom ( $index [ '$id' ]),
'type' => $index [ 'type' ],
'attributes' => $index [ 'attributes' ],
'lengths' => $index [ 'lengths' ],
'orders' => $index [ 'orders' ],
]);
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
$dbForConsole -> createCollection ( 'bucket_' . $bucket -> getInternalId (), $attributes , $indexes );
2021-12-21 15:01:40 +00:00
}
2024-10-08 07:54:40 +00:00
$pools -> reclaim ();
Console :: success ( '[Setup] - Server database init completed...' );
2024-10-01 14:30:47 +00:00
});
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
Console :: success ( 'Server started successfully (max payload is ' . number_format ( $payloadSize ) . ' bytes)' );
Console :: info ( " Master pid { $http -> master_pid } , manager pid { $http -> manager_pid } " );
// listen ctrl + c
Process :: signal ( 2 , function () use ( $http ) {
Console :: log ( 'Stop by Ctrl+C' );
$http -> shutdown ();
2024-10-01 14:30:47 +00:00
});
2024-10-08 07:54:40 +00:00
});
$http -> on ( 'request' , function ( SwooleRequest $swooleRequest , SwooleResponse $swooleResponse ) use ( $register ) {
App :: setResource ( 'swooleRequest' , fn () => $swooleRequest );
App :: setResource ( 'swooleResponse' , fn () => $swooleResponse );
$request = new Request ( $swooleRequest );
$response = new Response ( $swooleResponse );
if ( Files :: isFileLoaded ( $request -> getURI ())) {
$time = ( 60 * 60 * 24 * 365 * 2 ); // 45 days cache
$response
-> setContentType ( Files :: getFileMimeType ( $request -> getURI ()))
-> addHeader ( 'Cache-Control' , 'public, max-age=' . $time )
-> addHeader ( 'Expires' , \date ( 'D, d M Y H:i:s' , \time () + $time ) . ' GMT' ) // 45 days cache
-> send ( Files :: getFileContents ( $request -> getURI ()));
return ;
}
$app = new App ( 'UTC' );
$pools = $register -> get ( 'pools' );
App :: setResource ( 'pools' , fn () => $pools );
try {
Authorization :: cleanRoles ();
Authorization :: setRole ( Role :: any () -> toString ());
$app -> run ( $request , $response );
} catch ( \Throwable $th ) {
$version = System :: getEnv ( '_APP_VERSION' , 'UNKNOWN' );
$logger = $app -> getResource ( " logger " );
if ( $logger ) {
try {
/** @var Utopia\Database\Document $user */
$user = $app -> getResource ( 'user' );
} catch ( \Throwable $_th ) {
// All good, user is optional information for logger
}
$route = $app -> getRoute ();
$log = $app -> getResource ( " log " );
if ( isset ( $user ) && ! $user -> isEmpty ()) {
$log -> setUser ( new User ( $user -> getId ()));
}
$log -> setNamespace ( " http " );
$log -> setServer ( \gethostname ());
$log -> setVersion ( $version );
$log -> setType ( Log :: TYPE_ERROR );
$log -> setMessage ( $th -> getMessage ());
$log -> addTag ( 'method' , $route -> getMethod ());
$log -> addTag ( 'url' , $route -> getPath ());
$log -> addTag ( 'verboseType' , get_class ( $th ));
$log -> addTag ( 'code' , $th -> getCode ());
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
$log -> addTag ( 'hostname' , $request -> getHostname ());
$log -> addTag ( 'locale' , ( string ) $request -> getParam ( 'locale' , $request -> getHeader ( 'x-appwrite-locale' , '' )));
$log -> addExtra ( 'file' , $th -> getFile ());
$log -> addExtra ( 'line' , $th -> getLine ());
$log -> addExtra ( 'trace' , $th -> getTraceAsString ());
$log -> addExtra ( 'roles' , Authorization :: getRoles ());
$action = $route -> getLabel ( " sdk.namespace " , " UNKNOWN_NAMESPACE " ) . '.' . $route -> getLabel ( " sdk.method " , " UNKNOWN_METHOD " );
$log -> setAction ( $action );
$isProduction = System :: getEnv ( '_APP_ENV' , 'development' ) === 'production' ;
$log -> setEnvironment ( $isProduction ? Log :: ENVIRONMENT_PRODUCTION : Log :: ENVIRONMENT_STAGING );
try {
$responseCode = $logger -> addLog ( $log );
Console :: info ( 'Error log pushed with status code: ' . $responseCode );
} catch ( Throwable $th ) {
Console :: error ( 'Error pushing log: ' . $th -> getMessage ());
}
}
Console :: error ( '[Error] Type: ' . get_class ( $th ));
Console :: error ( '[Error] Message: ' . $th -> getMessage ());
Console :: error ( '[Error] File: ' . $th -> getFile ());
Console :: error ( '[Error] Line: ' . $th -> getLine ());
$swooleResponse -> setStatusCode ( 500 );
$output = (( App :: isDevelopment ())) ? [
'message' => 'Error: ' . $th -> getMessage (),
'code' => 500 ,
'file' => $th -> getFile (),
'line' => $th -> getLine (),
'trace' => $th -> getTrace (),
'version' => $version ,
] : [
'message' => 'Error: Server Error' ,
'code' => 500 ,
'version' => $version ,
];
$swooleResponse -> end ( \json_encode ( $output ));
} finally {
$pools -> reclaim ();
}
});
2020-06-26 09:54:37 +00:00
2022-05-23 14:54:50 +00:00
$http -> start ();