2019-05-09 06:54:39 +00:00
< ? php
2023-06-15 05:29:03 +00:00
use Appwrite\Auth\Auth ;
use Appwrite\Detector\Detector ;
use Appwrite\Event\Database as EventDatabase ;
use Appwrite\Event\Event ;
2025-01-30 04:53:53 +00:00
use Appwrite\Event\StatsUsage ;
2022-02-06 16:50:48 +00:00
use Appwrite\Extend\Exception ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\AuthType ;
use Appwrite\SDK\ContentType ;
use Appwrite\SDK\Method ;
use Appwrite\SDK\Response as SDKResponse ;
2023-06-15 05:29:03 +00:00
use Appwrite\Utopia\Database\Validator\CustomId ;
use Appwrite\Utopia\Database\Validator\Queries\Collections ;
use Appwrite\Utopia\Database\Validator\Queries\Databases ;
2023-07-21 11:10:44 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Indexes ;
2023-10-19 03:19:40 +00:00
use Appwrite\Utopia\Request ;
2023-06-15 05:29:03 +00:00
use Appwrite\Utopia\Response ;
use MaxMind\Db\Reader ;
use Utopia\App ;
2021-08-15 21:09:40 +00:00
use Utopia\Audit\Audit ;
2023-06-15 05:29:03 +00:00
use Utopia\Config\Config ;
2021-08-16 23:21:00 +00:00
use Utopia\Database\Database ;
2025-02-25 05:51:11 +00:00
use Utopia\Database\DateTime ;
2021-08-16 23:21:00 +00:00
use Utopia\Database\Document ;
2023-06-15 05:29:03 +00:00
use Utopia\Database\Exception\Authorization as AuthorizationException ;
use Utopia\Database\Exception\Duplicate as DuplicateException ;
use Utopia\Database\Exception\Limit as LimitException ;
2024-11-04 07:57:08 +00:00
use Utopia\Database\Exception\NotFound as NotFoundException ;
2025-04-16 11:33:14 +00:00
use Utopia\Database\Exception\Order as OrderException ;
2024-09-09 07:17:47 +00:00
use Utopia\Database\Exception\Query as QueryException ;
2023-06-15 05:29:03 +00:00
use Utopia\Database\Exception\Structure as StructureException ;
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2021-08-16 23:21:00 +00:00
use Utopia\Database\Query ;
2021-08-12 01:05:19 +00:00
use Utopia\Database\Validator\Authorization ;
2023-06-15 05:29:03 +00:00
use Utopia\Database\Validator\Index as IndexValidator ;
2021-06-22 19:34:42 +00:00
use Utopia\Database\Validator\Key ;
2021-07-05 20:27:20 +00:00
use Utopia\Database\Validator\Permissions ;
2023-06-15 05:29:03 +00:00
use Utopia\Database\Validator\Queries ;
2024-05-09 16:54:28 +00:00
use Utopia\Database\Validator\Query\Cursor ;
2023-06-15 05:29:03 +00:00
use Utopia\Database\Validator\Query\Limit ;
use Utopia\Database\Validator\Query\Offset ;
2021-07-05 20:27:20 +00:00
use Utopia\Database\Validator\UID ;
2022-05-25 12:10:10 +00:00
use Utopia\Locale\Locale ;
2023-06-15 05:29:03 +00:00
use Utopia\Validator\ArrayList ;
use Utopia\Validator\Boolean ;
use Utopia\Validator\JSON ;
use Utopia\Validator\Text ;
use Utopia\Validator\WhiteList ;
2021-07-27 01:00:36 +00:00
2023-10-19 03:19:40 +00:00
App :: init ()
-> groups ([ 'api' , 'database' ])
-> inject ( 'request' )
-> inject ( 'dbForProject' )
-> action ( function ( Request $request , Database $dbForProject ) {
$timeout = \intval ( $request -> getHeader ( 'x-appwrite-timeout' ));
if ( ! empty ( $timeout ) && App :: isDevelopment ()) {
$dbForProject -> setTimeout ( $timeout );
}
});
2022-06-22 10:51:49 +00:00
App :: post ( '/v1/databases' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Create database' )
2022-06-22 10:51:49 +00:00
-> groups ([ 'api' , 'database' ])
-> label ( 'event' , 'databases.[databaseId].create' )
-> label ( 'scope' , 'databases.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'database.create' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'database/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'databases' ,
2025-01-17 04:31:39 +00:00
name : 'create' ,
description : '/docs/references/databases/create.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_DATABASE ,
)
],
contentType : ContentType :: JSON
))
2023-01-20 22:22:16 +00:00
-> param ( 'databaseId' , '' , new CustomId (), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
2023-05-18 13:25:22 +00:00
-> param ( 'name' , '' , new Text ( 128 ), 'Database name. Max length: 128 chars.' )
2023-08-22 20:11:33 +00:00
-> param ( 'enabled' , true , new Boolean (), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.' , true )
2022-06-22 10:51:49 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-04-27 05:03:44 +00:00
-> action ( function ( string $databaseId , string $name , bool $enabled , Response $response , Database $dbForProject , Event $queueForEvents ) {
2022-06-22 10:51:49 +00:00
2022-08-14 14:22:38 +00:00
$databaseId = $databaseId == 'unique()' ? ID :: unique () : $databaseId ;
2022-06-22 10:51:49 +00:00
try {
$dbForProject -> createDocument ( 'databases' , new Document ([
2022-08-14 14:22:38 +00:00
'$id' => $databaseId ,
2022-06-22 10:51:49 +00:00
'name' => $name ,
2023-05-18 13:25:22 +00:00
'enabled' => $enabled ,
2022-06-22 10:51:49 +00:00
'search' => implode ( ' ' , [ $databaseId , $name ]),
]));
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
2023-06-15 00:34:12 +00:00
$collections = ( Config :: getParam ( 'collections' , [])[ 'databases' ] ? ? [])[ 'collections' ] ? ? [];
2022-06-22 10:51:49 +00:00
if ( empty ( $collections )) {
2022-08-14 06:56:12 +00:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'The "collections" collection is not configured.' );
2022-06-22 10:51:49 +00:00
}
$attributes = [];
$indexes = [];
foreach ( $collections [ 'attributes' ] as $attribute ) {
$attributes [] = new Document ([
2022-08-15 11:24:31 +00:00
'$id' => $attribute [ '$id' ],
2022-06-22 10:51:49 +00:00
'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' ] ? ? ''
]);
}
foreach ( $collections [ 'indexes' ] as $index ) {
$indexes [] = new Document ([
2022-08-15 11:24:31 +00:00
'$id' => $index [ '$id' ],
2022-06-22 10:51:49 +00:00
'type' => $index [ 'type' ],
'attributes' => $index [ 'attributes' ],
'lengths' => $index [ 'lengths' ],
'orders' => $index [ 'orders' ],
]);
}
$dbForProject -> createCollection ( 'database_' . $database -> getInternalId (), $attributes , $indexes );
2023-06-15 05:29:03 +00:00
} catch ( DuplicateException ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_ALREADY_EXISTS );
2022-06-22 10:51:49 +00:00
}
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'databaseId' , $database -> getId ());
2022-06-22 10:51:49 +00:00
2022-09-07 11:02:36 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $database , Response :: MODEL_DATABASE );
2022-06-22 10:51:49 +00:00
});
App :: get ( '/v1/databases' )
2023-10-02 14:02:48 +00:00
-> desc ( 'List databases' )
2022-06-22 10:51:49 +00:00
-> groups ([ 'api' , 'database' ])
-> label ( 'scope' , 'databases.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'databases' ,
2025-01-17 04:31:39 +00:00
name : 'list' ,
description : '/docs/references/databases/list.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DATABASE_LIST ,
)
],
contentType : ContentType :: JSON
))
2023-03-29 19:38:39 +00:00
-> param ( 'queries' , [], new Databases (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Databases :: ALLOWED_ATTRIBUTES ), true )
2022-06-22 10:51:49 +00:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2022-08-25 09:59:28 +00:00
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
2024-11-04 07:57:08 +00:00
$queries = Query :: parseQueries ( $queries );
2022-08-11 23:53:52 +00:00
if ( ! empty ( $search )) {
2022-08-23 09:40:17 +00:00
$queries [] = Query :: search ( 'search' , $search );
2022-08-11 23:53:52 +00:00
}
2024-02-12 09:55:45 +00:00
/**
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 03:25:55 +00:00
});
2022-08-30 23:31:43 +00:00
$cursor = reset ( $cursor );
2022-08-30 11:55:23 +00:00
if ( $cursor ) {
2024-10-17 05:41:24 +00:00
/** @var Query $cursor */
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2022-08-23 09:40:17 +00:00
$databaseId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'databases' , $databaseId );
2022-06-22 10:51:49 +00:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-23 09:40:17 +00:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Database ' { $databaseId } ' for the 'cursor' value not found. " );
2022-06-22 10:51:49 +00:00
}
2022-08-23 09:40:17 +00:00
$cursor -> setValue ( $cursorDocument );
2022-06-22 10:51:49 +00:00
}
2022-08-23 09:40:17 +00:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 11:33:14 +00:00
try {
2025-04-17 04:46:26 +00:00
$databases = $dbForProject -> find ( 'databases' , $queries );
$total = $dbForProject -> count ( 'databases' , $filterQueries , APP_LIMIT_COUNT );
2025-04-16 11:33:14 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 11:33:14 +00:00
}
2022-06-22 10:51:49 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'databases' => $databases ,
'total' => $total ,
2022-06-22 10:51:49 +00:00
]), Response :: MODEL_DATABASE_LIST );
});
App :: get ( '/v1/databases/:databaseId' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Get database' )
2022-06-22 10:51:49 +00:00
-> groups ([ 'api' , 'database' ])
-> label ( 'scope' , 'databases.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'databases' ,
2025-01-17 04:31:39 +00:00
name : 'get' ,
description : '/docs/references/databases/get.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DATABASE ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2022-08-10 02:18:18 +00:00
-> action ( function ( string $databaseId , Response $response , Database $dbForProject ) {
2022-06-22 10:51:49 +00:00
2023-05-24 10:09:08 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
$response -> dynamic ( $database , Response :: MODEL_DATABASE );
});
App :: get ( '/v1/databases/:databaseId/logs' )
2023-10-02 14:02:48 +00:00
-> desc ( 'List database logs' )
2022-06-22 10:51:49 +00:00
-> groups ([ 'api' , 'database' ])
2022-08-19 14:14:39 +00:00
-> label ( 'scope' , 'databases.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'logs' ,
2025-01-17 04:31:39 +00:00
name : 'listLogs' ,
description : '/docs/references/databases/get-logs.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_LOG_LIST ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2023-05-16 12:56:20 +00:00
-> param ( 'queries' , [], new Queries ([ new Limit (), new Offset ()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset' , true )
2022-06-22 10:51:49 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'locale' )
-> inject ( 'geodb' )
2022-08-23 13:03:38 +00:00
-> action ( function ( string $databaseId , array $queries , Response $response , Database $dbForProject , Locale $locale , Reader $geodb ) {
2022-06-22 10:51:49 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2025-02-25 08:03:37 +00:00
// Temp fix for logs
$queries [] = Query :: or ([
2025-02-26 01:00:16 +00:00
Query :: greaterThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-26T01:30+00:00' ))),
2025-02-25 08:03:37 +00:00
Query :: lessThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-13T00:00+00:00' ))),
]);
2025-02-25 05:51:11 +00:00
2022-06-22 10:51:49 +00:00
$audit = new Audit ( $dbForProject );
$resource = 'database/' . $databaseId ;
2025-02-25 07:26:00 +00:00
$logs = $audit -> getLogsByResource ( $resource , $queries );
2022-08-23 13:10:27 +00:00
2022-08-23 13:03:38 +00:00
$output = [];
2022-06-22 10:51:49 +00:00
foreach ( $logs as $i => & $log ) {
$log [ 'userAgent' ] = ( ! empty ( $log [ 'userAgent' ])) ? $log [ 'userAgent' ] : 'UNKNOWN' ;
$detector = new Detector ( $log [ 'userAgent' ]);
$detector -> skipBotDetection (); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$os = $detector -> getOS ();
$client = $detector -> getClient ();
$device = $detector -> getDevice ();
$output [ $i ] = new Document ([
'event' => $log [ 'event' ],
2023-07-12 17:27:57 +00:00
'userId' => ID :: custom ( $log [ 'data' ][ 'userId' ]),
2022-06-22 10:51:49 +00:00
'userEmail' => $log [ 'data' ][ 'userEmail' ] ? ? null ,
'userName' => $log [ 'data' ][ 'userName' ] ? ? null ,
'mode' => $log [ 'data' ][ 'mode' ] ? ? null ,
'ip' => $log [ 'ip' ],
'time' => $log [ 'time' ],
'osCode' => $os [ 'osCode' ],
'osName' => $os [ 'osName' ],
'osVersion' => $os [ 'osVersion' ],
'clientType' => $client [ 'clientType' ],
'clientCode' => $client [ 'clientCode' ],
'clientName' => $client [ 'clientName' ],
'clientVersion' => $client [ 'clientVersion' ],
'clientEngine' => $client [ 'clientEngine' ],
'clientEngineVersion' => $client [ 'clientEngineVersion' ],
'deviceName' => $device [ 'deviceName' ],
'deviceBrand' => $device [ 'deviceBrand' ],
'deviceModel' => $device [ 'deviceModel' ]
]);
$record = $geodb -> get ( $log [ 'ip' ]);
if ( $record ) {
$output [ $i ][ 'countryCode' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), false ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), $locale -> getText ( 'locale.country.unknown' ));
} else {
$output [ $i ][ 'countryCode' ] = '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'locale.country.unknown' );
}
}
$response -> dynamic ( new Document ([
2025-02-25 07:26:00 +00:00
'total' => $audit -> countLogsByResource ( $resource , $queries ),
2025-02-25 06:21:35 +00:00
'logs' => $output ,
2022-06-22 10:51:49 +00:00
]), Response :: MODEL_LOG_LIST );
});
App :: put ( '/v1/databases/:databaseId' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Update database' )
2022-07-08 05:43:20 +00:00
-> groups ([ 'api' , 'database' , 'schema' ])
2022-06-22 10:51:49 +00:00
-> label ( 'scope' , 'databases.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2022-06-22 10:51:49 +00:00
-> label ( 'event' , 'databases.[databaseId].update' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'database.update' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'database/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'databases' ,
2025-01-17 04:31:39 +00:00
name : 'update' ,
description : '/docs/references/databases/update.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DATABASE ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2022-07-08 05:43:20 +00:00
-> param ( 'name' , null , new Text ( 128 ), 'Database name. Max length: 128 chars.' )
2023-08-22 20:11:33 +00:00
-> param ( 'enabled' , true , new Boolean (), 'Is database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.' , true )
2022-06-22 13:30:35 +00:00
-> inject ( 'response' )
2022-06-22 10:51:49 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2023-09-27 15:51:17 +00:00
-> action ( function ( string $databaseId , string $name , bool $enabled , Response $response , Database $dbForProject , Event $queueForEvents ) {
2022-06-22 10:51:49 +00:00
2023-05-24 10:09:08 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2024-03-11 12:03:30 +00:00
$database = $dbForProject -> updateDocument ( 'databases' , $databaseId , $database
-> setAttribute ( 'name' , $name )
-> setAttribute ( 'enabled' , $enabled )
-> setAttribute ( 'search' , implode ( ' ' , [ $databaseId , $name ])));
2022-06-22 10:51:49 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents -> setParam ( 'databaseId' , $database -> getId ());
2022-06-22 10:51:49 +00:00
$response -> dynamic ( $database , Response :: MODEL_DATABASE );
});
App :: delete ( '/v1/databases/:databaseId' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Delete database' )
2022-07-08 05:43:20 +00:00
-> groups ([ 'api' , 'database' , 'schema' ])
2022-06-22 10:51:49 +00:00
-> label ( 'scope' , 'databases.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2022-06-22 10:51:49 +00:00
-> label ( 'event' , 'databases.[databaseId].delete' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'database.delete' )
2022-08-08 14:32:54 +00:00
-> label ( 'audits.resource' , 'database/{request.databaseId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'databases' ,
2025-01-17 04:31:39 +00:00
name : 'delete' ,
description : '/docs/references/databases/delete.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-10-16 23:18:51 +00:00
-> inject ( 'queueForDatabase' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
-> action ( function ( string $databaseId , Response $response , Database $dbForProject , EventDatabase $queueForDatabase , Event $queueForEvents , StatsUsage $queueForStatsUsage ) {
2022-06-22 10:51:49 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
if ( ! $dbForProject -> deleteDocument ( 'databases' , $databaseId )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove collection from DB' );
2022-06-22 10:51:49 +00:00
}
2023-12-14 13:32:06 +00:00
$dbForProject -> purgeCachedDocument ( 'databases' , $database -> getId ());
$dbForProject -> purgeCachedCollection ( 'databases_' . $database -> getInternalId ());
2022-06-22 10:51:49 +00:00
2023-10-16 23:18:51 +00:00
$queueForDatabase
-> setType ( DATABASE_TYPE_DELETE_DATABASE )
-> setDatabase ( $database );
2022-06-22 10:51:49 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $database -> getId ())
2023-07-18 11:16:33 +00:00
-> setPayload ( $response -> output ( $database , Response :: MODEL_DATABASE ));
2022-06-22 10:51:49 +00:00
$response -> noContent ();
});
2025-04-26 10:35:07 +00:00
App :: post ( '/v1/databases/:databaseId/tables' )
-> alias ( '/v1/databases/:databaseId/collections' )
-> desc ( 'Create table' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].create' )
2019-06-08 13:13:19 +00:00
-> label ( 'scope' , 'collections.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.event' , 'table.create' )
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'tables' ,
name : 'createTable' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/create-collection.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_COLLECTION ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new CustomId (), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
-> param ( 'name' , '' , new Text ( 128 ), 'Table name. Max length: 128 chars.' )
2023-10-13 13:43:23 +00:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE ), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
-> param ( 'documentSecurity' , false , new Boolean ( true ), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
2023-08-22 20:11:33 +00:00
-> param ( 'enabled' , true , new Boolean (), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.' , true )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2023-05-24 12:28:40 +00:00
-> inject ( 'mode' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $name , ? array $permissions , bool $documentSecurity , bool $enabled , Response $response , Database $dbForProject , string $mode , Event $queueForEvents ) {
2022-06-22 10:51:49 +00:00
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2021-06-15 13:38:24 +00:00
2025-04-26 10:35:07 +00:00
$tableId = $tableId == 'unique()' ? ID :: unique () : $tableId ;
2021-07-05 20:27:20 +00:00
2022-08-23 01:42:25 +00:00
// Map aggregate permissions into the multiple permissions they represent.
2024-11-06 07:36:27 +00:00
$permissions = Permission :: aggregate ( $permissions ) ? ? [];
2022-08-16 08:33:06 +00:00
2021-08-16 17:28:33 +00:00
try {
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> createDocument ( 'database_' . $database -> getInternalId (), new Document ([
'$id' => $tableId ,
2022-06-22 10:51:49 +00:00
'databaseInternalId' => $database -> getInternalId (),
'databaseId' => $databaseId ,
2024-11-06 07:36:27 +00:00
'$permissions' => $permissions ,
2022-08-04 04:20:11 +00:00
'documentSecurity' => $documentSecurity ,
2023-05-18 13:25:22 +00:00
'enabled' => $enabled ,
2021-08-16 17:28:33 +00:00
'name' => $name ,
2025-04-26 10:35:07 +00:00
'search' => implode ( ' ' , [ $tableId , $name ]),
2021-08-16 17:28:33 +00:00
]));
2022-01-24 08:20:02 +00:00
2025-04-26 10:35:07 +00:00
$dbForProject -> createCollection ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), permissions : $permissions , documentSecurity : $documentSecurity );
2022-08-24 08:50:05 +00:00
} catch ( DuplicateException ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_ALREADY_EXISTS );
2022-08-24 08:50:05 +00:00
} catch ( LimitException ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_LIMIT_EXCEEDED );
2021-08-16 17:28:33 +00:00
}
2019-05-09 06:54:39 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setContext ( 'database' , $database )
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ());
2022-06-22 10:51:49 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
2025-04-26 10:35:07 +00:00
-> dynamic ( $table , Response :: MODEL_COLLECTION );
2020-12-26 15:05:04 +00:00
});
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables' )
-> alias ( '/v1/databases/:databaseId/collections' )
-> desc ( 'List tables' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2020-01-31 22:34:07 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'tables' ,
name : 'listTables' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/list-collections.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_COLLECTION_LIST ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2023-03-29 19:38:39 +00:00
-> param ( 'queries' , [], new Collections (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Collections :: ALLOWED_ATTRIBUTES ), true )
2021-07-05 19:19:18 +00:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2023-05-26 11:38:09 +00:00
-> inject ( 'mode' )
-> action ( function ( string $databaseId , array $queries , string $search , Response $response , Database $dbForProject , string $mode ) {
2022-06-22 10:51:49 +00:00
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2020-06-29 21:43:34 +00:00
2024-11-04 07:57:08 +00:00
$queries = Query :: parseQueries ( $queries );
2021-08-16 17:28:33 +00:00
2022-08-11 23:53:52 +00:00
if ( ! empty ( $search )) {
2022-08-23 12:30:28 +00:00
$queries [] = Query :: search ( 'search' , $search );
2021-08-16 17:28:33 +00:00
}
2020-06-29 21:43:34 +00:00
2024-02-12 09:55:45 +00:00
/**
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 03:25:55 +00:00
});
2022-08-30 23:31:43 +00:00
$cursor = reset ( $cursor );
2022-08-30 11:55:23 +00:00
if ( $cursor ) {
2022-08-23 12:30:28 +00:00
/** @var Query $cursor */
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2025-04-26 10:35:07 +00:00
$tableId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2021-08-06 12:35:57 +00:00
2022-08-11 23:53:52 +00:00
if ( $cursorDocument -> isEmpty ()) {
2025-04-26 10:35:07 +00:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Table ' { $tableId } ' for the 'cursor' value not found. " );
2022-08-11 23:53:52 +00:00
}
2022-08-23 12:30:28 +00:00
$cursor -> setValue ( $cursorDocument );
2021-08-06 12:35:57 +00:00
}
2022-08-23 12:30:28 +00:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 11:33:14 +00:00
try {
2025-04-26 10:35:07 +00:00
$tables = $dbForProject -> find ( 'database_' . $database -> getInternalId (), $queries );
2025-04-17 04:46:26 +00:00
$total = $dbForProject -> count ( 'database_' . $database -> getInternalId (), $filterQueries , APP_LIMIT_COUNT );
2025-04-16 11:33:14 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 11:33:14 +00:00
}
2025-04-26 10:35:07 +00:00
// TODO: collections > tables
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ([
2025-04-26 10:35:07 +00:00
'collections' => $tables ,
2025-05-04 07:52:26 +00:00
'total' => $total ,
]), Response :: MODEL_COLLECTION_LIST );
2023-02-26 12:33:18 +00:00
});
2025-05-04 07:52:26 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId' )
-> desc ( 'Get table' )
-> groups ([ 'api' , 'database' ])
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-05-04 07:52:26 +00:00
group : 'tables' ,
name : 'getTable' ,
description : '/docs/references/databases/get-collection.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-05-04 07:52:26 +00:00
model : Response :: MODEL_COLLECTION ,
2025-01-17 04:31:39 +00:00
)
],
contentType : ContentType :: JSON
))
2023-02-26 12:33:18 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-05-04 07:52:26 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID.' )
2023-02-26 12:33:18 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-05-04 07:52:26 +00:00
-> action ( function ( string $databaseId , string $tableId , Response $response , Database $dbForProject ) {
2023-02-26 12:33:18 +00:00
2025-05-04 07:52:26 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2023-03-10 13:06:34 +00:00
2025-05-04 07:52:26 +00:00
if ( $database -> isEmpty ()) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2023-03-10 13:06:34 +00:00
}
2025-05-04 07:52:26 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2023-02-26 12:33:18 +00:00
2025-05-04 07:52:26 +00:00
if ( $table -> isEmpty ()) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
2023-02-26 12:33:18 +00:00
2025-05-04 07:52:26 +00:00
$response -> dynamic ( $table , Response :: MODEL_COLLECTION );
2023-02-26 12:33:18 +00:00
});
2025-05-04 07:52:26 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/logs' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/logs' )
-> desc ( 'List table logs' )
-> groups ([ 'api' , 'database' ])
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-05-04 07:52:26 +00:00
group : 'tables' ,
name : 'listTableLogs' ,
description : '/docs/references/databases/get-collection-logs.md' ,
auth : [ AuthType :: ADMIN ],
2025-01-17 04:31:39 +00:00
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-05-04 07:52:26 +00:00
model : Response :: MODEL_LOG_LIST ,
2025-01-17 04:31:39 +00:00
)
],
contentType : ContentType :: JSON
))
2023-02-26 12:33:18 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-05-04 07:52:26 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID.' )
-> param ( 'queries' , [], new Queries ([ new Limit (), new Offset ()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset' , true )
2023-02-26 12:33:18 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-05-04 07:52:26 +00:00
-> inject ( 'locale' )
-> inject ( 'geodb' )
-> action ( function ( string $databaseId , string $tableId , array $queries , Response $response , Database $dbForProject , Locale $locale , Reader $geodb ) {
2023-02-26 12:33:18 +00:00
2025-05-04 07:52:26 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
if ( $database -> isEmpty ()) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
}
$tableDocument = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
$table = $dbForProject -> getCollection ( 'database_' . $database -> getInternalId () . '_collection_' . $tableDocument -> getInternalId ());
if ( $table -> isEmpty ()) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
// Temp fix for logs
$queries [] = Query :: or ([
Query :: greaterThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-26T01:30+00:00' ))),
Query :: lessThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-13T00:00+00:00' ))),
]);
$audit = new Audit ( $dbForProject );
$resource = 'database/' . $databaseId . '/table/' . $tableId ;
$logs = $audit -> getLogsByResource ( $resource , $queries );
$output = [];
foreach ( $logs as $i => & $log ) {
$log [ 'userAgent' ] = ( ! empty ( $log [ 'userAgent' ])) ? $log [ 'userAgent' ] : 'UNKNOWN' ;
$detector = new Detector ( $log [ 'userAgent' ]);
$detector -> skipBotDetection (); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$os = $detector -> getOS ();
$client = $detector -> getClient ();
$device = $detector -> getDevice ();
$output [ $i ] = new Document ([
'event' => $log [ 'event' ],
'userId' => $log [ 'data' ][ 'userId' ],
'userEmail' => $log [ 'data' ][ 'userEmail' ] ? ? null ,
'userName' => $log [ 'data' ][ 'userName' ] ? ? null ,
'mode' => $log [ 'data' ][ 'mode' ] ? ? null ,
'ip' => $log [ 'ip' ],
'time' => $log [ 'time' ],
'osCode' => $os [ 'osCode' ],
'osName' => $os [ 'osName' ],
'osVersion' => $os [ 'osVersion' ],
'clientType' => $client [ 'clientType' ],
'clientCode' => $client [ 'clientCode' ],
'clientName' => $client [ 'clientName' ],
'clientVersion' => $client [ 'clientVersion' ],
'clientEngine' => $client [ 'clientEngine' ],
'clientEngineVersion' => $client [ 'clientEngineVersion' ],
'deviceName' => $device [ 'deviceName' ],
'deviceBrand' => $device [ 'deviceBrand' ],
'deviceModel' => $device [ 'deviceModel' ]
]);
$record = $geodb -> get ( $log [ 'ip' ]);
if ( $record ) {
$output [ $i ][ 'countryCode' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), false ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), $locale -> getText ( 'locale.country.unknown' ));
} else {
$output [ $i ][ 'countryCode' ] = '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'locale.country.unknown' );
}
}
$response -> dynamic ( new Document ([
'total' => $audit -> countLogsByResource ( $resource , $queries ),
'logs' => $output ,
]), Response :: MODEL_LOG_LIST );
2023-02-26 12:33:18 +00:00
});
2025-05-04 07:52:26 +00:00
App :: put ( '/v1/databases/:databaseId/tables/:tableId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId' )
-> desc ( 'Update table' )
2023-03-23 03:58:41 +00:00
-> groups ([ 'api' , 'database' , 'schema' ])
-> label ( 'scope' , 'collections.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-05-04 07:52:26 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].update' )
-> label ( 'audits.event' , 'table.update' )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-05-04 07:52:26 +00:00
group : 'tables' ,
name : 'updateTable' ,
description : '/docs/references/databases/update-collection.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-05-04 07:52:26 +00:00
model : Response :: MODEL_COLLECTION ,
2025-01-17 04:31:39 +00:00
)
],
contentType : ContentType :: JSON
))
2023-03-23 03:58:41 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-05-04 07:52:26 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID.' )
-> param ( 'name' , null , new Text ( 128 ), 'Collection name. Max length: 128 chars.' )
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE ), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
-> param ( 'documentSecurity' , false , new Boolean ( true ), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
-> param ( 'enabled' , true , new Boolean (), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.' , true )
2023-03-23 03:58:41 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-05-04 07:52:26 +00:00
-> inject ( 'mode' )
2023-10-03 18:56:36 +00:00
-> inject ( 'queueForEvents' )
2025-05-04 07:52:26 +00:00
-> action ( function ( string $databaseId , string $tableId , string $name , ? array $permissions , bool $documentSecurity , bool $enabled , Response $response , Database $dbForProject , string $mode , Event $queueForEvents ) {
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
if ( $database -> isEmpty ()) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
}
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
if ( $table -> isEmpty ()) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
$permissions ? ? = $table -> getPermissions () ? ? [];
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission :: aggregate ( $permissions );
$enabled ? ? = $table -> getAttribute ( 'enabled' , true );
$table = $dbForProject -> updateDocument (
'database_' . $database -> getInternalId (),
2025-04-26 10:35:07 +00:00
$tableId ,
2025-05-04 07:52:26 +00:00
$table
-> setAttribute ( 'name' , $name )
-> setAttribute ( '$permissions' , $permissions )
-> setAttribute ( 'documentSecurity' , $documentSecurity )
-> setAttribute ( 'enabled' , $enabled )
-> setAttribute ( 'search' , \implode ( ' ' , [ $tableId , $name ]))
2023-03-23 03:58:41 +00:00
);
2025-05-04 07:52:26 +00:00
$dbForProject -> updateCollection ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $permissions , $documentSecurity );
2023-03-23 03:58:41 +00:00
2025-05-04 07:52:26 +00:00
$queueForEvents
-> setContext ( 'database' , $database )
-> setParam ( 'databaseId' , $databaseId )
-> setParam ( 'tableId' , $table -> getId ());
2023-03-23 03:58:41 +00:00
2025-05-04 07:52:26 +00:00
$response -> dynamic ( $table , Response :: MODEL_COLLECTION );
2023-03-23 03:58:41 +00:00
});
2025-05-04 07:52:26 +00:00
App :: delete ( '/v1/databases/:databaseId/tables/:tableId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId' )
-> desc ( 'Delete table' )
2022-07-08 05:43:20 +00:00
-> groups ([ 'api' , 'database' , 'schema' ])
2021-08-14 10:16:00 +00:00
-> label ( 'scope' , 'collections.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-05-04 07:52:26 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].delete' )
-> label ( 'audits.event' , 'table.delete' )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-05-04 07:52:26 +00:00
group : 'tables' ,
name : 'deleteTable' ,
description : '/docs/references/databases/delete-collection.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-05-04 07:52:26 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID.' )
2021-03-24 15:40:33 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2023-09-27 15:51:17 +00:00
-> inject ( 'queueForDatabase' )
-> inject ( 'queueForEvents' )
2025-05-04 07:52:26 +00:00
-> inject ( 'mode' )
-> action ( function ( string $databaseId , string $tableId , Response $response , Database $dbForProject , EventDatabase $queueForDatabase , Event $queueForEvents , string $mode ) {
2021-03-24 15:40:33 +00:00
2025-05-04 07:52:26 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
2025-05-04 07:52:26 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2025-05-04 07:52:26 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2021-03-24 15:40:33 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 13:15:00 +00:00
}
2025-05-04 07:52:26 +00:00
if ( ! $dbForProject -> deleteDocument ( 'database_' . $database -> getInternalId (), $tableId )) {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove collection from DB' );
2021-10-05 00:23:15 +00:00
}
2025-05-04 07:52:26 +00:00
$dbForProject -> purgeCachedCollection ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId ());
2023-03-30 17:36:24 +00:00
2022-12-20 16:11:30 +00:00
$queueForDatabase
2025-05-04 07:52:26 +00:00
-> setType ( DATABASE_TYPE_DELETE_COLLECTION )
-> setDatabase ( $database )
-> setTable ( $table );
2021-10-26 01:12:27 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2025-05-04 07:52:26 +00:00
-> setContext ( 'database' , $database )
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
2025-05-04 07:52:26 +00:00
-> setPayload ( $response -> output ( $table , Response :: MODEL_COLLECTION ));
2021-03-24 15:40:33 +00:00
$response -> noContent ();
});
2025-04-26 10:35:07 +00:00
App :: post ( '/v1/databases/:databaseId/tables/:tableId/indexes' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/indexes' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Create index' )
2021-03-23 21:19:19 +00:00
-> groups ([ 'api' , 'database' ])
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].indexes.[indexId].create' )
2021-08-14 10:16:00 +00:00
-> label ( 'scope' , 'collections.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'index.create' )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
2025-03-31 09:32:47 +00:00
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'tables' ,
2025-01-17 04:31:39 +00:00
name : 'createIndex' ,
description : '/docs/references/databases/create-index.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_INDEX ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
2021-12-16 15:04:30 +00:00
-> param ( 'key' , null , new Key (), 'Index Key.' )
2024-02-26 02:38:58 +00:00
-> param ( 'type' , null , new WhiteList ([ Database :: INDEX_KEY , Database :: INDEX_FULLTEXT , Database :: INDEX_UNIQUE ]), 'Index type.' )
2025-04-26 10:35:07 +00:00
-> param ( 'columns' , null , new ArrayList ( new Key ( true ), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of columns to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' columns are allowed, each 32 characters long.' )
2022-05-01 07:54:58 +00:00
-> param ( 'orders' , [], new ArrayList ( new WhiteList ([ 'ASC' , 'DESC' ], false , Database :: VAR_STRING ), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.' , true )
2021-03-23 21:19:19 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForDatabase' )
-> inject ( 'queueForEvents' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $key , string $type , array $columns , array $orders , Response $response , Database $dbForProject , EventDatabase $queueForDatabase , Event $queueForEvents ) {
2021-03-23 21:19:19 +00:00
2024-03-06 17:34:21 +00:00
$db = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
if ( $db -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2023-06-15 05:29:03 +00:00
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $db -> getInternalId (), $tableId );
2021-06-10 13:15:00 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 13:15:00 +00:00
}
2021-12-27 12:45:23 +00:00
$count = $dbForProject -> count ( 'indexes' , [
2025-04-26 10:35:07 +00:00
Query :: equal ( 'collectionInternalId' , [ $table -> getInternalId ()]),
2022-08-11 23:53:52 +00:00
Query :: equal ( 'databaseInternalId' , [ $db -> getInternalId ()])
2022-06-22 10:51:49 +00:00
], 61 );
2021-08-24 23:35:32 +00:00
2022-11-03 20:24:03 +00:00
$limit = $dbForProject -> getLimitForIndexes ();
2021-08-24 23:35:32 +00:00
if ( $count >= $limit ) {
2022-08-14 08:05:11 +00:00
throw new Exception ( Exception :: INDEX_LIMIT_EXCEEDED , 'Index limit exceeded' );
2021-08-24 23:35:32 +00:00
}
2021-07-03 00:20:09 +00:00
// Convert Document[] to array of attribute metadata
2025-04-26 10:35:07 +00:00
$oldColumns = \array_map ( fn ( $a ) => $a -> getArrayCopy (), $table -> getAttribute ( 'attributes' ));
2021-07-02 22:23:58 +00:00
2025-04-26 10:35:07 +00:00
$oldColumns [] = [
2022-06-20 14:22:27 +00:00
'key' => '$id' ,
2022-09-08 22:18:36 +00:00
'type' => Database :: VAR_STRING ,
2022-06-20 14:22:27 +00:00
'status' => 'available' ,
'required' => true ,
'array' => false ,
'default' => null ,
2024-07-10 07:00:10 +00:00
'size' => Database :: LENGTH_KEY
2022-06-20 14:22:27 +00:00
];
2022-06-22 09:46:37 +00:00
2025-04-26 10:35:07 +00:00
$oldColumns [] = [
2022-06-22 09:46:37 +00:00
'key' => '$createdAt' ,
2022-09-08 22:18:36 +00:00
'type' => Database :: VAR_DATETIME ,
2022-06-22 09:46:37 +00:00
'status' => 'available' ,
'signed' => false ,
'required' => false ,
'array' => false ,
'default' => null ,
'size' => 0
];
2025-04-26 10:35:07 +00:00
$oldColumns [] = [
2022-06-22 09:46:37 +00:00
'key' => '$updatedAt' ,
2022-09-08 22:18:36 +00:00
'type' => Database :: VAR_DATETIME ,
2022-06-22 09:46:37 +00:00
'status' => 'available' ,
'signed' => false ,
'required' => false ,
'array' => false ,
'default' => null ,
'size' => 0
];
2021-07-02 22:23:58 +00:00
2021-06-23 14:21:32 +00:00
// lengths hidden by default
$lengths = [];
2025-04-26 10:35:07 +00:00
foreach ( $columns as $i => $column ) {
2021-07-02 22:23:58 +00:00
// find attribute metadata in collection document
2025-04-26 10:35:07 +00:00
$columnIndex = \array_search ( $column , array_column ( $oldColumns , 'key' ));
2021-07-02 22:23:58 +00:00
2025-04-26 10:35:07 +00:00
if ( $columnIndex === false ) {
throw new Exception ( Exception :: ATTRIBUTE_UNKNOWN , 'Unknown column: ' . $column . '. Verify the column name or create the column.' );
2021-07-02 22:23:58 +00:00
}
2025-04-26 10:35:07 +00:00
$columnStatus = $oldColumns [ $columnIndex ][ 'status' ];
$columnType = $oldColumns [ $columnIndex ][ 'type' ];
$columnArray = $oldColumns [ $columnIndex ][ 'array' ] ? ? false ;
2021-07-02 22:23:58 +00:00
2025-04-26 10:35:07 +00:00
if ( $columnType === Database :: VAR_RELATIONSHIP ) {
throw new Exception ( Exception :: ATTRIBUTE_TYPE_INVALID , 'Cannot create an index for a relationship column: ' . $oldColumns [ $columnIndex ][ 'key' ]);
2023-03-31 05:06:50 +00:00
}
2021-10-05 00:43:27 +00:00
// ensure attribute is available
2025-04-26 10:35:07 +00:00
if ( $columnStatus !== 'available' ) {
throw new Exception ( Exception :: ATTRIBUTE_NOT_AVAILABLE , 'Column not available: ' . $oldColumns [ $columnIndex ][ 'key' ]);
2021-10-05 00:43:27 +00:00
}
2024-02-08 13:01:20 +00:00
$lengths [ $i ] = null ;
2025-04-26 10:35:07 +00:00
if ( $columnArray === true ) {
2024-02-08 13:01:20 +00:00
$lengths [ $i ] = Database :: ARRAY_INDEX_LENGTH ;
$orders [ $i ] = null ;
}
2021-07-02 22:23:58 +00:00
}
2023-06-15 05:29:03 +00:00
$index = new Document ([
2025-04-26 10:35:07 +00:00
'$id' => ID :: custom ( $db -> getInternalId () . '_' . $table -> getInternalId () . '_' . $key ),
2023-06-15 05:29:03 +00:00
'key' => $key ,
'status' => 'processing' , // processing, available, failed, deleting, stuck
'databaseInternalId' => $db -> getInternalId (),
'databaseId' => $databaseId ,
2025-04-26 10:35:07 +00:00
'collectionInternalId' => $table -> getInternalId (),
'collectionId' => $tableId ,
2023-06-15 05:29:03 +00:00
'type' => $type ,
2025-04-26 10:35:07 +00:00
'attributes' => $columns ,
2023-06-15 05:29:03 +00:00
'lengths' => $lengths ,
'orders' => $orders ,
]);
2023-10-19 01:26:44 +00:00
$validator = new IndexValidator (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' ),
2024-11-12 09:27:24 +00:00
$dbForProject -> getAdapter () -> getMaxIndexLength (),
$dbForProject -> getAdapter () -> getInternalIndexesKeys (),
2023-10-19 01:26:44 +00:00
);
if ( ! $validator -> isValid ( $index )) {
2023-06-15 05:29:03 +00:00
throw new Exception ( Exception :: INDEX_INVALID , $validator -> getDescription ());
}
2021-08-22 15:00:00 +00:00
try {
2023-06-15 05:29:03 +00:00
$index = $dbForProject -> createDocument ( 'indexes' , $index );
} catch ( DuplicateException ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: INDEX_ALREADY_EXISTS );
2021-08-22 15:00:00 +00:00
}
2025-04-26 10:35:07 +00:00
$dbForProject -> purgeCachedDocument ( 'database_' . $db -> getInternalId (), $tableId );
2021-03-23 21:19:19 +00:00
2022-12-20 16:11:30 +00:00
$queueForDatabase
2022-04-13 12:39:31 +00:00
-> setType ( DATABASE_TYPE_CREATE_INDEX )
2022-06-22 10:51:49 +00:00
-> setDatabase ( $db )
2025-04-27 05:03:44 +00:00
-> setTable ( $table )
-> setRow ( $index );
2021-06-18 16:13:37 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
2022-04-13 12:39:31 +00:00
-> setParam ( 'indexId' , $index -> getId ())
2025-04-26 10:35:07 +00:00
-> setContext ( 'table' , $table )
2023-05-24 10:09:08 +00:00
-> setContext ( 'database' , $db );
2022-04-13 12:39:31 +00:00
2022-09-07 11:11:10 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $index , Response :: MODEL_INDEX );
2021-03-23 21:19:19 +00:00
});
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/indexes' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/indexes' )
2023-10-02 14:02:48 +00:00
-> desc ( 'List indexes' )
2021-03-24 15:40:33 +00:00
-> groups ([ 'api' , 'database' ])
2021-08-14 10:16:00 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'indexes' ,
2025-01-17 04:31:39 +00:00
name : 'listIndexes' ,
description : '/docs/references/databases/list-indexes.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_INDEX_LIST ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
2023-07-21 11:10:44 +00:00
-> param ( 'queries' , [], new Indexes (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Indexes :: ALLOWED_ATTRIBUTES ), true )
2021-03-24 15:40:33 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , array $queries , Response $response , Database $dbForProject ) {
2024-02-12 09:55:45 +00:00
/** @var Document $database */
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2021-06-09 21:11:51 +00:00
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2023-07-24 18:33:58 +00:00
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2021-03-24 15:40:33 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 13:15:00 +00:00
}
2024-11-04 07:57:08 +00:00
$queries = Query :: parseQueries ( $queries );
2024-02-12 16:02:04 +00:00
2024-11-04 07:57:08 +00:00
\array_push (
$queries ,
Query :: equal ( 'databaseId' , [ $databaseId ]),
2025-04-26 10:35:07 +00:00
Query :: equal ( 'collectionId' , [ $tableId ]),
2024-11-04 07:57:08 +00:00
);
2021-03-24 15:40:33 +00:00
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2024-03-06 17:34:21 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2024-03-06 17:34:21 +00:00
});
$cursor = reset ( $cursor );
2023-07-21 11:10:44 +00:00
if ( $cursor ) {
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-07-21 11:10:44 +00:00
$indexId = $cursor -> getValue ();
2024-03-06 17:34:21 +00:00
$cursorDocument = Authorization :: skip ( fn () => $dbForProject -> find ( 'indexes' , [
2025-04-26 10:35:07 +00:00
Query :: equal ( 'collectionInternalId' , [ $table -> getInternalId ()]),
2024-02-12 09:55:45 +00:00
Query :: equal ( 'databaseInternalId' , [ $database -> getInternalId ()]),
2023-07-26 06:07:23 +00:00
Query :: equal ( 'key' , [ $indexId ]),
Query :: limit ( 1 )
]));
2021-06-10 13:15:00 +00:00
2023-07-21 11:10:44 +00:00
if ( empty ( $cursorDocument ) || $cursorDocument [ 0 ] -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Index ' { $indexId } ' for the 'cursor' value not found. " );
}
2021-03-24 15:40:33 +00:00
2023-07-21 11:10:44 +00:00
$cursor -> setValue ( $cursorDocument [ 0 ]);
}
2021-03-24 15:40:33 +00:00
2023-07-21 11:10:44 +00:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 11:33:14 +00:00
try {
2025-04-17 04:46:26 +00:00
$total = $dbForProject -> count ( 'indexes' , $filterQueries , APP_LIMIT_COUNT );
$indexes = $dbForProject -> find ( 'indexes' , $queries );
2025-04-16 11:33:14 +00:00
} catch ( OrderException $e ) {
2025-04-26 10:35:07 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order column ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all rows order column values are non-null. " );
2025-04-16 11:33:14 +00:00
}
2025-04-17 04:46:26 +00:00
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'total' => $total ,
'indexes' => $indexes ,
2021-06-09 21:11:51 +00:00
]), Response :: MODEL_INDEX_LIST );
2021-03-24 15:40:33 +00:00
});
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/indexes/:key' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/indexes/:key' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Get index' )
2021-03-25 19:52:57 +00:00
-> groups ([ 'api' , 'database' ])
2021-08-14 10:16:00 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'indexes' ,
2025-01-17 04:31:39 +00:00
name : 'getIndex' ,
description : '/docs/references/databases/get-index.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_INDEX ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
2021-12-16 15:04:30 +00:00
-> param ( 'key' , null , new Key (), 'Index Key.' )
2021-03-25 19:52:57 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $key , Response $response , Database $dbForProject ) {
2022-06-22 10:51:49 +00:00
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2021-03-25 19:52:57 +00:00
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2021-06-09 21:11:51 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 13:15:00 +00:00
}
2025-04-26 10:35:07 +00:00
$index = $table -> find ( 'key' , $key , 'indexes' );
2023-07-20 09:22:05 +00:00
if ( empty ( $index )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: INDEX_NOT_FOUND );
2021-06-09 21:11:51 +00:00
}
2021-07-25 14:47:18 +00:00
$response -> dynamic ( $index , Response :: MODEL_INDEX );
2021-03-25 19:52:57 +00:00
});
2025-04-26 10:35:07 +00:00
App :: delete ( '/v1/databases/:databaseId/tables/:tableId/indexes/:key' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/indexes/:key' )
2023-10-02 14:02:48 +00:00
-> desc ( 'Delete index' )
2021-03-25 19:52:57 +00:00
-> groups ([ 'api' , 'database' ])
2021-08-14 10:16:00 +00:00
-> label ( 'scope' , 'collections.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].indexes.[indexId].update' )
2022-09-05 08:00:08 +00:00
-> label ( 'audits.event' , 'index.delete' )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'indexes' ,
2025-01-17 04:31:39 +00:00
name : 'deleteIndex' ,
description : '/docs/references/databases/delete-index.md' ,
auth : [ AuthType :: KEY ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
2021-12-16 15:04:30 +00:00
-> param ( 'key' , '' , new Key (), 'Index Key.' )
2021-03-25 19:52:57 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForDatabase' )
-> inject ( 'queueForEvents' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $key , Response $response , Database $dbForProject , EventDatabase $queueForDatabase , Event $queueForEvents ) {
2021-03-25 19:52:57 +00:00
2024-03-06 17:34:21 +00:00
$db = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
if ( $db -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $db -> getInternalId (), $tableId );
2021-06-09 21:11:51 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 13:15:00 +00:00
}
2025-04-26 10:35:07 +00:00
$index = $dbForProject -> getDocument ( 'indexes' , $db -> getInternalId () . '_' . $table -> getInternalId () . '_' . $key );
2021-06-09 21:11:51 +00:00
2021-08-23 04:06:53 +00:00
if ( empty ( $index -> getId ())) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: INDEX_NOT_FOUND );
2021-03-25 19:52:57 +00:00
}
2021-10-05 00:23:15 +00:00
// Only update status if removing available index
if ( $index -> getAttribute ( 'status' ) === 'available' ) {
2021-12-27 12:45:23 +00:00
$index = $dbForProject -> updateDocument ( 'indexes' , $index -> getId (), $index -> setAttribute ( 'status' , 'deleting' ));
2021-10-05 00:23:15 +00:00
}
2025-04-26 10:35:07 +00:00
$dbForProject -> purgeCachedDocument ( 'database_' . $db -> getInternalId (), $tableId );
2021-08-23 04:06:53 +00:00
2022-12-20 16:11:30 +00:00
$queueForDatabase
2022-04-13 12:39:31 +00:00
-> setType ( DATABASE_TYPE_DELETE_INDEX )
2022-06-22 10:51:49 +00:00
-> setDatabase ( $db )
2025-04-27 05:03:44 +00:00
-> setTable ( $table )
-> setRow ( $index );
2021-03-25 19:52:57 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
2022-04-13 12:39:31 +00:00
-> setParam ( 'indexId' , $index -> getId ())
2025-04-26 10:35:07 +00:00
-> setContext ( 'table' , $table )
2022-08-05 06:01:25 +00:00
-> setContext ( 'database' , $db )
2023-05-24 10:09:08 +00:00
-> setPayload ( $response -> output ( $index , Response :: MODEL_INDEX ));
2021-03-25 19:52:57 +00:00
$response -> noContent ();
});
2025-04-26 10:35:07 +00:00
App :: post ( '/v1/databases/:databaseId/tables/:tableId/rows' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents' )
-> desc ( 'Create row' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].rows.[rowId].create' )
2020-01-31 22:34:07 +00:00
-> label ( 'scope' , 'documents.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.event' , 'row.create' )
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}' )
2022-08-31 03:58:32 +00:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-30 23:34:17 +00:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT * 2 )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2025-01-17 04:31:39 +00:00
-> label (
'sdk' ,
[
new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'rows' ,
name : 'createRow' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/create-document.md' ,
auth : [ AuthType :: SESSION , AuthType :: KEY , AuthType :: JWT ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_CREATED ,
model : Response :: MODEL_DOCUMENT ,
)
],
contentType : ContentType :: JSON
)
]
)
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'rowId' , '' , new CustomId (), 'Row ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.' )
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define columns before creating rows.' )
-> param ( 'data' , [], new JSON (), 'Row data as JSON object.' )
2023-10-13 13:43:23 +00:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE , [ Database :: PERMISSION_READ , Database :: PERMISSION_UPDATE , Database :: PERMISSION_DELETE , Database :: PERMISSION_WRITE ]), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2021-03-21 22:17:20 +00:00
-> inject ( 'user' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $rowId , string $tableId , string | array $data , ? array $permissions , Response $response , Database $dbForProject , Document $user , Event $queueForEvents , StatsUsage $queueForStatsUsage ) {
2021-10-05 10:30:33 +00:00
2020-06-29 21:43:34 +00:00
$data = ( \is_string ( $data )) ? \json_decode ( $data , true ) : $data ; // Cast to JSON array
if ( empty ( $data )) {
2023-06-07 01:51:24 +00:00
throw new Exception ( Exception :: DOCUMENT_MISSING_DATA );
2020-06-29 21:43:34 +00:00
}
2020-01-31 22:34:07 +00:00
2020-06-29 21:43:34 +00:00
if ( isset ( $data [ '$id' ])) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DOCUMENT_INVALID_STRUCTURE , '$id is not allowed for creating new documents, try update instead' );
2020-06-29 21:43:34 +00:00
}
2021-10-05 10:30:33 +00:00
2025-01-02 07:56:14 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-08-08 10:58:28 +00:00
2023-08-08 19:46:01 +00:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
2023-07-31 18:24:21 +00:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
2023-08-16 21:58:25 +00:00
if ( $database -> isEmpty () || ( ! $database -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-08-16 08:30:00 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-08-08 10:58:28 +00:00
}
2025-04-26 10:35:07 +00:00
$table = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId ));
2020-01-31 22:34:07 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty () || ( ! $table -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2023-08-16 21:58:25 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2022-08-08 10:58:28 +00:00
}
2022-08-23 01:42:25 +00:00
$allowedPermissions = [
Database :: PERMISSION_READ ,
Database :: PERMISSION_UPDATE ,
Database :: PERMISSION_DELETE ,
];
2022-08-16 05:54:44 +00:00
2022-08-23 01:42:25 +00:00
// Map aggregate permissions to into the set of individual permissions they represent.
$permissions = Permission :: aggregate ( $permissions , $allowedPermissions );
// Add permissions for current the user if none were provided.
2022-08-15 12:56:19 +00:00
if ( \is_null ( $permissions )) {
$permissions = [];
2022-08-15 13:16:20 +00:00
if ( ! empty ( $user -> getId ())) {
2022-08-15 12:56:19 +00:00
foreach ( $allowedPermissions as $permission ) {
2022-08-15 13:16:20 +00:00
$permissions [] = ( new Permission ( $permission , 'user' , $user -> getId ())) -> toString ();
2022-08-15 12:56:19 +00:00
}
}
}
2022-08-14 05:24:50 +00:00
2022-08-15 12:56:19 +00:00
// Users can only manage their own roles, API keys and Admin users can manage any
2023-08-16 21:58:25 +00:00
if ( ! $isAPIKey && ! $isPrivilegedUser ) {
2022-08-15 12:56:19 +00:00
foreach ( Database :: PERMISSIONS as $type ) {
foreach ( $permissions as $permission ) {
2022-08-16 11:26:38 +00:00
$permission = Permission :: parse ( $permission );
if ( $permission -> getPermission () != $type ) {
2022-08-15 12:56:19 +00:00
continue ;
}
2022-08-16 11:26:38 +00:00
$role = ( new Role (
$permission -> getRole (),
$permission -> getIdentifier (),
$permission -> getDimension ()
)) -> toString ();
2022-08-15 12:56:19 +00:00
if ( ! Authorization :: isRole ( $role )) {
2023-08-22 03:25:55 +00:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'Permissions must be one of: (' . \implode ( ', ' , Authorization :: getRoles ()) . ')' );
2022-08-15 12:56:19 +00:00
}
}
}
2022-08-08 10:58:28 +00:00
}
2025-04-26 10:35:07 +00:00
$data [ '$collection' ] = $table -> getId (); // Adding this param to make API easier for developers
$data [ '$id' ] = $rowId == 'unique()' ? ID :: unique () : $rowId ;
2022-08-02 09:18:49 +00:00
$data [ '$permissions' ] = $permissions ;
2025-04-26 10:35:07 +00:00
$row = new Document ( $data );
2020-01-31 22:34:07 +00:00
2025-01-06 16:51:39 +00:00
$operations = 0 ;
2025-04-26 10:35:07 +00:00
$checkPermissions = function ( Document $table , Document $row , string $permission ) use ( & $checkPermissions , $dbForProject , $database , & $operations ) {
2025-01-06 16:51:39 +00:00
$operations ++ ;
2025-04-26 10:35:07 +00:00
$documentSecurity = $table -> getAttribute ( 'documentSecurity' , false );
2023-03-28 09:02:49 +00:00
$validator = new Authorization ( $permission );
2025-04-26 10:35:07 +00:00
$valid = $validator -> isValid ( $table -> getPermissionsByType ( $permission ));
2023-03-28 09:02:49 +00:00
if (( $permission === Database :: PERMISSION_UPDATE && ! $documentSecurity ) || ! $valid ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
if ( $permission === Database :: PERMISSION_UPDATE ) {
2025-04-26 10:35:07 +00:00
$valid = $valid || $validator -> isValid ( $row -> getUpdate ());
2023-03-28 09:02:49 +00:00
if ( $documentSecurity && ! $valid ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
}
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-28 09:02:49 +00:00
);
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 09:02:49 +00:00
if ( empty ( $related )) {
continue ;
}
2023-04-13 03:59:57 +00:00
2023-04-14 10:03:16 +00:00
$isList = \is_array ( $related ) && \array_values ( $related ) === $related ;
if ( $isList ) {
2023-04-13 03:59:57 +00:00
$relations = $related ;
} else {
$relations = [ $related ];
2023-03-29 01:33:53 +00:00
}
2023-03-28 09:02:49 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2023-03-28 09:02:49 +00:00
);
2023-04-13 03:59:57 +00:00
foreach ( $relations as & $relation ) {
if (
2023-04-21 01:44:53 +00:00
\is_array ( $relation )
&& \array_values ( $relation ) !== $relation
2023-04-13 03:59:57 +00:00
&& ! isset ( $relation [ '$id' ])
) {
$relation [ '$id' ] = ID :: unique ();
$relation = new Document ( $relation );
}
2023-03-29 01:37:56 +00:00
if ( $relation instanceof Document ) {
2025-01-02 07:56:14 +00:00
$current = Authorization :: skip (
2025-04-26 10:35:07 +00:00
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $relatedTable -> getInternalId (), $relation -> getId ())
2023-03-28 09:02:49 +00:00
);
2023-03-31 04:33:21 +00:00
if ( $current -> isEmpty ()) {
$type = Database :: PERMISSION_CREATE ;
2023-04-13 03:59:57 +00:00
if ( isset ( $relation [ '$id' ]) && $relation [ '$id' ] === 'unique()' ) {
2023-03-31 04:33:21 +00:00
$relation [ '$id' ] = ID :: unique ();
}
} else {
2023-04-03 10:45:10 +00:00
$relation -> removeAttribute ( '$collectionId' );
$relation -> removeAttribute ( '$databaseId' );
2025-04-26 10:35:07 +00:00
$relation -> setAttribute ( '$collection' , $relatedTable -> getId ());
2023-03-31 04:33:21 +00:00
$type = Database :: PERMISSION_UPDATE ;
}
2023-03-28 09:02:49 +00:00
2025-04-26 10:35:07 +00:00
$checkPermissions ( $relatedTable , $relation , $type );
2023-03-28 09:02:49 +00:00
}
}
2023-04-13 03:59:57 +00:00
2023-04-14 10:03:16 +00:00
if ( $isList ) {
2025-04-26 10:35:07 +00:00
$row -> setAttribute ( $relationship -> getAttribute ( 'key' ), \array_values ( $relations ));
2023-04-13 03:59:57 +00:00
} else {
2025-04-26 10:35:07 +00:00
$row -> setAttribute ( $relationship -> getAttribute ( 'key' ), \reset ( $relations ));
2023-04-13 03:59:57 +00:00
}
2023-03-28 09:02:49 +00:00
}
};
2025-04-26 10:35:07 +00:00
$checkPermissions ( $table , $row , Database :: PERMISSION_CREATE );
2023-03-28 09:02:49 +00:00
2024-03-06 17:34:21 +00:00
try {
2025-04-26 10:35:07 +00:00
$row = $dbForProject -> createDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $row );
2024-11-04 07:57:08 +00:00
} catch ( StructureException $e ) {
throw new Exception ( Exception :: DOCUMENT_INVALID_STRUCTURE , $e -> getMessage ());
} catch ( DuplicateException $e ) {
2024-03-06 17:34:21 +00:00
throw new Exception ( Exception :: DOCUMENT_ALREADY_EXISTS );
2024-11-04 07:57:08 +00:00
} catch ( NotFoundException $e ) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2024-03-06 17:34:21 +00:00
}
2020-01-31 22:34:07 +00:00
2025-01-02 08:09:44 +00:00
2025-04-26 10:35:07 +00:00
// Add $tableId and $databaseId for all rows
$processRow = function ( Document $table , Document $row ) use ( & $processRow , $dbForProject , $database ) {
$row -> setAttribute ( '$databaseId' , $database -> getId ());
$row -> setAttribute ( '$collectionId' , $table -> getId ());
2023-03-27 06:03:00 +00:00
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-27 06:03:00 +00:00
);
2023-03-28 03:10:12 +00:00
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 03:10:12 +00:00
if ( empty ( $related )) {
2023-03-27 06:03:00 +00:00
continue ;
}
2023-03-28 03:10:12 +00:00
if ( ! \is_array ( $related )) {
$related = [ $related ];
}
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2023-03-28 03:10:12 +00:00
);
2023-03-27 06:03:00 +00:00
2023-03-29 01:37:56 +00:00
foreach ( $related as $relation ) {
2023-04-03 10:39:31 +00:00
if ( $relation instanceof Document ) {
2025-04-26 10:35:07 +00:00
$processRow ( $relatedTable , $relation );
2023-04-03 10:39:31 +00:00
}
2023-03-27 02:42:36 +00:00
}
}
};
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$processRow ( $table , $row );
2020-01-31 22:34:07 +00:00
2025-02-12 11:19:51 +00:00
$queueForStatsUsage
2025-03-18 14:20:25 +00:00
-> addMetric ( METRIC_DATABASES_OPERATIONS_WRITES , max ( $operations , 1 ))
2025-03-30 05:54:48 +00:00
-> addMetric ( str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_OPERATIONS_WRITES ), $operations ); // per collection
2025-01-02 14:45:21 +00:00
2025-01-06 15:19:39 +00:00
$response -> addHeader ( 'X-Debug-Operations' , $operations );
2024-07-11 15:03:28 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
2025-04-26 10:35:07 +00:00
-> dynamic ( $row , Response :: MODEL_DOCUMENT );
2024-07-11 15:03:28 +00:00
$relationships = \array_map (
fn ( $document ) => $document -> getAttribute ( 'key' ),
\array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-07-11 15:03:28 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
)
);
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
-> setParam ( 'rowId' , $row -> getId ())
-> setContext ( 'table' , $table )
2022-06-22 10:51:49 +00:00
-> setContext ( 'database' , $database )
2024-07-11 15:03:28 +00:00
-> setPayload ( $response -> getPayload (), sensitive : $relationships );
2020-12-26 15:05:04 +00:00
});
2020-01-31 22:34:07 +00:00
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/rows' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents' )
-> desc ( 'List rows' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2019-06-08 13:13:19 +00:00
-> label ( 'scope' , 'documents.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'rows' ,
name : 'listRows' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/list-documents.md' ,
auth : [ AuthType :: SESSION , AuthType :: KEY , AuthType :: JWT ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DOCUMENT_LIST ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
2023-03-29 19:38:39 +00:00
-> param ( 'queries' , [], new ArrayList ( new Text ( APP_LIMIT_ARRAY_ELEMENT_SIZE ), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.' , true )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , array $queries , Response $response , Database $dbForProject , StatsUsage $queueForStatsUsage ) {
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2023-08-08 19:46:01 +00:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
2023-07-31 18:24:21 +00:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
2019-05-09 06:54:39 +00:00
2023-08-16 21:58:25 +00:00
if ( $database -> isEmpty () || ( ! $database -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
$table = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId ));
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty () || ( ! $table -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2023-04-19 00:34:39 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2022-08-20 00:47:10 +00:00
}
2021-12-28 17:57:24 +00:00
2024-02-08 16:10:25 +00:00
try {
2024-02-12 16:02:04 +00:00
$queries = Query :: parseQueries ( $queries );
2024-02-08 16:10:25 +00:00
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2020-06-29 21:43:34 +00:00
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-06 14:10:40 +00:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 03:25:55 +00:00
});
$cursor = \reset ( $cursor );
2022-08-30 11:55:23 +00:00
if ( $cursor ) {
2024-05-09 16:54:28 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2025-04-26 10:35:07 +00:00
$rowId = $cursor -> getValue ();
2021-07-05 20:27:20 +00:00
2025-04-26 10:35:07 +00:00
$cursorRow = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $rowId ));
2022-08-24 13:24:19 +00:00
2025-04-26 10:35:07 +00:00
if ( $cursorRow -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Row ' { $rowId } ' for the 'cursor' value not found. " );
2021-08-05 19:01:00 +00:00
}
2022-08-11 23:53:52 +00:00
2025-04-26 10:35:07 +00:00
$cursor -> setValue ( $cursorRow );
2022-08-11 23:53:52 +00:00
}
2025-04-16 11:33:14 +00:00
try {
2025-04-26 10:35:07 +00:00
$rows = $dbForProject -> find ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $queries );
$total = $dbForProject -> count ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $queries , APP_LIMIT_COUNT );
2025-04-16 11:33:14 +00:00
} catch ( OrderException $e ) {
2025-04-26 10:35:07 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order column ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all rows order column values are non-null. " );
2025-04-16 11:33:14 +00:00
}
2021-08-12 01:26:31 +00:00
2025-01-06 16:01:44 +00:00
$operations = 0 ;
2025-01-02 08:09:44 +00:00
2025-04-26 10:35:07 +00:00
// Add $tableId and $databaseId for all rows
$processRow = ( function ( Document $table , Document $row ) use ( & $processRow , $dbForProject , $database , & $operations ) : bool {
if ( $row -> isEmpty ()) {
2023-03-28 09:02:49 +00:00
return false ;
}
2025-01-06 16:01:44 +00:00
$operations ++ ;
2025-04-26 10:35:07 +00:00
$row -> removeAttribute ( '$collection' );
$row -> setAttribute ( '$databaseId' , $database -> getId ());
$row -> setAttribute ( '$collectionId' , $table -> getId ());
2023-03-27 06:03:00 +00:00
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-27 06:03:00 +00:00
);
2023-03-28 03:10:12 +00:00
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 03:10:12 +00:00
if ( empty ( $related )) {
2025-01-06 16:51:39 +00:00
if ( \in_array ( \gettype ( $related ), [ 'array' , 'object' ])) {
$operations ++ ;
}
2023-03-27 06:03:00 +00:00
continue ;
}
2025-01-06 16:01:44 +00:00
2023-03-28 03:10:12 +00:00
if ( ! \is_array ( $related )) {
2023-03-28 09:02:49 +00:00
$relations = [ $related ];
} else {
$relations = $related ;
2023-03-28 03:10:12 +00:00
}
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
2025-01-02 08:09:44 +00:00
// todo: Use local cache for this getDocument
2025-04-26 10:35:07 +00:00
$relatedTable = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId ));
2023-03-27 06:03:00 +00:00
2023-03-28 09:02:49 +00:00
foreach ( $relations as $index => $doc ) {
2023-03-31 04:56:25 +00:00
if ( $doc instanceof Document ) {
2025-04-26 10:35:07 +00:00
if ( ! $processRow ( $relatedTable , $doc )) {
2023-03-31 04:56:25 +00:00
unset ( $relations [ $index ]);
}
2023-03-28 09:02:49 +00:00
}
}
if ( \is_array ( $related )) {
2025-04-26 10:35:07 +00:00
$row -> setAttribute ( $relationship -> getAttribute ( 'key' ), \array_values ( $relations ));
2023-03-28 09:02:49 +00:00
} elseif ( empty ( $relations )) {
2025-04-26 10:35:07 +00:00
$row -> setAttribute ( $relationship -> getAttribute ( 'key' ), null );
2023-03-27 02:42:36 +00:00
}
}
2023-03-28 09:02:49 +00:00
return true ;
2023-08-16 21:58:25 +00:00
});
2023-03-27 02:42:36 +00:00
2025-04-26 10:35:07 +00:00
foreach ( $rows as $row ) {
$processRow ( $table , $row );
2023-08-16 21:58:25 +00:00
}
2021-12-27 10:45:24 +00:00
2025-02-12 11:19:51 +00:00
$queueForStatsUsage
2025-03-18 14:20:25 +00:00
-> addMetric ( METRIC_DATABASES_OPERATIONS_READS , max ( $operations , 1 ))
2025-02-12 11:19:51 +00:00
-> addMetric ( str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_OPERATIONS_READS ), $operations );
2025-01-06 09:28:26 +00:00
2025-01-06 15:19:39 +00:00
$response -> addHeader ( 'X-Debug-Operations' , $operations );
2025-01-06 14:50:23 +00:00
2024-02-24 10:55:33 +00:00
$select = \array_reduce ( $queries , function ( $result , $query ) {
return $result || ( $query -> getMethod () === Query :: TYPE_SELECT );
}, false );
// Check if the SELECT query includes $databaseId and $collectionId
$hasDatabaseId = false ;
2025-04-26 10:35:07 +00:00
$hasTableId = false ;
2024-02-24 10:55:33 +00:00
if ( $select ) {
$hasDatabaseId = \array_reduce ( $queries , function ( $result , $query ) {
return $result || ( $query -> getMethod () === Query :: TYPE_SELECT && \in_array ( '$databaseId' , $query -> getValues ()));
}, false );
2025-04-26 10:35:07 +00:00
$hasTableId = \array_reduce ( $queries , function ( $result , $query ) {
2024-02-24 10:55:33 +00:00
return $result || ( $query -> getMethod () === Query :: TYPE_SELECT && \in_array ( '$collectionId' , $query -> getValues ()));
}, false );
}
if ( $select ) {
2025-04-26 10:35:07 +00:00
foreach ( $rows as $row ) {
2024-02-24 10:55:33 +00:00
if ( ! $hasDatabaseId ) {
2025-04-26 10:35:07 +00:00
$row -> removeAttribute ( '$databaseId' );
2024-02-24 10:55:33 +00:00
}
2025-04-26 10:35:07 +00:00
if ( ! $hasTableId ) {
$row -> removeAttribute ( '$collectionId' );
2024-02-24 10:55:33 +00:00
}
}
}
2021-07-25 14:47:18 +00:00
$response -> dynamic ( new Document ([
2022-02-27 09:57:09 +00:00
'total' => $total ,
2025-04-26 10:35:07 +00:00
'documents' => $rows ,
2021-06-14 19:55:36 +00:00
]), Response :: MODEL_DOCUMENT_LIST );
2020-12-26 15:05:04 +00:00
});
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/rows/:rowId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents/:rowId' )
-> desc ( 'Get row' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2019-06-08 13:13:19 +00:00
-> label ( 'scope' , 'documents.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'rows' ,
name : 'getRow' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/get-document.md' ,
auth : [ AuthType :: SESSION , AuthType :: KEY , AuthType :: JWT ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DOCUMENT ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
-> param ( 'rowId' , '' , new UID (), 'Row ID.' )
2024-02-02 10:54:34 +00:00
-> param ( 'queries' , [], new ArrayList ( new Text ( APP_LIMIT_ARRAY_ELEMENT_SIZE ), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.' , true )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $rowId , array $queries , Response $response , Database $dbForProject , StatsUsage $queueForStatsUsage ) {
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2023-08-08 19:46:01 +00:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
2023-07-31 18:24:21 +00:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
2019-05-09 06:54:39 +00:00
2023-08-16 21:58:25 +00:00
if ( $database -> isEmpty () || ( ! $database -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
$table = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId ));
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty () || ( ! $table -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2023-08-16 21:58:25 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-06-10 18:19:42 +00:00
}
2023-08-22 03:25:55 +00:00
try {
2024-02-12 16:02:04 +00:00
$queries = Query :: parseQueries ( $queries );
2025-04-26 10:35:07 +00:00
$row = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $rowId , $queries );
2023-08-22 03:25:55 +00:00
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
} catch ( QueryException $e ) {
2024-02-12 16:02:04 +00:00
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
2022-08-24 08:50:05 +00:00
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
if ( $row -> isEmpty ()) {
2022-08-16 09:07:30 +00:00
throw new Exception ( Exception :: DOCUMENT_NOT_FOUND );
2022-08-08 10:58:28 +00:00
}
2025-01-06 16:01:44 +00:00
$operations = 0 ;
2025-01-02 08:09:44 +00:00
2025-04-26 10:35:07 +00:00
// Add $tableId and $databaseId for all rows
$processRow = function ( Document $table , Document $row ) use ( & $processRow , $dbForProject , $database , & $operations ) {
if ( $row -> isEmpty ()) {
2023-04-19 00:37:41 +00:00
return ;
2023-03-28 09:02:49 +00:00
}
2025-01-06 16:01:44 +00:00
$operations ++ ;
2025-04-26 10:35:07 +00:00
$row -> setAttribute ( '$databaseId' , $database -> getId ());
$row -> setAttribute ( '$collectionId' , $table -> getId ());
2023-03-27 06:03:00 +00:00
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-27 06:03:00 +00:00
);
2023-03-28 03:10:12 +00:00
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 03:10:12 +00:00
if ( empty ( $related )) {
2025-01-06 16:51:39 +00:00
if ( \in_array ( \gettype ( $related ), [ 'array' , 'object' ])) {
$operations ++ ;
}
2023-03-27 06:03:00 +00:00
continue ;
}
2025-01-06 16:01:44 +00:00
2023-03-28 03:10:12 +00:00
if ( ! \is_array ( $related )) {
$related = [ $related ];
}
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2023-03-28 03:10:12 +00:00
);
2023-03-27 02:42:36 +00:00
2023-03-29 01:37:56 +00:00
foreach ( $related as $relation ) {
2023-04-03 10:39:31 +00:00
if ( $relation instanceof Document ) {
2025-04-26 10:35:07 +00:00
$processRow ( $relatedTable , $relation );
2023-04-03 10:39:31 +00:00
}
2023-03-27 02:42:36 +00:00
}
}
};
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$processRow ( $table , $row );
2021-12-27 10:45:24 +00:00
2025-02-12 11:19:51 +00:00
$queueForStatsUsage
2025-03-18 14:20:25 +00:00
-> addMetric ( METRIC_DATABASES_OPERATIONS_READS , max ( $operations , 1 ))
2025-02-12 11:19:51 +00:00
-> addMetric ( str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_OPERATIONS_READS ), $operations );
2025-01-02 07:56:14 +00:00
2025-01-06 15:19:39 +00:00
$response -> addHeader ( 'X-Debug-Operations' , $operations );
2025-04-26 10:35:07 +00:00
$response -> dynamic ( $row , Response :: MODEL_DOCUMENT );
2020-12-26 15:05:04 +00:00
});
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/rows/:rowId/logs' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents/:rowId/logs' )
-> desc ( 'List row logs' )
2021-08-29 07:43:09 +00:00
-> groups ([ 'api' , 'database' ])
-> label ( 'scope' , 'documents.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-03-31 05:48:17 +00:00
group : 'logs' ,
2025-04-26 10:35:07 +00:00
name : 'listRowLogs' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/get-document-logs.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_LOG_LIST ,
)
],
contentType : ContentType :: JSON ,
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Collection ID.' )
-> param ( 'rowId' , '' , new UID (), 'Row ID.' )
2023-05-16 12:56:20 +00:00
-> param ( 'queries' , [], new Queries ([ new Limit (), new Offset ()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset' , true )
2021-08-29 07:43:09 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2021-08-29 07:43:09 +00:00
-> inject ( 'locale' )
-> inject ( 'geodb' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $rowId , array $queries , Response $response , Database $dbForProject , Locale $locale , Reader $geodb ) {
2021-08-29 07:43:09 +00:00
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
if ( $database -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2022-08-25 01:04:47 +00:00
2025-04-26 10:35:07 +00:00
$table = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
2021-08-29 07:43:09 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-08-29 07:43:09 +00:00
}
2025-04-26 10:35:07 +00:00
$row = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $rowId );
2021-08-29 07:43:09 +00:00
2025-04-26 10:35:07 +00:00
if ( $row -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DOCUMENT_NOT_FOUND );
2021-08-29 07:43:09 +00:00
}
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2025-02-25 08:03:37 +00:00
// Temp fix for logs
$queries [] = Query :: or ([
2025-02-26 01:00:16 +00:00
Query :: greaterThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-26T01:30+00:00' ))),
2025-02-25 08:03:37 +00:00
Query :: lessThan ( '$createdAt' , DateTime :: format ( new \DateTime ( '2025-02-13T00:00+00:00' ))),
]);
2025-02-25 05:51:11 +00:00
2021-12-27 12:45:23 +00:00
$audit = new Audit ( $dbForProject );
2025-04-26 10:35:07 +00:00
$resource = 'database/' . $databaseId . '/table/' . $tableId . '/row/' . $row -> getId ();
2025-02-25 07:26:00 +00:00
$logs = $audit -> getLogsByResource ( $resource , $queries );
2021-08-29 07:43:09 +00:00
$output = [];
foreach ( $logs as $i => & $log ) {
$log [ 'userAgent' ] = ( ! empty ( $log [ 'userAgent' ])) ? $log [ 'userAgent' ] : 'UNKNOWN' ;
2021-12-12 17:59:12 +00:00
$detector = new Detector ( $log [ 'userAgent' ]);
$detector -> skipBotDetection (); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
2021-08-29 07:43:09 +00:00
2021-12-12 17:59:12 +00:00
$os = $detector -> getOS ();
$client = $detector -> getClient ();
$device = $detector -> getDevice ();
2021-08-29 07:43:09 +00:00
$output [ $i ] = new Document ([
'event' => $log [ 'event' ],
2023-07-12 17:27:57 +00:00
'userId' => $log [ 'data' ][ 'userId' ],
2021-08-29 07:43:09 +00:00
'userEmail' => $log [ 'data' ][ 'userEmail' ] ? ? null ,
'userName' => $log [ 'data' ][ 'userName' ] ? ? null ,
'mode' => $log [ 'data' ][ 'mode' ] ? ? null ,
'ip' => $log [ 'ip' ],
'time' => $log [ 'time' ],
2021-12-12 17:59:12 +00:00
'osCode' => $os [ 'osCode' ],
'osName' => $os [ 'osName' ],
'osVersion' => $os [ 'osVersion' ],
'clientType' => $client [ 'clientType' ],
'clientCode' => $client [ 'clientCode' ],
'clientName' => $client [ 'clientName' ],
'clientVersion' => $client [ 'clientVersion' ],
'clientEngine' => $client [ 'clientEngine' ],
'clientEngineVersion' => $client [ 'clientEngineVersion' ],
'deviceName' => $device [ 'deviceName' ],
'deviceBrand' => $device [ 'deviceBrand' ],
'deviceModel' => $device [ 'deviceModel' ]
2021-08-29 07:43:09 +00:00
]);
$record = $geodb -> get ( $log [ 'ip' ]);
if ( $record ) {
2022-05-23 14:54:50 +00:00
$output [ $i ][ 'countryCode' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), false ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'countries.' . strtolower ( $record [ 'country' ][ 'iso_code' ]), $locale -> getText ( 'locale.country.unknown' ));
2021-08-29 07:43:09 +00:00
} else {
$output [ $i ][ 'countryCode' ] = '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'locale.country.unknown' );
}
}
2025-01-06 16:57:35 +00:00
2021-11-18 10:33:42 +00:00
$response -> dynamic ( new Document ([
2025-02-25 07:26:00 +00:00
'total' => $audit -> countLogsByResource ( $resource , $queries ),
2025-02-25 06:21:35 +00:00
'logs' => $output ,
2021-11-18 10:33:42 +00:00
]), Response :: MODEL_LOG_LIST );
2021-08-29 07:43:09 +00:00
});
2025-04-26 10:35:07 +00:00
App :: patch ( '/v1/databases/:databaseId/tables/:tableId/rows/:rowId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents/:rowId' )
-> desc ( 'Update row' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].rows.[rowId].update' )
2019-06-08 13:13:19 +00:00
-> label ( 'scope' , 'documents.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-04-26 10:35:07 +00:00
-> label ( 'audits.event' , 'row.update' )
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}' )
2022-08-31 03:58:32 +00:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-30 23:34:17 +00:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT * 2 )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'rows' ,
name : 'updateRow' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/update-document.md' ,
auth : [ AuthType :: SESSION , AuthType :: KEY , AuthType :: JWT ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_DOCUMENT ,
)
],
contentType : ContentType :: JSON
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Collection ID.' )
-> param ( 'rowId' , '' , new UID (), 'Row ID.' )
-> param ( 'data' , [], new JSON (), 'Row data as JSON object. Include only columns and value pairs to be updated.' , true )
2023-10-13 13:43:23 +00:00
-> param ( 'permissions' , null , new Permissions ( APP_LIMIT_ARRAY_PARAMS_SIZE , [ Database :: PERMISSION_READ , Database :: PERMISSION_UPDATE , Database :: PERMISSION_DELETE , Database :: PERMISSION_WRITE ]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).' , true )
2023-01-20 00:36:17 +00:00
-> inject ( 'requestTimestamp' )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $rowId , string | array $data , ? array $permissions , ? \DateTime $requestTimestamp , Response $response , Database $dbForProject , Event $queueForEvents , StatsUsage $queueForStatsUsage ) {
2019-05-09 06:54:39 +00:00
2022-08-08 10:58:28 +00:00
$data = ( \is_string ( $data )) ? \json_decode ( $data , true ) : $data ; // Cast to JSON array
2022-08-14 05:21:33 +00:00
if ( empty ( $data ) && \is_null ( $permissions )) {
2022-08-16 09:07:30 +00:00
throw new Exception ( Exception :: DOCUMENT_MISSING_PAYLOAD );
2022-08-08 10:58:28 +00:00
}
2025-01-02 07:56:14 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
2023-08-08 19:46:01 +00:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
2023-07-31 18:24:21 +00:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
2023-08-16 21:58:25 +00:00
if ( $database -> isEmpty () || ( ! $database -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
$table = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId ));
2019-09-16 19:03:24 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty () || ( ! $table -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2023-08-16 21:58:25 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-08-12 01:05:19 +00:00
}
2022-08-25 01:04:47 +00:00
// Read permission should not be required for update
2025-04-26 10:35:07 +00:00
/** @var Document $row */
$row = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $rowId ));
2021-06-15 13:38:24 +00:00
2025-04-26 10:35:07 +00:00
if ( $row -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DOCUMENT_NOT_FOUND );
2020-06-29 21:43:34 +00:00
}
2019-05-09 06:54:39 +00:00
2022-08-25 03:51:21 +00:00
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission :: aggregate ( $permissions , [
Database :: PERMISSION_READ ,
Database :: PERMISSION_UPDATE ,
Database :: PERMISSION_DELETE ,
]);
2022-08-15 12:56:19 +00:00
// Users can only manage their own roles, API keys and Admin users can manage any
2022-08-08 10:58:28 +00:00
$roles = Authorization :: getRoles ();
2023-08-16 21:58:25 +00:00
if ( ! $isAPIKey && ! $isPrivilegedUser && ! \is_null ( $permissions )) {
2022-08-15 12:56:19 +00:00
foreach ( Database :: PERMISSIONS as $type ) {
foreach ( $permissions as $permission ) {
2022-08-16 11:26:38 +00:00
$permission = Permission :: parse ( $permission );
if ( $permission -> getPermission () != $type ) {
2022-08-15 12:56:19 +00:00
continue ;
}
2022-08-16 11:26:38 +00:00
$role = ( new Role (
$permission -> getRole (),
$permission -> getIdentifier (),
$permission -> getDimension ()
)) -> toString ();
2022-08-15 12:56:19 +00:00
if ( ! Authorization :: isRole ( $role )) {
2022-08-25 03:51:21 +00:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'Permissions must be one of: (' . \implode ( ', ' , $roles ) . ')' );
2022-08-15 12:56:19 +00:00
}
2022-08-08 10:58:28 +00:00
}
}
}
2022-08-02 09:18:49 +00:00
2022-08-25 11:46:56 +00:00
if ( \is_null ( $permissions )) {
2025-04-26 10:35:07 +00:00
$permissions = $row -> getPermissions () ? ? [];
2022-08-25 11:46:56 +00:00
}
2025-04-26 10:35:07 +00:00
$data [ '$id' ] = $rowId ;
2022-08-02 09:18:49 +00:00
$data [ '$permissions' ] = $permissions ;
2025-04-26 10:35:07 +00:00
$newRow = new Document ( $data );
2020-10-30 19:53:27 +00:00
2025-01-06 16:51:39 +00:00
$operations = 0 ;
2025-01-06 16:01:44 +00:00
2025-04-26 10:35:07 +00:00
$setTable = ( function ( Document $collection , Document $document ) use ( & $setTable , $dbForProject , $database , & $operations ) {
2025-01-06 16:51:39 +00:00
$operations ++ ;
2023-03-28 09:02:49 +00:00
$relationships = \array_filter (
$collection -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-28 09:02:49 +00:00
);
foreach ( $relationships as $relationship ) {
$related = $document -> getAttribute ( $relationship -> getAttribute ( 'key' ));
if ( empty ( $related )) {
continue ;
}
2023-04-13 03:59:57 +00:00
2023-04-14 10:03:16 +00:00
$isList = \is_array ( $related ) && \array_values ( $related ) === $related ;
if ( $isList ) {
2023-04-13 03:59:57 +00:00
$relations = $related ;
} else {
$relations = [ $related ];
2023-03-29 01:33:53 +00:00
}
2023-03-28 09:02:49 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2025-01-02 07:56:14 +00:00
);
2023-03-28 09:02:49 +00:00
2023-04-19 00:37:41 +00:00
foreach ( $relations as & $relation ) {
2023-07-18 11:16:33 +00:00
// If the relation is an array it can be either update or create a child document.
2023-04-13 03:59:57 +00:00
if (
\is_array ( $relation )
&& \array_values ( $relation ) !== $relation
&& ! isset ( $relation [ '$id' ])
) {
$relation [ '$id' ] = ID :: unique ();
$relation = new Document ( $relation );
}
2023-03-29 01:37:56 +00:00
if ( $relation instanceof Document ) {
2025-04-26 10:35:07 +00:00
$oldRow = Authorization :: skip ( fn () => $dbForProject -> getDocument (
'database_' . $database -> getInternalId () . '_collection_' . $relatedTable -> getInternalId (),
2025-01-02 07:56:14 +00:00
$relation -> getId ()
));
2023-08-07 23:10:07 +00:00
$relation -> removeAttribute ( '$collectionId' );
$relation -> removeAttribute ( '$databaseId' );
// Attribute $collection is required for Utopia.
2025-01-02 07:56:14 +00:00
$relation -> setAttribute (
'$collection' ,
2025-04-26 10:35:07 +00:00
'database_' . $database -> getInternalId () . '_collection_' . $relatedTable -> getInternalId ()
2025-01-02 07:56:14 +00:00
);
2023-03-31 04:33:21 +00:00
2025-04-26 10:35:07 +00:00
if ( $oldRow -> isEmpty ()) {
2023-04-13 03:59:57 +00:00
if ( isset ( $relation [ '$id' ]) && $relation [ '$id' ] === 'unique()' ) {
2023-03-31 04:33:21 +00:00
$relation [ '$id' ] = ID :: unique ();
}
}
2025-04-26 10:35:07 +00:00
$setTable ( $relatedTable , $relation );
2023-03-28 09:02:49 +00:00
}
}
2023-04-13 03:59:57 +00:00
2023-04-14 10:03:16 +00:00
if ( $isList ) {
2023-04-13 03:59:57 +00:00
$document -> setAttribute ( $relationship -> getAttribute ( 'key' ), \array_values ( $relations ));
} else {
$document -> setAttribute ( $relationship -> getAttribute ( 'key' ), \reset ( $relations ));
}
2022-08-24 08:50:05 +00:00
}
2023-08-17 13:34:41 +00:00
});
2023-03-28 09:02:49 +00:00
2025-04-26 10:35:07 +00:00
$setTable ( $table , $newRow );
2022-08-08 10:58:28 +00:00
2025-02-12 11:19:51 +00:00
$queueForStatsUsage
2025-03-18 14:20:25 +00:00
-> addMetric ( METRIC_DATABASES_OPERATIONS_WRITES , max ( $operations , 1 ))
2025-02-12 11:19:51 +00:00
-> addMetric ( str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_OPERATIONS_WRITES ), $operations );
2025-01-06 09:28:26 +00:00
2025-01-06 15:19:39 +00:00
$response -> addHeader ( 'X-Debug-Operations' , $operations );
2025-01-06 14:50:23 +00:00
2023-08-17 13:34:41 +00:00
try {
2025-04-26 10:35:07 +00:00
$row = $dbForProject -> withRequestTimestamp (
2025-01-02 07:56:14 +00:00
$requestTimestamp ,
fn () => $dbForProject -> updateDocument (
2025-04-26 10:35:07 +00:00
'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (),
$row -> getId (),
$newRow
2025-01-02 07:56:14 +00:00
)
);
2022-08-25 11:30:26 +00:00
} catch ( AuthorizationException ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2022-08-08 10:58:28 +00:00
} catch ( DuplicateException ) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DOCUMENT_ALREADY_EXISTS );
2024-11-04 07:57:08 +00:00
} catch ( StructureException $e ) {
throw new Exception ( Exception :: DOCUMENT_INVALID_STRUCTURE , $e -> getMessage ());
} catch ( NotFoundException $e ) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-08-15 21:09:40 +00:00
}
2021-12-16 18:12:06 +00:00
2025-04-26 10:35:07 +00:00
// Add $tableId and $databaseId for all rows
$processRow = function ( Document $table , Document $row ) use ( & $processRow , $dbForProject , $database ) {
$row -> setAttribute ( '$databaseId' , $database -> getId ());
$row -> setAttribute ( '$collectionId' , $table -> getId ());
2023-03-27 02:42:36 +00:00
2023-03-27 06:03:00 +00:00
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-27 06:03:00 +00:00
);
2023-03-28 03:10:12 +00:00
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 03:10:12 +00:00
if ( empty ( $related )) {
2023-03-27 07:08:02 +00:00
continue ;
}
2023-03-28 03:10:12 +00:00
if ( ! \is_array ( $related )) {
$related = [ $related ];
}
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2025-01-02 07:56:14 +00:00
);
2023-03-27 07:08:02 +00:00
2023-03-29 01:37:56 +00:00
foreach ( $related as $relation ) {
2023-04-03 10:39:31 +00:00
if ( $relation instanceof Document ) {
2025-04-26 10:35:07 +00:00
$processRow ( $relatedTable , $relation );
2023-04-03 10:39:31 +00:00
}
2023-03-27 02:42:36 +00:00
}
}
};
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$processRow ( $table , $row );
2023-03-27 02:42:36 +00:00
2025-04-26 10:35:07 +00:00
$response -> dynamic ( $row , Response :: MODEL_DOCUMENT );
2024-07-11 15:03:28 +00:00
$relationships = \array_map (
fn ( $document ) => $document -> getAttribute ( 'key' ),
\array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-07-11 15:03:28 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
)
);
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
-> setParam ( 'rowId' , $row -> getId ())
-> setContext ( 'table' , $table )
2022-06-22 10:51:49 +00:00
-> setContext ( 'database' , $database )
2024-07-11 15:03:28 +00:00
-> setPayload ( $response -> getPayload (), sensitive : $relationships );
2020-12-26 15:05:04 +00:00
});
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
App :: delete ( '/v1/databases/:databaseId/tables/:tableId/rows/:rowId' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/documents/:rowId' )
-> desc ( 'Delete row' )
2020-06-25 18:32:12 +00:00
-> groups ([ 'api' , 'database' ])
2019-06-08 13:13:19 +00:00
-> label ( 'scope' , 'documents.write' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-04-26 10:35:07 +00:00
-> label ( 'event' , 'databases.[databaseId].tables.[tableId].rows.[rowId].delete' )
-> label ( 'audits.event' , 'row.delete' )
-> label ( 'audits.resource' , 'database/{request.databaseId}/table/{request.tableId}/row/{request.rowId}' )
2022-08-31 03:58:32 +00:00
-> label ( 'abuse-key' , 'ip:{ip},method:{method},url:{url},userId:{userId}' )
2022-08-30 23:34:17 +00:00
-> label ( 'abuse-limit' , APP_LIMIT_WRITE_RATE_DEFAULT )
-> label ( 'abuse-time' , APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-26 10:35:07 +00:00
group : 'rows' ,
name : 'deleteRow' ,
2025-01-17 04:31:39 +00:00
description : '/docs/references/databases/delete-document.md' ,
auth : [ AuthType :: SESSION , AuthType :: KEY , AuthType :: JWT ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2022-06-22 10:51:49 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).' )
-> param ( 'rowId' , '' , new UID (), 'Row ID.' )
2023-01-20 00:36:17 +00:00
-> inject ( 'requestTimestamp' )
2020-12-26 15:05:04 +00:00
-> inject ( 'response' )
2021-12-27 12:45:23 +00:00
-> inject ( 'dbForProject' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $tableId , string $rowId , ? \DateTime $requestTimestamp , Response $response , Database $dbForProject , Event $queueForEvents , StatsUsage $queueForStatsUsage ) {
2024-03-06 17:34:21 +00:00
$database = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2022-06-22 10:51:49 +00:00
2023-08-08 19:46:01 +00:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
2023-07-31 18:24:21 +00:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
2019-05-09 06:54:39 +00:00
2023-08-16 21:58:25 +00:00
if ( $database -> isEmpty () || ( ! $database -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
2022-06-22 10:51:49 +00:00
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
$table = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId ));
2019-05-09 06:54:39 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty () || ( ! $table -> getAttribute ( 'enabled' , false ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2023-08-16 21:58:25 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2021-08-12 01:05:19 +00:00
}
2022-08-25 01:04:47 +00:00
// Read permission should not be required for delete
2025-04-26 10:35:07 +00:00
$row = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (), $rowId ));
2021-06-10 18:19:42 +00:00
2025-04-26 10:35:07 +00:00
if ( $row -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: DOCUMENT_NOT_FOUND );
2020-06-29 21:43:34 +00:00
}
2019-05-09 06:54:39 +00:00
2024-11-04 07:57:08 +00:00
try {
2025-04-26 10:35:07 +00:00
$dbForProject -> withRequestTimestamp ( $requestTimestamp , function () use ( $dbForProject , $database , $table , $rowId ) {
2025-01-02 07:56:14 +00:00
$dbForProject -> deleteDocument (
2025-04-26 10:35:07 +00:00
'database_' . $database -> getInternalId () . '_collection_' . $table -> getInternalId (),
$rowId
2025-01-02 07:56:14 +00:00
);
2024-11-04 07:57:08 +00:00
});
} catch ( NotFoundException $e ) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
2022-08-08 10:58:28 +00:00
2025-04-26 10:35:07 +00:00
// Add $tableId and $databaseId for all rows
$processRow = function ( Document $table , Document $row ) use ( & $processRow , $dbForProject , $database ) {
$row -> setAttribute ( '$databaseId' , $database -> getId ());
$row -> setAttribute ( '$collectionId' , $table -> getId ());
2021-06-10 18:19:42 +00:00
2023-03-27 06:03:00 +00:00
$relationships = \array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-03-06 17:34:21 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
2023-03-27 06:03:00 +00:00
);
2023-03-28 03:10:12 +00:00
foreach ( $relationships as $relationship ) {
2025-04-26 10:35:07 +00:00
$related = $row -> getAttribute ( $relationship -> getAttribute ( 'key' ));
2023-03-28 03:10:12 +00:00
if ( empty ( $related )) {
2023-03-27 06:03:00 +00:00
continue ;
}
2023-03-28 03:10:12 +00:00
if ( ! \is_array ( $related )) {
$related = [ $related ];
}
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$relatedTableId = $relationship -> getAttribute ( 'relatedCollection' );
$relatedTable = Authorization :: skip (
fn () => $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $relatedTableId )
2025-01-02 07:56:14 +00:00
);
2023-03-27 02:42:36 +00:00
2023-03-29 03:30:47 +00:00
foreach ( $related as $relation ) {
2023-04-03 10:39:31 +00:00
if ( $relation instanceof Document ) {
2025-04-26 10:35:07 +00:00
$processRow ( $relatedTable , $relation );
2023-04-03 10:39:31 +00:00
}
2023-03-27 02:42:36 +00:00
}
}
};
2023-03-27 06:03:00 +00:00
2025-04-26 10:35:07 +00:00
$processRow ( $table , $row );
2021-12-27 10:45:24 +00:00
2025-02-12 11:19:51 +00:00
$queueForStatsUsage
-> addMetric ( METRIC_DATABASES_OPERATIONS_WRITES , 1 )
2025-03-30 05:54:48 +00:00
-> addMetric ( str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_OPERATIONS_WRITES ), 1 ); // per collection
2025-01-06 09:28:26 +00:00
2025-01-06 17:07:18 +00:00
$response -> addHeader ( 'X-Debug-Operations' , 1 );
2025-01-06 16:57:35 +00:00
2024-07-11 15:03:28 +00:00
$relationships = \array_map (
fn ( $document ) => $document -> getAttribute ( 'key' ),
\array_filter (
2025-04-26 10:35:07 +00:00
$table -> getAttribute ( 'attributes' , []),
2024-07-11 15:03:28 +00:00
fn ( $attribute ) => $attribute -> getAttribute ( 'type' ) === Database :: VAR_RELATIONSHIP
)
);
2022-01-03 10:29:15 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
2022-06-22 10:51:49 +00:00
-> setParam ( 'databaseId' , $databaseId )
2025-04-26 10:35:07 +00:00
-> setParam ( 'tableId' , $table -> getId ())
-> setParam ( 'rowId' , $row -> getId ())
-> setContext ( 'table' , $table )
2022-06-22 10:51:49 +00:00
-> setContext ( 'database' , $database )
2025-04-26 10:35:07 +00:00
-> setPayload ( $response -> output ( $row , Response :: MODEL_DOCUMENT ), sensitive : $relationships );
2021-04-14 09:02:17 +00:00
2020-06-29 21:43:34 +00:00
$response -> noContent ();
2021-08-24 14:03:32 +00:00
});
2022-06-22 10:51:49 +00:00
App :: get ( '/v1/databases/usage' )
2024-02-26 02:44:20 +00:00
-> desc ( 'Get databases usage stats' )
2023-11-06 21:28:45 +00:00
-> groups ([ 'api' , 'database' , 'usage' ])
2022-06-23 08:50:11 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getUsage' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/databases/get-usage.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USAGE_DATABASES ,
)
],
contentType : ContentType :: JSON
))
2023-11-08 09:09:32 +00:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '30d' , '90d' ], true ), '`Date range.' , true )
2022-06-23 08:50:11 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $range , Response $response , Database $dbForProject ) {
2023-10-25 07:39:59 +00:00
$periods = Config :: getParam ( 'usage' , []);
$stats = $usage = [];
$days = $periods [ $range ];
$metrics = [
METRIC_DATABASES ,
METRIC_COLLECTIONS ,
METRIC_DOCUMENTS ,
2025-01-23 13:24:02 +00:00
METRIC_DATABASES_STORAGE ,
METRIC_DATABASES_OPERATIONS_READS ,
METRIC_DATABASES_OPERATIONS_WRITES ,
2023-10-25 07:39:59 +00:00
];
2022-06-23 08:50:11 +00:00
2023-11-08 09:09:32 +00:00
Authorization :: skip ( function () use ( $dbForProject , $days , $metrics , & $stats ) {
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$result = $dbForProject -> findOne ( 'stats' , [
2023-11-01 15:20:12 +00:00
Query :: equal ( 'metric' , [ $metric ]),
Query :: equal ( 'period' , [ 'inf' ])
]);
2022-06-22 10:51:49 +00:00
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'total' ] = $result [ 'value' ] ? ? 0 ;
2023-10-25 07:39:59 +00:00
$limit = $days [ 'limit' ];
$period = $days [ 'period' ];
2024-02-01 10:21:50 +00:00
$results = $dbForProject -> find ( 'stats' , [
2023-10-25 07:39:59 +00:00
Query :: equal ( 'metric' , [ $metric ]),
2023-10-25 12:06:54 +00:00
Query :: equal ( 'period' , [ $period ]),
2023-10-25 07:39:59 +00:00
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ] = [];
2023-10-25 07:39:59 +00:00
foreach ( $results as $result ) {
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ][ $result -> getAttribute ( 'time' )] = [
'value' => $result -> getAttribute ( 'value' ),
2023-10-25 07:39:59 +00:00
];
2023-10-15 17:41:09 +00:00
}
2023-10-25 07:39:59 +00:00
}
});
2023-10-15 17:41:09 +00:00
2023-10-25 07:39:59 +00:00
$format = match ( $days [ 'period' ]) {
'1h' => 'Y-m-d\TH:00:00.000P' ,
'1d' => 'Y-m-d\T00:00:00.000P' ,
};
2023-10-15 17:41:09 +00:00
2024-03-06 17:34:21 +00:00
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$usage [ $metric ][ 'total' ] = $stats [ $metric ][ 'total' ];
2024-03-06 17:34:21 +00:00
$usage [ $metric ][ 'data' ] = [];
$leap = time () - ( $days [ 'limit' ] * $days [ 'factor' ]);
while ( $leap < time ()) {
$leap += $days [ 'factor' ];
$formatDate = date ( $format , $leap );
$usage [ $metric ][ 'data' ][] = [
'value' => $stats [ $metric ][ 'data' ][ $formatDate ][ 'value' ] ? ? 0 ,
'date' => $formatDate ,
];
}
2022-06-23 08:50:11 +00:00
}
2023-10-25 07:39:59 +00:00
$response -> dynamic ( new Document ([
'range' => $range ,
2025-05-04 07:52:26 +00:00
'databasesTotal' => $usage [ $metrics [ 0 ]][ 'total' ],
2023-11-08 09:09:32 +00:00
'collectionsTotal' => $usage [ $metrics [ 1 ]][ 'total' ],
2025-05-04 07:52:26 +00:00
'documentsTotal' => $usage [ $metrics [ 2 ]][ 'total' ],
'storageTotal' => $usage [ $metrics [ 3 ]][ 'total' ],
2025-01-23 13:24:02 +00:00
'databasesReadsTotal' => $usage [ $metrics [ 4 ]][ 'total' ],
'databasesWritesTotal' => $usage [ $metrics [ 5 ]][ 'total' ],
2025-05-04 07:52:26 +00:00
'databases' => $usage [ $metrics [ 0 ]][ 'data' ],
2023-11-08 09:09:32 +00:00
'collections' => $usage [ $metrics [ 1 ]][ 'data' ],
2025-05-04 07:52:26 +00:00
'documents' => $usage [ $metrics [ 2 ]][ 'data' ],
'storage' => $usage [ $metrics [ 3 ]][ 'data' ],
2025-01-23 13:24:02 +00:00
'databasesReads' => $usage [ $metrics [ 4 ]][ 'data' ],
'databasesWrites' => $usage [ $metrics [ 5 ]][ 'data' ],
2023-10-25 07:39:59 +00:00
]), Response :: MODEL_USAGE_DATABASES );
2022-06-23 08:50:11 +00:00
});
2022-06-22 10:51:49 +00:00
App :: get ( '/v1/databases/:databaseId/usage' )
2024-02-26 02:44:20 +00:00
-> desc ( 'Get database usage stats' )
2023-11-06 21:28:45 +00:00
-> groups ([ 'api' , 'database' , 'usage' ])
2022-06-23 08:50:11 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getDatabaseUsage' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/databases/get-database-usage.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USAGE_DATABASE ,
)
],
contentType : ContentType :: JSON ,
))
2022-06-23 08:50:11 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2023-11-08 09:09:32 +00:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '30d' , '90d' ], true ), '`Date range.' , true )
2022-06-23 08:50:11 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $databaseId , string $range , Response $response , Database $dbForProject ) {
2025-05-04 07:52:26 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
2022-06-23 08:50:11 +00:00
2023-10-25 07:39:59 +00:00
if ( $database -> isEmpty ()) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
}
2022-06-23 08:50:11 +00:00
2023-10-25 07:39:59 +00:00
$periods = Config :: getParam ( 'usage' , []);
$stats = $usage = [];
$days = $periods [ $range ];
$metrics = [
str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_COLLECTIONS ),
str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_DOCUMENTS ),
2025-01-23 13:24:02 +00:00
str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASE_ID_STORAGE ),
str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASES_OPERATIONS_READS ),
str_replace ( '{databaseInternalId}' , $database -> getInternalId (), METRIC_DATABASES_OPERATIONS_WRITES )
2023-10-25 07:39:59 +00:00
];
2022-06-22 10:51:49 +00:00
2023-11-08 09:09:32 +00:00
Authorization :: skip ( function () use ( $dbForProject , $days , $metrics , & $stats ) {
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$result = $dbForProject -> findOne ( 'stats' , [
2023-11-01 15:20:12 +00:00
Query :: equal ( 'metric' , [ $metric ]),
Query :: equal ( 'period' , [ 'inf' ])
]);
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'total' ] = $result [ 'value' ] ? ? 0 ;
2023-10-25 07:39:59 +00:00
$limit = $days [ 'limit' ];
$period = $days [ 'period' ];
2024-02-01 10:21:50 +00:00
$results = $dbForProject -> find ( 'stats' , [
2023-10-25 07:39:59 +00:00
Query :: equal ( 'metric' , [ $metric ]),
2023-10-25 12:06:54 +00:00
Query :: equal ( 'period' , [ $period ]),
2023-10-25 07:39:59 +00:00
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ] = [];
2023-10-25 07:39:59 +00:00
foreach ( $results as $result ) {
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ][ $result -> getAttribute ( 'time' )] = [
'value' => $result -> getAttribute ( 'value' ),
2023-10-25 07:39:59 +00:00
];
2022-06-22 10:51:49 +00:00
}
2023-10-25 07:39:59 +00:00
}
});
$format = match ( $days [ 'period' ]) {
'1h' => 'Y-m-d\TH:00:00.000P' ,
'1d' => 'Y-m-d\T00:00:00.000P' ,
};
2024-03-06 17:34:21 +00:00
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$usage [ $metric ][ 'total' ] = $stats [ $metric ][ 'total' ];
2024-03-06 17:34:21 +00:00
$usage [ $metric ][ 'data' ] = [];
$leap = time () - ( $days [ 'limit' ] * $days [ 'factor' ]);
while ( $leap < time ()) {
$leap += $days [ 'factor' ];
$formatDate = date ( $format , $leap );
$usage [ $metric ][ 'data' ][] = [
'value' => $stats [ $metric ][ 'data' ][ $formatDate ][ 'value' ] ? ? 0 ,
'date' => $formatDate ,
];
}
2022-06-23 08:50:11 +00:00
}
2022-06-22 10:51:49 +00:00
2023-10-25 07:39:59 +00:00
$response -> dynamic ( new Document ([
'range' => $range ,
2025-05-04 07:52:26 +00:00
'collectionsTotal' => $usage [ $metrics [ 0 ]][ 'total' ],
'documentsTotal' => $usage [ $metrics [ 1 ]][ 'total' ],
'storageTotal' => $usage [ $metrics [ 2 ]][ 'total' ],
2025-01-23 13:24:02 +00:00
'databaseReadsTotal' => $usage [ $metrics [ 3 ]][ 'total' ],
'databaseWritesTotal' => $usage [ $metrics [ 4 ]][ 'total' ],
2025-05-04 07:52:26 +00:00
'collections' => $usage [ $metrics [ 0 ]][ 'data' ],
'documents' => $usage [ $metrics [ 1 ]][ 'data' ],
'storage' => $usage [ $metrics [ 2 ]][ 'data' ],
'databaseReads' => $usage [ $metrics [ 3 ]][ 'data' ],
'databaseWrites' => $usage [ $metrics [ 4 ]][ 'data' ],
2023-10-25 07:39:59 +00:00
]), Response :: MODEL_USAGE_DATABASE );
2022-06-23 08:50:11 +00:00
});
2022-06-22 10:51:49 +00:00
2025-04-26 10:35:07 +00:00
App :: get ( '/v1/databases/:databaseId/tables/:tableId/usage' )
-> alias ( '/v1/databases/:databaseId/collections/:tableId/usage' )
-> desc ( 'Get table usage stats' )
2023-11-06 21:28:45 +00:00
-> groups ([ 'api' , 'database' , 'usage' ])
2022-06-23 08:50:11 +00:00
-> label ( 'scope' , 'collections.read' )
2024-10-29 15:07:12 +00:00
-> label ( 'resourceType' , RESOURCE_TYPE_DATABASES )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'databases' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-04-26 10:35:07 +00:00
name : 'getTableUsage' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/databases/get-collection-usage.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_USAGE_COLLECTION ,
)
],
contentType : ContentType :: JSON ,
))
2022-06-23 08:50:11 +00:00
-> param ( 'databaseId' , '' , new UID (), 'Database ID.' )
2023-11-08 09:09:32 +00:00
-> param ( 'range' , '30d' , new WhiteList ([ '24h' , '30d' , '90d' ], true ), 'Date range.' , true )
2025-04-26 10:35:07 +00:00
-> param ( 'tableId' , '' , new UID (), 'Collection ID.' )
2022-06-23 08:50:11 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-04-26 10:35:07 +00:00
-> action ( function ( string $databaseId , string $range , string $tableId , Response $response , Database $dbForProject ) {
2022-06-22 10:51:49 +00:00
2024-03-06 17:34:21 +00:00
$database = $dbForProject -> getDocument ( 'databases' , $databaseId );
2025-04-26 10:35:07 +00:00
$tableDocument = $dbForProject -> getDocument ( 'database_' . $database -> getInternalId (), $tableId );
$table = $dbForProject -> getCollection ( 'database_' . $database -> getInternalId () . '_collection_' . $tableDocument -> getInternalId ());
2022-06-23 08:50:11 +00:00
2025-04-26 10:35:07 +00:00
if ( $table -> isEmpty ()) {
2022-07-26 14:24:32 +00:00
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
2022-06-23 08:50:11 +00:00
}
2022-06-22 10:51:49 +00:00
2023-10-25 07:39:59 +00:00
$periods = Config :: getParam ( 'usage' , []);
$stats = $usage = [];
$days = $periods [ $range ];
$metrics = [
2025-04-26 10:35:07 +00:00
str_replace ([ '{databaseInternalId}' , '{collectionInternalId}' ], [ $database -> getInternalId (), $tableDocument -> getInternalId ()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS ),
2023-10-25 07:39:59 +00:00
];
2022-06-23 08:50:11 +00:00
2023-11-08 09:09:32 +00:00
Authorization :: skip ( function () use ( $dbForProject , $days , $metrics , & $stats ) {
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$result = $dbForProject -> findOne ( 'stats' , [
2023-11-01 15:20:12 +00:00
Query :: equal ( 'metric' , [ $metric ]),
Query :: equal ( 'period' , [ 'inf' ])
]);
2022-06-22 10:51:49 +00:00
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'total' ] = $result [ 'value' ] ? ? 0 ;
2023-10-25 07:39:59 +00:00
$limit = $days [ 'limit' ];
$period = $days [ 'period' ];
2024-02-01 10:21:50 +00:00
$results = $dbForProject -> find ( 'stats' , [
2023-10-25 07:39:59 +00:00
Query :: equal ( 'metric' , [ $metric ]),
2023-10-25 12:06:54 +00:00
Query :: equal ( 'period' , [ $period ]),
2023-10-25 07:39:59 +00:00
Query :: limit ( $limit ),
Query :: orderDesc ( 'time' ),
]);
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ] = [];
2023-10-25 07:39:59 +00:00
foreach ( $results as $result ) {
2023-11-08 09:09:32 +00:00
$stats [ $metric ][ 'data' ][ $result -> getAttribute ( 'time' )] = [
2025-05-04 07:52:26 +00:00
'value' => $result -> getAttribute ( 'value' ),
2023-10-25 07:39:59 +00:00
];
2023-10-15 17:41:09 +00:00
}
2023-10-25 07:39:59 +00:00
}
});
2022-06-23 08:50:11 +00:00
2023-10-25 07:39:59 +00:00
$format = match ( $days [ 'period' ]) {
'1h' => 'Y-m-d\TH:00:00.000P' ,
'1d' => 'Y-m-d\T00:00:00.000P' ,
};
2022-06-22 10:51:49 +00:00
2024-03-06 17:34:21 +00:00
foreach ( $metrics as $metric ) {
2025-05-04 07:52:26 +00:00
$usage [ $metric ][ 'total' ] = $stats [ $metric ][ 'total' ];
2024-03-06 17:34:21 +00:00
$usage [ $metric ][ 'data' ] = [];
$leap = time () - ( $days [ 'limit' ] * $days [ 'factor' ]);
while ( $leap < time ()) {
$leap += $days [ 'factor' ];
$formatDate = date ( $format , $leap );
$usage [ $metric ][ 'data' ][] = [
'value' => $stats [ $metric ][ 'data' ][ $formatDate ][ 'value' ] ? ? 0 ,
'date' => $formatDate ,
];
}
2022-06-23 08:50:11 +00:00
}
2022-06-22 10:51:49 +00:00
2023-10-25 07:39:59 +00:00
$response -> dynamic ( new Document ([
'range' => $range ,
2025-05-04 07:52:26 +00:00
'documentsTotal' => $usage [ $metrics [ 0 ]][ 'total' ],
'documents' => $usage [ $metrics [ 0 ]][ 'data' ],
2023-10-25 07:39:59 +00:00
]), Response :: MODEL_USAGE_COLLECTION );
2022-06-23 08:50:11 +00:00
});