added crud operation

This commit is contained in:
ArnabChatterjee20k
2026-04-13 20:28:40 +05:30
parent 14351c7338
commit 51fd96f4bf
9 changed files with 597 additions and 0 deletions
+2
View File
@@ -9,6 +9,7 @@ use Appwrite\Platform\Modules\Core;
use Appwrite\Platform\Modules\Databases;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Health;
use Appwrite\Platform\Modules\Presences;
use Appwrite\Platform\Modules\Project;
use Appwrite\Platform\Modules\Projects;
use Appwrite\Platform\Modules\Proxy;
@@ -29,6 +30,7 @@ class Appwrite extends Platform
$this->addModule(new Avatars\Module());
$this->addModule(new Databases\Module());
$this->addModule(new Projects\Module());
$this->addModule(new Presences\Module());
$this->addModule(new Functions\Module());
$this->addModule(new Health\Module());
$this->addModule(new Sites\Module());
@@ -0,0 +1,64 @@
<?php
namespace Appwrite\Platform\Modules\Presences;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action as PlatformAction;
use Appwrite\Utopia\Database\Documents\User;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
class Action extends PlatformAction
{
public function setPermission(Document $document, ?array $permissions, Document $user, Authorization $authorization): Document
{
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
Database::PERMISSION_WRITE,
];
$permissions = Permission::aggregate($permissions, $allowedPermissions);
// Add permissions for current the user if none were provided.
if (\is_null($permissions)) {
$permissions = [];
if (!empty($user->getId()) && !$isPrivilegedUser) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
}
}
}
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!$authorization->hasRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $authorization->getRoles()) . ')');
}
}
}
}
$document->setAttribute('$permissions', $permissions ?? []);
return $document;
}
}
@@ -0,0 +1,66 @@
<?php
namespace Appwrite\Platform\Modules\Presences\HTTP;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Delete extends Base
{
use HTTP;
public static function getName()
{
return 'deletePresence';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
->setHttpPath('/v1/presences/:presenceId')
->desc('Delete presence')
->groups(['api', 'presences'])
->label('scope', 'users.write')
->label('sdk', new Method(
namespace: 'presences',
group: 'presences',
name: 'delete',
description: 'Delete a presence log by its unique ID.',
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
),
],
contentType: ContentType::NONE,
))
->param('presenceId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Presence unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
}
public function action(string $presenceId, Response $response, Database $dbForProject): void
{
$presence = $dbForProject->getDocument('presenceLogs', $presenceId);
if ($presence->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$dbForProject->deleteDocument('presenceLogs', $presenceId);
$response->noContent();
}
}
@@ -0,0 +1,62 @@
<?php
namespace Appwrite\Platform\Modules\Presences\HTTP;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class Get extends Base
{
use HTTP;
public static function getName()
{
return 'getPresence';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/presences/:presenceId')
->desc('Get presence')
->groups(['api', 'presences'])
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'presences',
group: 'presences',
name: 'get',
description: 'Get a presence log by its unique ID.',
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PRESENCE,
),
],
))
->param('presenceId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Presence unique ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
}
public function action(string $presenceId, Response $response, Database $dbForProject): void
{
$presence = $dbForProject->getDocument('presenceLogs', $presenceId);
if ($presence->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$response->dynamic($presence, Response::MODEL_PRESENCE);
}
}
@@ -0,0 +1,126 @@
<?php
namespace Appwrite\Platform\Modules\Presences\HTTP;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Presences\Action as PresenceAction;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Update extends PresenceAction
{
public static function getName()
{
return 'updatePresence';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/presences/:presenceId')
->desc('Update presence')
->groups(['api', 'presences'])
->label('scope', 'users.write')
->label('sdk', new Method(
namespace: 'presences',
group: 'presences',
name: 'update',
description: 'Update a presence log by its unique ID.',
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PRESENCE,
),
],
))
->param('presenceId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Presence unique ID.', false, ['dbForProject'])
->param('userId', null, new Nullable(new UID()), 'User ID.', true)
->param('status', null, new Nullable(new Text(Database::LENGTH_KEY)), 'Presence status.', true)
->param('expiry', null, new Nullable(new DatetimeValidator()), 'Presence expiry datetime.', true)
->param('metadata', null, new Nullable(new JSON()), 'Presence metadata object.', true)
->param('permissions', null, new Nullable(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)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('authorization')
->callback($this->action(...));
}
public function action(
string $presenceId,
?string $userId,
?string $status,
?string $expiry,
?array $metadata,
?array $permissions,
Response $response,
Database $dbForProject,
Document $user,
Authorization $authorization
): void {
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($userId && !$isAPIKey && !$isPrivilegedUser) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'userId is not allowed for non-API key and non-privileged users');
}
if (($isAPIKey || $isPrivilegedUser) && !$userId) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'userId is required for API key and privileged users');
}
$presence = $dbForProject->getDocument('presenceLogs', $presenceId);
if ($presence->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND, params: [$presenceId]);
}
$updateData = [];
if ($userId !== null) {
$updateData['userId'] = $userId;
}
if ($status !== null) {
$updateData['status'] = $status;
}
if ($expiry !== null) {
$updateData['expiry'] = $expiry;
}
if ($metadata !== null) {
$updateData['metadata'] = $metadata;
}
$updates = new Document($updateData);
if ($permissions !== null) {
$this->setPermission($updates, $permissions, $user, $authorization);
}
if (empty($updateData) && $permissions === null) {
$response->dynamic($presence, Response::MODEL_PRESENCE);
return;
}
$presence = $dbForProject->updateDocument('presenceLogs', $presenceId, $updates);
$response->dynamic($presence, Response::MODEL_PRESENCE);
}
}
@@ -0,0 +1,135 @@
<?php
namespace Appwrite\Platform\Modules\Presences\HTTP;
use Appwrite\Extend\Exception;
use Appwrite\ID;
use Appwrite\Platform\Modules\Presences\Action as PresenceAction;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\JSON;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
class Upsert extends PresenceAction
{
use HTTP;
public static function getName()
{
return 'upsertPresence';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/presences/:presenceId')
->desc('Upsert presence')
->groups(['api', 'presences'])
->label('scope', 'users.write')
->label('sdk', new Method(
namespace: 'presences',
group: 'presences',
name: 'upsert',
description: 'Create or update a presence log by its unique ID.',
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PRESENCE,
),
],
))
->param('presenceId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Presence unique ID.', false, ['dbForProject'])
->param('userId', '', new UID(), 'User ID.', false)
->param('status', '', new Text(Database::LENGTH_KEY), 'Presence status.', false)
->param('permissions', null, new Nullable(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)
// TODO: what shall be the min and max date here
->param('expiry', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Presence expiry datetime.', true)
->param('metadata', [], new JSON(), 'Presence metadata object.', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('authorization')
->callback($this->action(...));
}
public function action(
string $presenceId,
?string $userId,
?string $status,
?array $permissions,
?string $expiry,
array $metadata,
Response $response,
Database $dbForProject,
Document $user,
Authorization $authorization
): void {
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($userId && !$isAPIKey && !$isPrivilegedUser) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, "userId is not allowed for non-API key and non-privileged users");
}
if (($isAPIKey || $isPrivilegedUser) && !$userId) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, "userId is required for API key and privileged users");
}
$userInternalId = null;
$resolvedUserId = $userId;
if (!$isAPIKey && !$isPrivilegedUser) {
$userInternalId = $user->getId();
$resolvedUserId = $user->getId();
} else {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND, params: [$userId]);
}
$userInternalId = $user->getId();
$resolvedUserId = $user->getId();
}
$presenceData = [
'userInternalId' => $userInternalId,
'userId' => $resolvedUserId,
'status' => $status,
'source' => 'HTTP',
'expiry' => $expiry ?? DateTime::addSeconds(new \DateTime(), 15 * 60),
// TODO: finding a way to find hostname
// 'hostname' => $hostname,
'metadata' => $metadata,
];
$presenceDocument = new Document($presenceData);
$this->setPermission($presenceDocument, $permissions, $user, $authorization);
$existingPresence = $dbForProject->findOne('presenceLogs', [
Query::equal('userId', [$resolvedUserId]),
]);
if ($existingPresence->isEmpty()) {
$presenceId = $presenceId === 'unique()' ? ID::unique() : $presenceId;
$presenceDocument->setAttribute('$id', $presenceId);
$presence = $dbForProject->createDocument('presenceLogs', $presenceDocument);
} else {
$presence = $dbForProject->updateDocument('presenceLogs', $existingPresence->getId(), $presenceDocument);
}
$response->dynamic($presence, Response::MODEL_PRESENCE);
}
}
@@ -0,0 +1,103 @@
<?php
namespace Appwrite\Platform\Modules\Presences\HTTP;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Base as QueryValidator;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class XList extends Base
{
use HTTP;
public static function getName()
{
return 'listPresences';
}
public function __construct()
{
$allowedAttributes = ['userInternalId', 'userId', 'expiry', 'status', 'source'];
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/presences')
->desc('List presences')
->groups(['api', 'presences'])
->label('scope', 'users.read')
->label('sdk', new Method(
namespace: 'presences',
group: 'presences',
name: 'list',
description: 'List presence logs.',
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_PRESENCE_LIST,
),
],
))
->param('queries', [], new QueryValidator('presenceLogs', $allowedAttributes), 'Array of query strings generated using the Query class provided by the SDK.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
}
public function action(array $queries, bool $includeTotal, Response $response, Database $dbForProject): void
{
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$cursor = Query::getCursorQueries($queries, false);
$cursor = \reset($cursor);
if ($cursor !== false) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$presenceId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('presenceLogs', $presenceId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Presence '{$presenceId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
try {
$documents = $dbForProject->find('presenceLogs', $queries);
$total = $includeTotal ? $dbForProject->count('presenceLogs', $filterQueries, APP_LIMIT_COUNT) : 0;
} catch (OrderException $e) {
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.");
}
$response->dynamic(new Document([
'presences' => $documents,
'total' => $total,
]), Response::MODEL_PRESENCE_LIST);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Presences;
use Appwrite\Platform\Modules\Presences\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,25 @@
<?php
namespace Appwrite\Platform\Modules\Presences\Services;
use Appwrite\Platform\Modules\Presences\HTTP\Delete as DeletePresence;
use Appwrite\Platform\Modules\Presences\HTTP\Get as GetPresence;
use Appwrite\Platform\Modules\Presences\HTTP\Update as UpdatePresence;
use Appwrite\Platform\Modules\Presences\HTTP\Upsert as UpsertPresence;
use Appwrite\Platform\Modules\Presences\HTTP\XList as ListPresences;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this
->addAction(UpsertPresence::getName(), new UpsertPresence())
->addAction(GetPresence::getName(), new GetPresence())
->addAction(ListPresences::getName(), new ListPresences())
->addAction(UpdatePresence::getName(), new UpdatePresence())
->addAction(DeletePresence::getName(), new DeletePresence());
}
}