2019-05-09 18:54:39 +12:00
< ? php
// Init
2019-10-25 06:53:37 +13:00
require_once __DIR__ . '/init.php' ;
global $env , $utopia , $request , $response , $register , $consoleDB , $project , $domain , $version , $service ;
2019-05-09 18:54:39 +12:00
use Utopia\App ;
use Utopia\Request ;
use Utopia\Response ;
2019-10-25 06:53:37 +13:00
use Utopia\Validator\Host ;
use Utopia\Validator\Range ;
use Utopia\View ;
use Utopia\Exception ;
2019-05-09 18:54:39 +12:00
use Auth\Auth ;
2019-12-16 07:56:29 +13:00
use Database\Database ;
2019-05-09 18:54:39 +12:00
use Database\Document ;
use Database\Validator\Authorization ;
2019-10-25 06:53:37 +13:00
use Event\Event ;
use Utopia\Validator\WhiteList ;
2019-05-09 18:54:39 +12:00
2019-10-01 17:57:41 +13:00
/*
2019-10-25 06:53:37 +13:00
* Configuration files
2019-05-09 18:54:39 +12:00
*/
2019-10-25 06:53:37 +13:00
$roles = include __DIR__ . '/config/roles.php' ; // User roles and scopes
$services = include __DIR__ . '/config/services.php' ; // List of services
2019-08-11 03:24:47 +12:00
2019-10-25 06:53:37 +13:00
$webhook = new Event ( 'v1-webhooks' , 'WebhooksV1' );
$audit = new Event ( 'v1-audits' , 'AuditsV1' );
$usage = new Event ( 'v1-usage' , 'UsageV1' );
2019-10-01 17:57:41 +13:00
2019-10-25 06:53:37 +13:00
/**
* Get All verified client URLs for both console and current projects
* + Filter for duplicated entries
2019-10-25 06:53:11 +13:00
*/
2019-10-25 06:53:37 +13:00
$clientsConsole = array_map ( function ( $node ) {
return $node [ 'url' ];
}, array_filter ( $console -> getAttribute ( 'platforms' , []), function ( $node ) {
if ( isset ( $node [ 'type' ]) && $node [ 'type' ] === 'web' && isset ( $node [ 'url' ]) && ! empty ( $node [ 'url' ])) {
return true ;
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
return false ;
}));
$clients = array_unique ( array_merge ( $clientsConsole , array_map ( function ( $node ) {
return $node [ 'url' ];
}, array_filter ( $project -> getAttribute ( 'platforms' , []), function ( $node ) {
if ( isset ( $node [ 'type' ]) && $node [ 'type' ] === 'web' && isset ( $node [ 'url' ]) && ! empty ( $node [ 'url' ])) {
return true ;
2019-05-09 18:54:39 +12:00
}
2019-10-25 06:53:37 +13:00
return false ;
}))));
2019-11-30 07:23:29 +13:00
$utopia -> init ( function () use ( $utopia , $request , $response , & $user , $project , $roles , $webhook , $audit , $usage , $domain , $clients ) {
2019-12-17 17:16:50 +13:00
2019-10-25 06:53:37 +13:00
$route = $utopia -> match ( $request );
$referrer = $request -> getServer ( 'HTTP_REFERER' , '' );
$origin = $request -> getServer ( 'HTTP_ORIGIN' , parse_url ( $referrer , PHP_URL_SCHEME ) . '://' . parse_url ( $referrer , PHP_URL_HOST ));
$refDomain = ( in_array ( $origin , $clients ))
? $origin : 'http://localhost' ;
/*
* Security Headers
*
* As recommended at :
* @ see https :// www . owasp . org / index . php / List_of_useful_HTTP_headers
*/
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'X-XSS-Protection' , '1; mode=block; report=/v1/xss?url=' . urlencode ( $request -> getServer ( 'REQUEST_URI' )))
//->addHeader('X-Frame-Options', ($refDomain == 'http://localhost') ? 'SAMEORIGIN' : 'ALLOW-FROM ' . $refDomain)
-> addHeader ( 'X-Content-Type-Options' , 'nosniff' )
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
-> addHeader ( 'Access-Control-Allow-Headers' , 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version' )
-> addHeader ( 'Access-Control-Allow-Origin' , $refDomain )
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' )
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
2020-01-29 23:49:18 +13:00
* Skip this check for non - web platforms which are not requiredto send an origin header
2019-10-25 06:53:37 +13:00
*/
$hostValidator = new Host ( $clients );
$origin = $request -> getServer ( 'HTTP_ORIGIN' , $request -> getServer ( 'HTTP_REFERER' , '' ));
2020-01-29 23:46:28 +13:00
if ( ! empty ( $origin ) && ! $hostValidator -> isValid ( $origin )
2019-10-25 06:53:37 +13:00
&& in_array ( $request -> getMethod (), [ Request :: METHOD_POST , Request :: METHOD_PUT , Request :: METHOD_PATCH , Request :: METHOD_DELETE ])
&& empty ( $request -> getHeader ( 'X-Appwrite-Key' , '' ))) {
throw new Exception ( 'Access from this client host is forbidden. ' . $hostValidator -> getDescription (), 403 );
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* ACL Check
*/
$role = ( $user -> isEmpty ()) ? Auth :: USER_ROLE_GUEST : Auth :: USER_ROLE_MEMBER ;
// Add user roles
$membership = $user -> search ( 'teamId' , $project -> getAttribute ( 'teamId' , null ), $user -> getAttribute ( 'memberships' , []));
if ( $membership ) {
foreach ( $membership -> getAttribute ( 'roles' , []) as $memberRole ) {
switch ( $memberRole ) {
case 'owner' :
$role = Auth :: USER_ROLE_OWNER ;
break ;
case 'admin' :
$role = Auth :: USER_ROLE_ADMIN ;
break ;
case 'developer' :
$role = Auth :: USER_ROLE_DEVELOPER ;
break ;
}
}
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$scope = $route -> getLabel ( 'scope' , 'none' ); // Allowed scope for chosen route
$scopes = $roles [ $role ][ 'scopes' ]; // Allowed scopes for user role
2020-01-12 10:53:57 +13:00
2019-10-25 06:53:37 +13:00
// Check if given key match project API keys
$key = $project -> search ( 'secret' , $request -> getHeader ( 'X-Appwrite-Key' , '' ), $project -> getAttribute ( 'keys' , []));
2020-01-12 10:53:57 +13:00
2019-10-25 06:53:37 +13:00
/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ( null !== $key && $user -> isEmpty ()) {
$user = new Document ([
2020-02-17 20:16:11 +13:00
'$id' => 0 ,
2019-10-25 06:53:37 +13:00
'status' => Auth :: USER_STATUS_ACTIVATED ,
2020-02-17 20:16:11 +13:00
'email' => 'app.' . $project -> getId () . '@service.' . $domain ,
2019-10-25 06:53:37 +13:00
'password' => '' ,
'name' => $project -> getAttribute ( 'name' , 'Untitled' ),
]);
$role = Auth :: USER_ROLE_APP ;
2020-01-13 04:20:51 +13:00
$scopes = array_merge ( $roles [ $role ][ 'scopes' ], $key -> getAttribute ( 'scopes' , []));
2019-10-25 06:53:37 +13:00
2020-02-11 19:17:40 +13:00
Authorization :: setDefaultStatus ( false ); // Cancel security segmentation for API keys.
2019-10-25 06:53:37 +13:00
}
2019-05-09 18:54:39 +12:00
2020-02-17 20:16:11 +13:00
Authorization :: setRole ( 'user:' . $user -> getId ());
2019-10-25 06:53:37 +13:00
Authorization :: setRole ( 'role:' . $role );
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
array_map ( function ( $node ) {
if ( isset ( $node [ 'teamId' ]) && isset ( $node [ 'roles' ])) {
Authorization :: setRole ( 'team:' . $node [ 'teamId' ]);
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
foreach ( $node [ 'roles' ] as $nodeRole ) { // Set all team roles
Authorization :: setRole ( 'team:' . $node [ 'teamId' ] . '/' . $nodeRole );
}
}
}, $user -> getAttribute ( 'memberships' , []));
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
// TDOO Check if user is god
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( ! in_array ( $scope , $scopes )) {
2020-02-17 20:16:11 +13:00
if ( empty ( $project -> getId ()) || Database :: SYSTEM_COLLECTION_PROJECTS !== $project -> getCollection ()) { // Check if permission is denied because project is missing
2019-12-17 17:16:50 +13:00
throw new Exception ( 'Project not found' , 404 );
}
2019-10-25 06:53:37 +13:00
throw new Exception ( $user -> getAttribute ( 'email' , 'Guest' ) . ' (role: ' . strtolower ( $roles [ $role ][ 'label' ]) . ') missing scope (' . $scope . ')' , 401 );
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( Auth :: USER_STATUS_BLOCKED == $user -> getAttribute ( 'status' )) { // Account has not been activated
throw new Exception ( 'Invalid credentials. User is blocked' , 401 ); // User is in status blocked
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
if ( $user -> getAttribute ( 'reset' )) {
throw new Exception ( 'Password reset is required' , 412 );
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Background Jobs
*/
$webhook
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'event' , $route -> getLabel ( 'webhook' , '' ))
-> setParam ( 'payload' , [])
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$audit
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
-> setParam ( 'userId' , $user -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'event' , '' )
-> setParam ( 'resource' , '' )
-> setParam ( 'userAgent' , $request -> getServer ( 'HTTP_USER_AGENT' , '' ))
-> setParam ( 'ip' , $request -> getIP ())
-> setParam ( 'data' , [])
;
2019-06-09 01:13:19 +12:00
2019-10-25 06:53:37 +13:00
$usage
2020-02-17 20:16:11 +13:00
-> setParam ( 'projectId' , $project -> getId ())
2019-10-25 06:53:37 +13:00
-> setParam ( 'url' , $request -> getServer ( 'HTTP_HOST' , '' ) . $request -> getServer ( 'REQUEST_URI' , '' ))
-> setParam ( 'method' , $request -> getServer ( 'REQUEST_METHOD' , 'UNKNOWN' ))
-> setParam ( 'request' , 0 )
-> setParam ( 'response' , 0 )
-> setParam ( 'storage' , 0 )
;
});
2019-05-09 18:54:39 +12:00
2020-02-13 21:04:52 +13:00
$utopia -> shutdown ( function () use ( $response , $request , $webhook , $audit , $usage , $mode , $project , $utopia ) {
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
/*
* Trigger Events for background jobs
*/
if ( ! empty ( $webhook -> getParam ( 'event' ))) {
$webhook -> trigger ();
}
2020-01-12 02:58:02 +13:00
2019-10-25 06:53:37 +13:00
if ( ! empty ( $audit -> getParam ( 'event' ))) {
$audit -> trigger ();
}
2020-02-13 21:04:52 +13:00
$route = $utopia -> match ( $request );
2019-05-09 18:54:39 +12:00
2020-02-17 20:16:11 +13:00
if ( $project -> getId ()
2020-02-13 21:04:52 +13:00
&& $mode !== APP_MODE_ADMIN
&& ! empty ( $route -> getLabel ( 'sdk.namespace' , null ))) { // Don't calculate console usage and admin mode
$usage
-> setParam ( 'request' , $request -> getSize ())
-> setParam ( 'response' , $response -> getSize ())
-> trigger ()
;
}
2019-10-25 06:53:37 +13:00
});
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$utopia -> options ( function () use ( $request , $response , $domain , $project ) {
$origin = $request -> getServer ( 'HTTP_ORIGIN' );
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$response
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
-> addHeader ( 'Access-Control-Allow-Headers' , 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version' )
-> addHeader ( 'Access-Control-Allow-Origin' , $origin )
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' )
-> send ();
});
2019-05-09 18:54:39 +12:00
2019-11-29 20:35:26 +13:00
$utopia -> error ( function ( $error /* @var $error Exception */ ) use ( $request , $response , $utopia , $project , $env , $version ) {
2019-10-25 06:53:37 +13:00
switch ( $error -> getCode ()) {
case 400 : // Error allowed publicly
case 401 : // Error allowed publicly
case 402 : // Error allowed publicly
case 403 : // Error allowed publicly
case 404 : // Error allowed publicly
2020-01-04 10:01:09 +13:00
case 409 : // Error allowed publicly
2019-10-25 06:53:37 +13:00
case 412 : // Error allowed publicly
case 429 : // Error allowed publicly
$code = $error -> getCode ();
$message = $error -> getMessage ();
break ;
default :
$code = 500 ; // All other errors get the generic 500 server error status code
$message = 'Server Error' ;
}
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$output = (( App :: ENV_TYPE_DEVELOPMENT == $env )) ? [
'message' => $error -> getMessage (),
'code' => $error -> getCode (),
'file' => $error -> getFile (),
'line' => $error -> getLine (),
'trace' => $error -> getTrace (),
'version' => $version ,
] : [
'message' => $message ,
'code' => $code ,
'version' => $version ,
];
$response
-> addHeader ( 'Cache-Control' , 'no-cache, no-store, must-revalidate' )
-> addHeader ( 'Expires' , '0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> setStatusCode ( $code )
2019-10-25 06:53:11 +13:00
;
2019-05-09 18:54:39 +12:00
2019-10-25 06:53:37 +13:00
$route = $utopia -> match ( $request );
$template = ( $route ) ? $route -> getLabel ( 'error' , null ) : null ;
if ( $template ) {
$layout = new View ( __DIR__ . '/views/layouts/default.phtml' );
$comp = new View ( $template );
$comp
-> setParam ( 'projectName' , $project -> getAttribute ( 'name' ))
-> setParam ( 'projectURL' , $project -> getAttribute ( 'url' ))
-> setParam ( 'message' , $error -> getMessage ())
-> setParam ( 'code' , $code )
;
$layout
-> setParam ( 'title' , $project -> getAttribute ( 'name' ) . ' - Error' )
-> setParam ( 'description' , 'No Description' )
-> setParam ( 'body' , $comp )
-> setParam ( 'version' , $version )
-> setParam ( 'litespeed' , false )
;
$response -> send ( $layout -> render ());
2019-10-25 06:53:11 +13:00
}
2019-10-25 06:53:37 +13:00
$response
-> json ( $output )
;
});
$utopia -> get ( '/manifest.json' )
-> desc ( 'Progressive app manifest file' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
$response -> json ([
'name' => APP_NAME ,
'short_name' => APP_NAME ,
'start_url' => '.' ,
'url' => 'https://appwrite.io/' ,
'display' => 'standalone' ,
'background_color' => '#fff' ,
'theme_color' => '#f02e65' ,
'description' => 'End to end backend server for frontend and mobile apps. 👩💻👨💻' ,
'icons' => [
[
'src' => 'images/favicon.png' ,
'sizes' => '256x256' ,
'type' => 'image/png' ,
],
],
]);
}
);
$utopia -> get ( '/robots.txt' )
-> desc ( 'Robots.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
2020-02-15 18:22:43 +13:00
$template = new View ( __DIR__ . '/views/general/robots.phtml' );
$response -> text ( $template -> render ( false ));
2019-10-25 06:53:37 +13:00
}
);
$utopia -> get ( '/humans.txt' )
-> desc ( 'Humans.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response ) {
2020-02-15 18:22:43 +13:00
$template = new View ( __DIR__ . '/views/general/humans.phtml' );
$response -> text ( $template -> render ( false ));
2019-10-25 06:53:37 +13:00
}
);
2020-02-19 11:13:18 +13:00
$utopia -> get ( '/.well-known/acme-challenge' )
-> desc ( 'SSL Verification' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $request , $response ) {
$base = realpath ( __DIR__ . '/../certs' );
$path = str_replace ( '/.well-known/acme-challenge/' , '' , $request -> getParam ( 'q' ));
$absolute = realpath ( $base . '/' . $path );
if ( ! $absolute ) {
throw new Exception ( 'Unknown Path' , 404 );
}
if ( ! substr ( $absolute , 0 , strlen ( $base )) === $base ) {
throw new Exception ( 'Invalid Path' , 401 );
}
$response -> text ( file_get_contents ( $absolute ));
}
);
2019-10-25 06:53:37 +13:00
$utopia -> get ( '/v1/info' ) // This is only visible to gods
2020-01-12 02:58:02 +13:00
-> label ( 'scope' , 'god' )
2019-10-25 06:53:37 +13:00
-> label ( 'docs' , false )
-> action (
2020-02-15 18:22:43 +13:00
function () use ( $response , $user , $project , $version , $env ) {
2019-10-25 06:53:37 +13:00
$response -> json ([
'name' => 'API' ,
'version' => $version ,
'environment' => $env ,
'time' => date ( 'Y-m-d H:i:s' , time ()),
'user' => [
2020-02-17 20:16:11 +13:00
'id' => $user -> getId (),
2019-10-25 06:53:37 +13:00
'name' => $user -> getAttribute ( 'name' , '' ),
],
'project' => [
2020-02-17 20:16:11 +13:00
'id' => $project -> getId (),
2019-10-25 06:53:37 +13:00
'name' => $project -> getAttribute ( 'name' , '' ),
],
]);
}
);
$utopia -> get ( '/v1/xss' )
-> desc ( 'Log XSS errors reported by browsers using X-XSS-Protection header' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () {
throw new Exception ( 'XSS detected and reported by a browser client' , 500 );
}
);
$utopia -> get ( '/v1/proxy' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response , $console , $clients ) {
$view = new View ( __DIR__ . '/views/proxy.phtml' );
$view
-> setParam ( 'routes' , '' )
-> setParam ( 'clients' , array_merge ( $clients , $console -> getAttribute ( 'clients' , [])))
;
$response
-> setContentType ( Response :: CONTENT_TYPE_HTML )
-> removeHeader ( 'X-Frame-Options' )
-> send ( $view -> render ());
}
);
2020-01-12 02:58:02 +13:00
$utopia -> get ( '/v1/open-api-2.json' )
2019-10-25 06:53:37 +13:00
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2020-01-31 05:18:46 +13:00
-> param ( 'platform' , 'client' , function () { return new WhiteList ([ APP_PLATFORM_CLIENT , APP_PLATFORM_SERVER , APP_PLATFORM_CONSOLE ]);}, 'Choose target platform.' , true )
2019-10-25 06:53:37 +13:00
-> param ( 'extensions' , 0 , function () { return new Range ( 0 , 1 );}, 'Show extra data.' , true )
-> param ( 'tests' , 0 , function () { return new Range ( 0 , 1 );}, 'Include only test services.' , true )
-> action (
function ( $platform , $extensions , $tests ) use ( $response , $request , $utopia , $domain , $services ) {
function fromCamelCase ( $input )
{
preg_match_all ( '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!' , $input , $matches );
$ret = $matches [ 0 ];
foreach ( $ret as & $match ) {
$match = $match == strtoupper ( $match ) ? strtolower ( $match ) : lcfirst ( $match );
}
return implode ( '_' , $ret );
}
function fromCamelCaseToDash ( $input )
{
return str_replace ([ ' ' , '_' ], '-' , strtolower ( preg_replace ( '/([a-zA-Z])(?=[A-Z])/' , '$1-' , $input )));
}
foreach ( $services as $service ) { /* @noinspection PhpIncludeInspection */
if ( $tests && ! $service [ 'tests' ]) {
continue ;
}
if ( ! $tests && ! $service [ 'sdk' ]) {
continue ;
}
2019-12-17 08:35:33 +13:00
2019-10-25 06:53:37 +13:00
/** @noinspection PhpIncludeInspection */
include_once $service [ 'controller' ];
}
$security = [
2020-01-31 05:18:46 +13:00
APP_PLATFORM_CLIENT => [ 'Project' => []],
APP_PLATFORM_SERVER => [ 'Project' => [], 'Key' => []],
APP_PLATFORM_CONSOLE => [ 'Project' => [], 'Key' => []],
2019-10-25 06:53:37 +13:00
];
2020-01-29 01:56:09 +13:00
$platforms = [
'client' => APP_PLATFORM_CLIENT ,
'server' => APP_PLATFORM_SERVER ,
2020-01-31 05:18:46 +13:00
'all' => APP_PLATFORM_CONSOLE ,
2020-01-29 01:56:09 +13:00
];
2019-10-25 06:53:37 +13:00
/*
* Specifications ( v3 . 0.0 ) :
* https :// github . com / OAI / OpenAPI - Specification / blob / master / versions / 3.0 . 0. md
*/
$output = [
'swagger' => '2.0' ,
'info' => [
'version' => APP_VERSION_STABLE ,
'title' => APP_NAME ,
'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)' ,
'termsOfService' => 'https://appwrite.io/policy/terms' ,
'contact' => [
'name' => 'Appwrite Team' ,
'url' => 'https://appwrite.io/support' ,
'email' => APP_EMAIL_TEAM ,
],
'license' => [
'name' => 'BSD-3-Clause' ,
'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE' ,
],
],
'host' => parse_url ( $request -> getServer ( '_APP_HOME' , $domain ), PHP_URL_HOST ),
'basePath' => '/v1' ,
'schemes' => [ 'https' ],
'consumes' => [ 'application/json' , 'multipart/form-data' ],
'produces' => [ 'application/json' ],
'securityDefinitions' => [
'Project' => [
'type' => 'apiKey' ,
'name' => 'X-Appwrite-Project' ,
2020-02-16 07:22:34 +13:00
'description' => 'Your project ID' ,
2019-10-25 06:53:37 +13:00
'in' => 'header' ,
],
'Key' => [
'type' => 'apiKey' ,
'name' => 'X-Appwrite-Key' ,
2020-02-16 07:22:34 +13:00
'description' => 'Your secret API key' ,
2019-10-25 06:53:37 +13:00
'in' => 'header' ,
],
'Locale' => [
'type' => 'apiKey' ,
'name' => 'X-Appwrite-Locale' ,
'description' => '' ,
'in' => 'header' ,
],
'Mode' => [
'type' => 'apiKey' ,
'name' => 'X-Appwrite-Mode' ,
'description' => '' ,
'in' => 'header' ,
],
],
'paths' => [],
'definitions' => [
2019-12-06 06:43:16 +13:00
// 'Pet' => [
// 'required' => ['id', 'name'],
// 'properties' => [
// 'id' => [
// 'type' => 'integer',
// 'format' => 'int64',
// ],
// 'name' => [
// 'type' => 'string',
// ],
// 'tag' => [
// 'type' => 'string',
// ],
// ],
// ],
// 'Pets' => array(
// 'type' => 'array',
// 'items' => array(
// '$ref' => '#/definitions/Pet',
// ),
// ),
2019-10-25 06:53:37 +13:00
'Error' => array (
'required' => array (
0 => 'code' ,
1 => 'message' ,
),
'properties' => array (
'code' => array (
'type' => 'integer' ,
'format' => 'int32' ,
),
'message' => array (
'type' => 'string' ,
),
),
),
],
'externalDocs' => [
'description' => 'Full API docs, specs and tutorials' ,
'url' => $request -> getServer ( 'REQUEST_SCHEME' , 'https' ) . '://' . $domain . '/docs' ,
],
];
2020-01-29 01:56:09 +13:00
if ( $extensions ) {
$output [ 'securityDefinitions' ][ 'Project' ][ 'extensions' ] = [ 'demo' => '5df5acd0d48c2' ];
$output [ 'securityDefinitions' ][ 'Key' ][ 'extensions' ] = [ 'demo' => '919c2d18fb5d4...a2ae413da83346ad2' ];
$output [ 'securityDefinitions' ][ 'Locale' ][ 'extensions' ] = [ 'demo' => 'en' ];
$output [ 'securityDefinitions' ][ 'Mode' ][ 'extensions' ] = [ 'demo' => '' ];
}
2019-10-25 06:53:37 +13:00
foreach ( $utopia -> getRoutes () as $key => $method ) {
foreach ( $method as $route ) { /* @var $route \Utopia\Route */
if ( ! $route -> getLabel ( 'docs' , true )) {
continue ;
}
if ( empty ( $route -> getLabel ( 'sdk.namespace' , null ))) {
continue ;
}
2020-01-31 05:18:46 +13:00
if ( $platform !== APP_PLATFORM_CONSOLE && ! in_array ( $platforms [ $platform ], $route -> getLabel ( 'sdk.platform' , []))) {
2020-01-29 01:56:09 +13:00
continue ;
}
2019-10-25 06:53:37 +13:00
$url = str_replace ( '/v1' , '' , $route -> getURL ());
$scope = $route -> getLabel ( 'scope' , '' );
$hide = $route -> getLabel ( 'sdk.hide' , false );
$consumes = [ 'application/json' ];
if ( $hide ) {
continue ;
}
2019-12-13 18:51:03 +13:00
$desc = ( ! empty ( $route -> getLabel ( 'sdk.description' , '' ))) ? realpath ( __DIR__ . '/..' . $route -> getLabel ( 'sdk.description' , '' )) : null ;
2019-10-25 06:53:37 +13:00
$temp = [
'summary' => $route -> getDesc (),
'operationId' => $route -> getLabel ( 'sdk.method' , uniqid ()),
'consumes' => [],
'tags' => [ $route -> getLabel ( 'sdk.namespace' , 'default' )],
'description' => ( $desc ) ? file_get_contents ( $desc ) : '' ,
2019-12-14 17:14:40 +13:00
// 'responses' => [
// 200 => [
// 'description' => 'An paged array of pets',
// 'schema' => [
// '$ref' => '#/definitions/Pet',
// ],
// ],
// ],
2019-10-25 06:53:37 +13:00
];
if ( $extensions ) {
2020-02-08 12:08:14 +13:00
$platformList = $route -> getLabel ( 'sdk.platform' , []);
if ( in_array ( APP_PLATFORM_CLIENT , $platformList )) {
$platformList = array_merge ( $platformList , [
APP_PLATFORM_WEB ,
APP_PLATFORM_IOS ,
APP_PLATFORM_ANDROID ,
APP_PLATFORM_FLUTTER ,
]);
}
2019-10-25 06:53:37 +13:00
$temp [ 'extensions' ] = [
'weight' => $route -> getOrder (),
2019-12-14 17:14:40 +13:00
'cookies' => $route -> getLabel ( 'sdk.cookies' , false ),
2019-10-25 06:53:37 +13:00
'location' => $route -> getLabel ( 'sdk.location' , false ),
'demo' => 'docs/examples/' . fromCamelCaseToDash ( $route -> getLabel ( 'sdk.namespace' , 'default' )) . '/' . fromCamelCaseToDash ( $temp [ 'operationId' ]) . '.md' ,
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route -> getLabel ( 'sdk.description' , '' ),
'rate-limit' => $route -> getLabel ( 'abuse-limit' , 0 ),
'rate-time' => $route -> getLabel ( 'abuse-time' , 3600 ),
'scope' => $route -> getLabel ( 'scope' , '' ),
2020-02-08 12:08:14 +13:00
'platforms' => $platformList ,
2019-10-25 06:53:37 +13:00
];
}
2020-02-16 19:25:48 +13:00
if (( ! empty ( $scope ))) { // && 'public' != $scope
2019-10-25 06:53:37 +13:00
$temp [ 'security' ][] = $route -> getLabel ( 'sdk.security' , $security [ $platform ]);
}
$requestBody = [
'content' => [
'application/x-www-form-urlencoded' => [
'schema' => [
'type' => 'object' ,
'properties' => [],
],
'required' => [],
],
],
];
foreach ( $route -> getParams () as $name => $param ) {
$validator = ( is_callable ( $param [ 'validator' ])) ? $param [ 'validator' ]() : $param [ 'validator' ]; /* @var $validator \Utopia\Validator */
$node = [
'name' => $name ,
'description' => $param [ 'description' ],
'required' => ! $param [ 'optional' ],
];
switch (( ! empty ( $validator )) ? get_class ( $validator ) : '' ) {
case 'Utopia\Validator\Text' :
$node [ 'type' ] = 'string' ;
$node [ 'x-example' ] = '[' . strtoupper ( fromCamelCase ( $node [ 'name' ])) . ']' ;
break ;
case 'Database\Validator\UID' :
$node [ 'type' ] = 'string' ;
$node [ 'x-example' ] = '[' . strtoupper ( fromCamelCase ( $node [ 'name' ])) . ']' ;
break ;
case 'Utopia\Validator\Email' :
$node [ 'type' ] = 'string' ;
$node [ 'format' ] = 'email' ;
$node [ 'x-example' ] = 'email@example.com' ;
break ;
case 'Utopia\Validator\URL' :
$node [ 'type' ] = 'string' ;
$node [ 'format' ] = 'url' ;
$node [ 'x-example' ] = 'https://example.com' ;
break ;
case 'Utopia\Validator\JSON' :
case 'Utopia\Validator\Mock' :
2020-02-08 12:08:14 +13:00
case 'Utopia\Validator\Assoc' :
$node [ 'type' ] = 'object' ;
2019-10-25 06:53:37 +13:00
$node [ 'type' ] = 'object' ;
$node [ 'x-example' ] = '{}' ;
//$node['format'] = 'json';
break ;
case 'Storage\Validators\File' :
$consumes = [ 'multipart/form-data' ];
$node [ 'type' ] = 'file' ;
break ;
case 'Utopia\Validator\ArrayList' :
$node [ 'type' ] = 'array' ;
$node [ 'collectionFormat' ] = 'multi' ;
$node [ 'items' ] = [
'type' => 'string' ,
];
break ;
case 'Auth\Validator\Password' :
$node [ 'type' ] = 'string' ;
$node [ 'format' ] = 'format' ;
$node [ 'x-example' ] = 'password' ;
break ;
case 'Utopia\Validator\Range' : /* @var $validator \Utopia\Validator\Range */
$node [ 'type' ] = 'integer' ;
$node [ 'format' ] = 'int32' ;
$node [ 'x-example' ] = $validator -> getMin ();
break ;
case 'Utopia\Validator\Numeric' :
$node [ 'type' ] = 'integer' ;
$node [ 'format' ] = 'int32' ;
break ;
case 'Utopia\Validator\Length' :
$node [ 'type' ] = 'string' ;
break ;
case 'Utopia\Validator\Host' :
$node [ 'type' ] = 'string' ;
$node [ 'format' ] = 'url' ;
$node [ 'x-example' ] = 'https://example.com' ;
break ;
case 'Utopia\Validator\WhiteList' : /* @var $validator \Utopia\Validator\WhiteList */
$node [ 'type' ] = 'string' ;
$node [ 'x-example' ] = $validator -> getList ()[ 0 ];
break ;
default :
$node [ 'type' ] = 'string' ;
break ;
}
if ( $param [ 'optional' ] && ! is_null ( $param [ 'default' ])) { // Param has default value
$node [ 'default' ] = $param [ 'default' ];
}
if ( false !== strpos ( $url , ':' . $name )) { // Param is in URL path
$node [ 'in' ] = 'path' ;
$temp [ 'parameters' ][] = $node ;
} elseif ( $key == 'GET' ) { // Param is in query
$node [ 'in' ] = 'query' ;
$temp [ 'parameters' ][] = $node ;
} else { // Param is in payload
$node [ 'in' ] = 'formData' ;
$temp [ 'parameters' ][] = $node ;
$requestBody [ 'content' ][ 'application/x-www-form-urlencoded' ][ 'schema' ][ 'properties' ][] = $node ;
if ( ! $param [ 'optional' ]) {
$requestBody [ 'content' ][ 'application/x-www-form-urlencoded' ][ 'required' ][] = $name ;
}
}
$url = str_replace ( ':' . $name , '{' . $name . '}' , $url );
}
$temp [ 'consumes' ] = $consumes ;
$output [ 'paths' ][ $url ][ strtolower ( $route -> getMethod ())] = $temp ;
}
}
/* foreach ( $consoleDB -> getMocks () as $mock ) {
var_dump ( $mock [ 'name' ]);
} */
ksort ( $output [ 'paths' ]);
2019-12-17 08:35:33 +13:00
$response
-> json ( $output );
2019-10-25 06:53:37 +13:00
}
);
2020-01-05 04:45:59 +13:00
$utopia -> get ( '/v1/debug' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
-> action (
function () use ( $response , $request , $utopia , $domain , $services ) {
$output = [
2020-01-12 02:58:02 +13:00
'scopes' => [],
2020-01-05 04:45:59 +13:00
'webhooks' => [],
'methods' => [],
'routes' => [],
2020-01-06 00:28:44 +13:00
'docs' => [],
2020-01-05 04:45:59 +13:00
];
foreach ( $services as $service ) { /* @noinspection PhpIncludeInspection */
/** @noinspection PhpIncludeInspection */
if ( $service [ 'tests' ]) {
continue ;
}
include_once $service [ 'controller' ];
}
$i = 0 ;
foreach ( $utopia -> getRoutes () as $key => $method ) {
foreach ( $method as $route ) { /* @var $route \Utopia\Route */
if ( ! $route -> getLabel ( 'docs' , true )) {
continue ;
}
if ( empty ( $route -> getLabel ( 'sdk.namespace' , null ))) {
continue ;
}
2020-01-12 02:58:02 +13:00
if ( $route -> getLabel ( 'scope' , false )) {
$output [ 'scopes' ][ $route -> getLabel ( 'scope' , false )] = $route -> getMethod () . ' ' . $route -> getURL ();
}
2020-01-06 00:28:44 +13:00
if ( $route -> getLabel ( 'sdk.description' , false )) {
2020-01-06 12:22:02 +13:00
if ( ! realpath ( __DIR__ . '/../' . $route -> getLabel ( 'sdk.description' , false ))) {
throw new Exception ( 'Docs file (' . $route -> getLabel ( 'sdk.description' , false ) . ') is missing' , 500 );
}
2020-01-06 00:28:44 +13:00
if ( array_key_exists ( $route -> getLabel ( 'sdk.description' , false ), $output [ 'docs' ])) {
throw new Exception ( 'Docs file (' . $route -> getLabel ( 'sdk.description' , false ) . ') is already in use by another route' , 500 );
}
$output [ 'docs' ][ $route -> getLabel ( 'sdk.description' , false )] = $route -> getMethod () . ' ' . $route -> getURL ();
}
2020-01-05 04:45:59 +13:00
if ( $route -> getLabel ( 'webhook' , false )) {
if ( array_key_exists ( $route -> getLabel ( 'webhook' , false ), $output [ 'webhooks' ])) {
2020-01-06 00:28:44 +13:00
//throw new Exception('Webhook ('.$route->getLabel('webhook', false).') is already in use by another route', 500);
2020-01-05 04:45:59 +13:00
}
$output [ 'webhooks' ][ $route -> getLabel ( 'webhook' , false )] = $route -> getMethod () . ' ' . $route -> getURL ();
}
if ( $route -> getLabel ( 'sdk.namespace' , false )) {
$method = $route -> getLabel ( 'sdk.namespace' , false ) . '->' . $route -> getLabel ( 'sdk.method' , false ) . '()' ;
if ( array_key_exists ( $method , $output [ 'methods' ])) {
throw new Exception ( 'Method (' . $method . ') is already in use by another route' , 500 );
}
$output [ 'methods' ][ $method ] = $route -> getMethod () . ' ' . $route -> getURL ();
}
$output [ 'routes' ][ $route -> getURL () . ' (' . $route -> getMethod () . ')' ] = [];
$i ++ ;
}
}
2020-01-12 02:58:02 +13:00
ksort ( $output [ 'scopes' ]);
2020-01-05 04:45:59 +13:00
ksort ( $output [ 'webhooks' ]);
ksort ( $output [ 'methods' ]);
ksort ( $output [ 'routes' ]);
2020-01-06 00:28:44 +13:00
ksort ( $output [ 'docs' ]);
2020-01-05 04:45:59 +13:00
$response
-> json ( $output );
}
);
2019-10-25 06:53:37 +13:00
$name = APP_NAME ;
if ( array_key_exists ( $service , $services )) { /** @noinspection PhpIncludeInspection */
include_once $services [ $service ][ 'controller' ];
$name = APP_NAME . ' ' . ucfirst ( $services [ $service ][ 'name' ]);
} else {
/** @noinspection PhpIncludeInspection */
include_once $services [ '/' ][ 'controller' ];
2019-05-09 18:54:39 +12:00
}
2019-10-25 06:53:37 +13:00
$utopia -> run ( $request , $response );