2019-05-09 18:54:39 +12:00
< ? php
2021-05-07 10:31:05 +12:00
use Appwrite\Auth\Auth ;
2023-01-09 18:46:02 +13:00
use Appwrite\Auth\Validator\Phone ;
2021-05-07 10:31:05 +12:00
use Appwrite\Detector\Detector ;
2022-05-09 03:25:01 +12:00
use Appwrite\Event\Delete ;
2022-05-03 23:57:26 +12:00
use Appwrite\Event\Event ;
2022-05-09 03:25:01 +12:00
use Appwrite\Event\Mail ;
2023-09-06 05:10:48 +12:00
use Appwrite\Event\Messaging ;
2022-05-03 23:57:26 +12:00
use Appwrite\Extend\Exception ;
2021-05-07 10:31:05 +12:00
use Appwrite\Network\Validator\Email ;
2024-02-13 05:02:04 +13:00
use Utopia\Database\Exception\Query as QueryException ;
2023-01-14 04:28:04 +13:00
use Utopia\Validator\Host ;
2022-01-19 00:05:04 +13:00
use Appwrite\Template\Template ;
use Appwrite\Utopia\Database\Validator\CustomId ;
2023-04-25 23:35:49 +12:00
use Utopia\Database\Validator\Queries ;
2022-08-25 06:25:15 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Memberships ;
2022-08-23 20:56:28 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Teams ;
2023-04-25 23:35:49 +12:00
use Utopia\Database\Validator\Query\Limit ;
use Utopia\Database\Validator\Query\Offset ;
2022-05-03 23:57:26 +12:00
use Appwrite\Utopia\Request ;
2022-01-19 00:05:04 +13:00
use Appwrite\Utopia\Response ;
2022-05-03 23:57:26 +12:00
use MaxMind\Db\Reader ;
2020-06-29 05:31:21 +12:00
use Utopia\App ;
2022-04-22 02:07:08 +12:00
use Utopia\Audit\Audit ;
2020-03-29 01:42:16 +13:00
use Utopia\Config\Config ;
2021-10-05 23:30:33 +13:00
use Utopia\Database\Database ;
2021-05-07 10:31:05 +12:00
use Utopia\Database\Document ;
2021-12-07 21:01:09 +13:00
use Utopia\Database\Exception\Authorization as AuthorizationException ;
2021-05-07 10:31:05 +12:00
use Utopia\Database\Exception\Duplicate ;
2022-12-15 04:42:25 +13:00
use Utopia\Database\Helpers\ID ;
2022-12-15 05:04:06 +13:00
use Utopia\Database\Helpers\Permission ;
2021-05-07 10:31:05 +12:00
use Utopia\Database\Query ;
2022-07-13 01:32:39 +12:00
use Utopia\Database\DateTime ;
2022-12-15 05:04:06 +13:00
use Utopia\Database\Helpers\Role ;
2021-05-07 10:31:05 +12:00
use Utopia\Database\Validator\Authorization ;
use Utopia\Database\Validator\Key ;
2021-07-26 02:47:18 +12:00
use Utopia\Database\Validator\UID ;
2022-05-04 21:51:48 +12:00
use Utopia\Locale\Locale ;
2022-01-19 00:05:04 +13:00
use Utopia\Validator\ArrayList ;
2023-03-07 03:24:02 +13:00
use Utopia\Validator\Assoc ;
use Utopia\Validator\Text ;
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App :: post ( '/v1/teams' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Create team' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].create' )
2019-05-09 18:54:39 +12:00
-> label ( 'scope' , 'teams.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'team.create' )
2022-08-09 02:32:54 +12:00
-> label ( 'audits.resource' , 'team/{response.$id}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2020-01-31 05:18:46 +13:00
-> label ( 'sdk.method' , 'create' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/create-team.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_TEAM )
2023-01-21 11:22:16 +13:00
-> param ( 'teamId' , '' , new CustomId (), 'Team 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.' )
2020-09-11 02:40:14 +12:00
-> param ( 'name' , null , new Text ( 128 ), 'Team name. Max length: 128 chars.' )
2023-10-14 02:43:23 +13:00
-> param ( 'roles' , [ 'owner' ], new ArrayList ( new Key (), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.' , true )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
-> inject ( 'user' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $teamId , string $name , array $roles , Response $response , Document $user , Database $dbForProject , Event $queueForEvents ) {
2020-06-30 23:09:28 +12:00
2021-12-11 06:52:33 +13:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
$isAppUser = Auth :: isAppUser ( Authorization :: getRoles ());
2020-11-20 19:48:15 +13:00
2022-08-15 02:22:38 +12:00
$teamId = $teamId == 'unique()' ? ID :: unique () : $teamId ;
2023-07-13 07:03:38 +12:00
2023-07-09 23:20:09 +12:00
try {
$team = Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'teams' , new Document ([
'$id' => $teamId ,
'$permissions' => [
Permission :: read ( Role :: team ( $teamId )),
Permission :: update ( Role :: team ( $teamId , 'owner' )),
Permission :: delete ( Role :: team ( $teamId , 'owner' )),
],
'name' => $name ,
'total' => ( $isPrivilegedUser || $isAppUser ) ? 0 : 1 ,
'prefs' => new \stdClass (),
'search' => implode ( ' ' , [ $teamId , $name ]),
])));
} catch ( Duplicate $th ) {
2023-07-13 07:03:38 +12:00
throw new Exception ( Exception :: TEAM_ALREADY_EXISTS );
2023-07-09 23:20:09 +12:00
}
2020-06-30 23:09:28 +12:00
2021-03-02 10:04:53 +13:00
if ( ! $isPrivilegedUser && ! $isAppUser ) { // Don't add user on server mode
2022-08-28 11:01:46 +12:00
if ( ! \in_array ( 'owner' , $roles )) {
$roles [] = 'owner' ;
}
2022-08-15 02:22:38 +12:00
$membershipId = ID :: unique ();
2020-06-30 23:09:28 +12:00
$membership = new Document ([
2022-08-15 02:22:38 +12:00
'$id' => $membershipId ,
2022-08-02 21:21:53 +12:00
'$permissions' => [
2022-08-15 23:24:31 +12:00
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: read ( Role :: team ( $team -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: team ( $team -> getId (), 'owner' )),
Permission :: delete ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: team ( $team -> getId (), 'owner' )),
2022-08-02 21:21:53 +12:00
],
2022-08-15 23:24:31 +12:00
'userId' => $user -> getId (),
2022-06-15 05:15:22 +12:00
'userInternalId' => $user -> getInternalId (),
2022-08-15 23:24:31 +12:00
'teamId' => $team -> getId (),
'teamInternalId' => $team -> getInternalId (),
2020-06-30 23:09:28 +12:00
'roles' => $roles ,
2022-07-14 02:02:49 +12:00
'invited' => DateTime :: now (),
'joined' => DateTime :: now (),
2020-06-30 23:09:28 +12:00
'confirm' => true ,
'secret' => '' ,
2022-02-17 05:26:19 +13:00
'search' => implode ( ' ' , [ $membershipId , $user -> getId ()])
2019-05-09 18:54:39 +12:00
]);
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> createDocument ( 'memberships' , $membership );
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2019-05-09 18:54:39 +12:00
}
2020-06-30 23:09:28 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents -> setParam ( 'teamId' , $team -> getId ());
2022-04-14 00:39:31 +12:00
2021-06-17 05:43:06 +12:00
if ( ! empty ( $user -> getId ())) {
2022-12-21 05:11:30 +13:00
$queueForEvents -> setParam ( 'userId' , $user -> getId ());
2021-06-17 05:43:06 +12:00
}
2022-09-07 23:11:10 +12:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic ( $team , Response :: MODEL_TEAM );
2020-12-27 05:48:43 +13:00
});
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App :: get ( '/v1/teams' )
2023-08-02 03:26:48 +12:00
-> desc ( 'List teams' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2020-02-01 11:34:07 +13:00
-> label ( 'scope' , 'teams.read' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2020-02-01 11:34:07 +13:00
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'list' )
-> label ( 'sdk.description' , '/docs/references/teams/list-teams.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_TEAM_LIST )
2023-02-10 14:06:33 +13:00
-> label ( 'sdk.offline.model' , '/teams' )
2023-03-30 08:38:39 +13:00
-> param ( 'queries' , [], new Teams (), '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 ( ', ' , Teams :: ALLOWED_ATTRIBUTES ), true )
2020-09-11 02:40:14 +12:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-08-23 20:56:28 +12:00
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
2021-05-07 10:31:05 +12:00
2024-02-13 05:02:04 +13:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2021-08-07 00:36:35 +12:00
2022-08-12 11:53:52 +12:00
if ( ! empty ( $search )) {
2022-08-23 20:56:28 +12:00
$queries [] = Query :: search ( 'search' , $search );
2021-08-07 00:36:35 +12:00
}
2024-02-12 22:55:45 +13:00
/**
2024-02-12 23:03:31 +13:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 22:55:45 +13:00
*/
2023-08-22 15:25:55 +12:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-07 03:10:40 +13:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 15:25:55 +12:00
});
2022-08-31 11:31:43 +12:00
$cursor = reset ( $cursor );
2022-08-30 23:55:23 +12:00
if ( $cursor ) {
2022-08-23 20:56:28 +12:00
/** @var Query $cursor */
$teamId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'teams' , $teamId );
2021-08-19 01:42:03 +12:00
2022-08-12 11:53:52 +12:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-23 20:56:28 +12:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Team ' { $teamId } ' for the 'cursor' value not found. " );
2022-08-12 11:53:52 +12:00
}
2022-08-23 20:56:28 +12:00
$cursor -> setValue ( $cursorDocument );
2021-08-19 01:42:03 +12:00
}
2021-10-07 03:11:04 +13:00
2022-08-23 20:56:28 +12:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
$results = $dbForProject -> find ( 'teams' , $queries );
2022-08-12 11:53:52 +12:00
$total = $dbForProject -> count ( 'teams' , $filterQueries , APP_LIMIT_COUNT );
2020-06-30 23:09:28 +12:00
2021-07-26 02:47:18 +12:00
$response -> dynamic ( new Document ([
2021-05-27 22:09:14 +12:00
'teams' => $results ,
2022-02-27 22:57:09 +13:00
'total' => $total ,
2020-08-07 02:49:29 +12:00
]), Response :: MODEL_TEAM_LIST );
2020-12-27 05:48:43 +13:00
});
2020-02-01 11:34:07 +13:00
2020-06-29 05:31:21 +12:00
App :: get ( '/v1/teams/:teamId' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Get team' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2020-02-01 11:34:07 +13:00
-> label ( 'scope' , 'teams.read' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2020-02-01 11:34:07 +13:00
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'get' )
-> label ( 'sdk.description' , '/docs/references/teams/get-team.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_TEAM )
2023-02-10 14:06:33 +13:00
-> label ( 'sdk.offline.model' , '/teams' )
-> label ( 'sdk.offline.key' , '{teamId}' )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-05-04 01:21:25 +12:00
-> action ( function ( string $teamId , Response $response , Database $dbForProject ) {
2020-02-01 11:34:07 +13:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2020-02-01 11:34:07 +13:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-02-01 11:34:07 +13:00
}
2020-06-30 23:09:28 +12:00
2021-07-26 02:47:18 +12:00
$response -> dynamic ( $team , Response :: MODEL_TEAM );
2020-12-27 05:48:43 +13:00
});
2020-02-01 11:34:07 +13:00
2023-03-07 03:24:02 +13:00
App :: get ( '/v1/teams/:teamId/prefs' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Get team preferences' )
2023-03-07 03:24:02 +13:00
-> groups ([ 'api' , 'teams' ])
-> label ( 'scope' , 'teams.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'getPrefs' )
-> label ( 'sdk.description' , '/docs/references/teams/get-team-prefs.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PREFERENCES )
-> label ( 'sdk.offline.model' , '/teams/{teamId}/prefs' )
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $teamId , Response $response , Database $dbForProject ) {
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
if ( $team -> isEmpty ()) {
throw new Exception ( Exception :: TEAM_NOT_FOUND );
}
2023-04-13 04:03:08 +12:00
$prefs = $team -> getAttribute ( 'prefs' , []);
2023-03-07 03:24:02 +13:00
$response -> dynamic ( new Document ( $prefs ), Response :: MODEL_PREFERENCES );
});
2020-06-29 05:31:21 +12:00
App :: put ( '/v1/teams/:teamId' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Update name' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].update' )
2019-05-09 18:54:39 +12:00
-> label ( 'scope' , 'teams.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'team.update' )
2022-08-09 02:32:54 +12:00
-> label ( 'audits.resource' , 'team/{response.$id}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2023-03-24 01:04:16 +13:00
-> label ( 'sdk.method' , 'updateName' )
2023-03-07 03:24:02 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/update-team-name.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_TEAM )
2023-02-10 14:06:33 +13:00
-> label ( 'sdk.offline.model' , '/teams' )
-> label ( 'sdk.offline.key' , '{teamId}' )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2021-12-11 04:44:54 +13:00
-> param ( 'name' , null , new Text ( 128 ), 'New team name. Max length: 128 chars.' )
2023-01-20 13:36:17 +13:00
-> inject ( 'requestTimestamp' )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
2023-10-16 06:41:09 +13:00
-> action ( function ( string $teamId , string $name , ? \DateTime $requestTimestamp , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2023-01-20 13:36:17 +13:00
$team
2021-08-15 06:56:28 +12:00
-> setAttribute ( 'name' , $name )
2023-01-20 13:36:17 +13:00
-> setAttribute ( 'search' , implode ( ' ' , [ $teamId , $name ]));
$team = $dbForProject -> withRequestTimestamp ( $requestTimestamp , function () use ( $dbForProject , $team ) {
return $dbForProject -> updateDocument ( 'teams' , $team -> getId (), $team );
});
2019-05-09 18:54:39 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents -> setParam ( 'teamId' , $team -> getId ());
2022-04-14 00:39:31 +12:00
2021-07-26 02:47:18 +12:00
$response -> dynamic ( $team , Response :: MODEL_TEAM );
2020-12-27 05:48:43 +13:00
});
2019-05-09 18:54:39 +12:00
2023-03-14 12:10:17 +13:00
App :: put ( '/v1/teams/:teamId/prefs' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Update preferences' )
2023-03-07 03:24:02 +13:00
-> groups ([ 'api' , 'teams' ])
-> label ( 'event' , 'teams.[teamId].update.prefs' )
-> label ( 'scope' , 'teams.write' )
-> label ( 'audits.event' , 'team.update' )
-> label ( 'audits.resource' , 'team/{response.$id}' )
-> label ( 'audits.userId' , '{response.$id}' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'updatePrefs' )
-> label ( 'sdk.description' , '/docs/references/teams/update-team-prefs.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_PREFERENCES )
2023-03-14 12:10:17 +13:00
-> label ( 'sdk.offline.model' , '/teams/{teamId}/prefs' )
2023-03-07 03:24:02 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
-> param ( 'prefs' , '' , new Assoc (), 'Prefs key-value JSON object.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-09-28 04:51:17 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $teamId , array $prefs , Response $response , Database $dbForProject , Event $queueForEvents ) {
2023-03-07 03:24:02 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
if ( $team -> isEmpty ()) {
throw new Exception ( Exception :: TEAM_NOT_FOUND );
}
$team = $dbForProject -> updateDocument ( 'teams' , $team -> getId (), $team -> setAttribute ( 'prefs' , $prefs ));
2023-09-28 04:51:17 +13:00
$queueForEvents -> setParam ( 'teamId' , $team -> getId ());
2023-03-07 03:24:02 +13:00
$response -> dynamic ( new Document ( $prefs ), Response :: MODEL_PREFERENCES );
});
2020-06-29 05:31:21 +12:00
App :: delete ( '/v1/teams/:teamId' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Delete team' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].delete' )
2019-05-09 18:54:39 +12:00
-> label ( 'scope' , 'teams.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'team.delete' )
2022-08-12 01:19:05 +12:00
-> label ( 'audits.resource' , 'team/{request.teamId}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2020-01-31 05:18:46 +13:00
-> label ( 'sdk.method' , 'delete' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/delete-team.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForDeletes' )
-> action ( function ( string $teamId , Response $response , Database $dbForProject , Event $queueForEvents , Delete $queueForDeletes ) {
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
if ( ! $dbForProject -> deleteDocument ( 'teams' , $teamId )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove team from DB' );
2019-05-09 18:54:39 +12:00
}
2020-06-30 23:09:28 +12:00
2022-12-21 05:11:30 +13:00
$queueForDeletes
2022-04-18 08:34:32 +12:00
-> setType ( DELETE_TYPE_DOCUMENT )
-> setDocument ( $team );
2021-05-12 05:55:38 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-04-14 00:39:31 +12:00
-> setParam ( 'teamId' , $team -> getId ())
-> setPayload ( $response -> output ( $team , Response :: MODEL_TEAM ))
2020-12-03 11:15:20 +13:00
;
2020-06-30 23:09:28 +12:00
$response -> noContent ();
2020-12-27 05:48:43 +13:00
});
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App :: post ( '/v1/teams/:teamId/memberships' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Create team membership' )
2021-03-01 07:36:13 +13:00
-> groups ([ 'api' , 'teams' , 'auth' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].memberships.[membershipId].create' )
2020-02-10 05:53:33 +13:00
-> label ( 'scope' , 'teams.write' )
2021-03-01 07:36:13 +13:00
-> label ( 'auth.type' , 'invites' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'membership.create' )
2022-08-12 01:19:05 +12:00
-> label ( 'audits.resource' , 'team/{request.teamId}' )
2022-08-12 23:01:12 +12:00
-> label ( 'audits.userId' , '{request.userId}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2020-01-31 05:18:46 +13:00
-> label ( 'sdk.method' , 'createMembership' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/create-team-membership.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_CREATED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MEMBERSHIP )
2021-04-13 21:38:40 +12:00
-> label ( 'abuse-limit' , 10 )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2023-01-09 18:46:02 +13:00
-> param ( 'email' , '' , new Email (), 'Email of the new team member.' , true )
2023-01-18 16:48:47 +13:00
-> param ( 'userId' , '' , new UID (), 'ID of the user to be added to a team.' , true )
2023-01-09 18:46:02 +13:00
-> param ( 'phone' , '' , new Phone (), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.' , true )
2023-10-14 02:43:23 +13:00
-> param ( 'roles' , [], new ArrayList ( new Key (), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.' )
2023-09-06 06:13:47 +12:00
-> param ( 'url' , '' , fn ( $clients ) => new Host ( $clients ), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.' , true , [ 'clients' ]) // TODO add our own built-in confirm page
2021-12-11 04:44:54 +13:00
-> param ( 'name' , '' , new Text ( 128 ), 'Name of the new team member. Max length: 128 chars.' , true )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'user' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2020-12-27 05:48:43 +13:00
-> inject ( 'locale' )
2023-06-12 02:08:48 +12:00
-> inject ( 'queueForMails' )
2023-09-28 04:51:17 +13:00
-> inject ( 'queueForMessaging' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
2023-10-26 06:33:23 +13:00
-> action ( function ( string $teamId , string $email , string $userId , string $phone , array $roles , string $url , string $name , Response $response , Document $project , Document $user , Database $dbForProject , Locale $locale , Mail $queueForMails , Messaging $queueForMessaging , Event $queueForEvents ) {
2023-09-06 06:13:47 +12:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
if ( empty ( $url )) {
if ( ! $isAPIKey && ! $isPrivilegedUser ) {
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'URL is required' );
}
}
2020-11-20 19:48:15 +13:00
2023-01-09 18:55:07 +13:00
if ( empty ( $userId ) && empty ( $email ) && empty ( $phone )) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'At least one of userId, email, or phone is required' );
}
2021-12-11 06:52:33 +13:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
$isAppUser = Auth :: isAppUser ( Authorization :: getRoles ());
2021-10-08 22:06:53 +13:00
2022-05-27 17:45:38 +12:00
if ( ! $isPrivilegedUser && ! $isAppUser && empty ( App :: getEnv ( '_APP_SMTP_HOST' ))) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SMTP_DISABLED );
2022-05-27 17:45:38 +12:00
}
2021-06-04 07:05:11 +12:00
$email = \strtolower ( $email );
2020-06-30 23:09:28 +12:00
$name = ( empty ( $name )) ? $email : $name ;
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2020-06-30 23:09:28 +12:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2023-01-09 18:55:07 +13:00
if ( ! empty ( $userId )) {
2023-01-09 18:46:02 +13:00
$invitee = $dbForProject -> getDocument ( 'users' , $userId );
2023-01-09 18:55:07 +13:00
if ( $invitee -> isEmpty ()) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: USER_NOT_FOUND , 'User with given userId doesn\'t exist.' , 404 );
}
2023-04-12 03:32:14 +12:00
if ( ! empty ( $email ) && $invitee -> getAttribute ( 'email' , '' ) !== $email ) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS , 'Given userId and email doesn\'t match' , 409 );
}
2023-04-12 03:32:14 +12:00
if ( ! empty ( $phone ) && $invitee -> getAttribute ( 'phone' , '' ) !== $phone ) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS , 'Given userId and phone doesn\'t match' , 409 );
}
2023-01-09 22:17:39 +13:00
$email = $invitee -> getAttribute ( 'email' , '' );
$phone = $invitee -> getAttribute ( 'phone' , '' );
$name = empty ( $name ) ? $invitee -> getAttribute ( 'name' , '' ) : $name ;
2023-01-09 18:55:07 +13:00
} elseif ( ! empty ( $email )) {
2023-01-09 18:46:02 +13:00
$invitee = $dbForProject -> findOne ( 'users' , [ Query :: equal ( 'email' , [ $email ])]); // Get user by email address
2023-04-12 03:32:14 +12:00
if ( ! empty ( $invitee ) && ! empty ( $phone ) && $invitee -> getAttribute ( 'phone' , '' ) !== $phone ) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS , 'Given email and phone doesn\'t match' , 409 );
}
2023-01-09 18:55:07 +13:00
} elseif ( ! empty ( $phone )) {
2023-01-09 18:46:02 +13:00
$invitee = $dbForProject -> findOne ( 'users' , [ Query :: equal ( 'phone' , [ $phone ])]);
2023-04-12 03:32:14 +12:00
if ( ! empty ( $invitee ) && ! empty ( $email ) && $invitee -> getAttribute ( 'email' , '' ) !== $email ) {
2023-01-09 18:46:02 +13:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS , 'Given phone and email doesn\'t match' , 409 );
}
}
2020-06-30 23:09:28 +12:00
if ( empty ( $invitee )) { // Create new user if no user with same email found
2021-08-06 20:34:17 +12:00
$limit = $project -> getAttribute ( 'auths' , [])[ 'limit' ] ? ? 0 ;
2021-10-08 22:06:53 +13:00
2021-03-01 07:36:13 +13:00
if ( $limit !== 0 && $project -> getId () !== 'console' ) { // check users limit, console invites are allways allowed.
2022-02-27 22:57:09 +13:00
$total = $dbForProject -> count ( 'users' , [], APP_LIMIT_USERS );
2021-10-08 22:06:53 +13:00
2022-05-24 02:54:50 +12:00
if ( $total >= $limit ) {
2022-08-14 18:56:12 +12:00
throw new Exception ( Exception :: USER_COUNT_EXCEEDED , 'Project registration is restricted. Contact your administrator for more information.' );
2021-03-01 07:36:13 +13:00
}
}
2023-05-18 13:11:45 +12:00
// Makes sure this email is not already used in another identity
$identityWithMatchingEmail = $dbForProject -> findOne ( 'identities' , [
Query :: equal ( 'providerEmail' , [ $email ]),
]);
if ( $identityWithMatchingEmail !== false && ! $identityWithMatchingEmail -> isEmpty ()) {
throw new Exception ( Exception :: USER_EMAIL_ALREADY_EXISTS );
}
2020-06-30 23:09:28 +12:00
try {
2022-08-15 02:22:38 +12:00
$userId = ID :: unique ();
2021-12-28 01:45:23 +13:00
$invitee = Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'users' , new Document ([
2022-08-15 02:22:38 +12:00
'$id' => $userId ,
2022-08-02 21:21:53 +12:00
'$permissions' => [
2022-08-14 17:21:11 +12:00
Permission :: read ( Role :: any ()),
2022-08-15 02:22:38 +12:00
Permission :: read ( Role :: user ( $userId )),
Permission :: update ( Role :: user ( $userId )),
Permission :: delete ( Role :: user ( $userId )),
2022-08-02 21:21:53 +12:00
],
2023-01-10 00:10:02 +13:00
'email' => empty ( $email ) ? null : $email ,
'phone' => empty ( $phone ) ? null : $phone ,
2020-06-30 23:09:28 +12:00
'emailVerification' => false ,
2021-07-14 23:02:12 +12:00
'status' => true ,
2024-01-18 00:03:04 +13:00
// TODO: Set password empty?
2022-05-05 23:21:31 +12:00
'password' => Auth :: passwordHash ( Auth :: passwordGenerator (), Auth :: DEFAULT_ALGO , Auth :: DEFAULT_ALGO_OPTIONS ),
'hash' => Auth :: DEFAULT_ALGO ,
'hashOptions' => Auth :: DEFAULT_ALGO_OPTIONS ,
2022-05-24 02:54:50 +12:00
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
2021-05-05 06:52:46 +12:00
*/
2022-07-04 21:55:11 +12:00
'passwordUpdate' => null ,
2022-07-14 02:02:49 +12:00
'registration' => DateTime :: now (),
2020-06-30 23:09:28 +12:00
'reset' => false ,
'name' => $name ,
2021-12-28 23:48:50 +13:00
'prefs' => new \stdClass (),
2022-04-26 20:52:59 +12:00
'sessions' => null ,
2022-04-27 23:06:53 +12:00
'tokens' => null ,
2022-04-28 00:44:47 +12:00
'memberships' => null ,
2023-08-23 13:36:04 +12:00
'search' => implode ( ' ' , [ $userId , $email , $name ]),
2021-12-18 04:25:05 +13:00
])));
2020-06-30 23:09:28 +12:00
} catch ( Duplicate $th ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: USER_ALREADY_EXISTS );
2019-05-09 18:54:39 +12:00
}
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-05-24 02:54:50 +12:00
$isOwner = Authorization :: isRole ( 'team:' . $team -> getId () . '/owner' );
2019-05-09 18:54:39 +12:00
2021-03-02 10:04:53 +13:00
if ( ! $isOwner && ! $isPrivilegedUser && ! $isAppUser ) { // Not owner, not admin, not app (server)
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'User is not allowed to send invitations for this team' );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
$secret = Auth :: tokenGenerator ();
2022-08-15 02:22:38 +12:00
$membershipId = ID :: unique ();
2020-06-30 23:09:28 +12:00
$membership = new Document ([
2022-08-15 02:22:38 +12:00
'$id' => $membershipId ,
2022-08-02 21:21:53 +12:00
'$permissions' => [
2022-08-14 17:21:11 +12:00
Permission :: read ( Role :: any ()),
2022-08-15 23:24:31 +12:00
Permission :: update ( Role :: user ( $invitee -> getId ())),
Permission :: update ( Role :: team ( $team -> getId (), 'owner' )),
Permission :: delete ( Role :: user ( $invitee -> getId ())),
Permission :: delete ( Role :: team ( $team -> getId (), 'owner' )),
2022-08-02 21:21:53 +12:00
],
2022-08-15 23:24:31 +12:00
'userId' => $invitee -> getId (),
'userInternalId' => $invitee -> getInternalId (),
'teamId' => $team -> getId (),
'teamInternalId' => $team -> getInternalId (),
2020-06-30 23:09:28 +12:00
'roles' => $roles ,
2022-07-14 02:02:49 +12:00
'invited' => DateTime :: now (),
'joined' => ( $isPrivilegedUser || $isAppUser ) ? DateTime :: now () : null ,
2021-03-02 10:04:53 +13:00
'confirm' => ( $isPrivilegedUser || $isAppUser ),
2020-06-30 23:09:28 +12:00
'secret' => Auth :: hash ( $secret ),
2022-02-17 05:26:19 +13:00
'search' => implode ( ' ' , [ $membershipId , $invitee -> getId ()])
2020-06-30 23:09:28 +12:00
]);
2021-03-02 10:04:53 +13:00
if ( $isPrivilegedUser || $isAppUser ) { // Allow admin to create membership
2021-05-10 06:37:47 +12:00
try {
2021-12-28 01:45:23 +13:00
$membership = Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'memberships' , $membership ));
2021-05-10 06:37:47 +12:00
} catch ( Duplicate $th ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVITE_ALREADY_EXISTS );
2021-05-10 06:37:47 +12:00
}
2024-02-12 14:18:19 +13:00
Authorization :: skip ( fn () => $dbForProject -> increaseDocumentAttribute ( 'teams' , $team -> getId (), 'total' , 1 ));
2019-05-09 18:54:39 +12:00
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $invitee -> getId ());
2020-06-30 23:09:28 +12:00
} else {
2021-05-10 06:37:47 +12:00
try {
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> createDocument ( 'memberships' , $membership );
2021-05-10 06:37:47 +12:00
} catch ( Duplicate $th ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVITE_ALREADY_EXISTS );
2021-05-10 06:37:47 +12:00
}
2019-05-09 18:54:39 +12:00
2023-01-09 21:29:33 +13:00
$url = Template :: parseURL ( $url );
$url [ 'query' ] = Template :: mergeQuery ((( isset ( $url [ 'query' ])) ? $url [ 'query' ] : '' ), [ 'membershipId' => $membership -> getId (), 'userId' => $invitee -> getId (), 'secret' => $secret , 'teamId' => $teamId ]);
$url = Template :: unParseURL ( $url );
2023-01-13 19:05:16 +13:00
if ( ! empty ( $email )) {
$projectName = $project -> isEmpty () ? 'Console' : $project -> getAttribute ( 'name' , '[APP-NAME]' );
2023-08-28 17:53:02 +12:00
$body = $locale -> getText ( " emails.invitation.body " );
2023-01-13 19:05:16 +13:00
$subject = \sprintf ( $locale -> getText ( " emails.invitation.subject " ), $team -> getAttribute ( 'name' ), $projectName );
2023-04-19 20:29:29 +12:00
$customTemplate = $project -> getAttribute ( 'templates' , [])[ 'email.invitation-' . $locale -> default ] ? ? [];
2023-08-28 17:53:26 +12:00
2023-08-29 21:40:30 +12:00
$message = Template :: fromFile ( __DIR__ . '/../../config/locale/templates/email-inner-base.tpl' );
2023-10-05 12:14:27 +13:00
$message
2024-01-09 06:08:17 +13:00
-> setParam ( '{{body}}' , $body , escapeHtml : false )
2023-10-05 12:14:27 +13:00
-> setParam ( '{{hello}}' , $locale -> getText ( " emails.invitation.hello " ))
-> setParam ( '{{footer}}' , $locale -> getText ( " emails.invitation.footer " ))
-> setParam ( '{{thanks}}' , $locale -> getText ( " emails.invitation.thanks " ))
-> setParam ( '{{signature}}' , $locale -> getText ( " emails.invitation.signature " ));
2023-08-29 21:40:30 +12:00
$body = $message -> render ();
$smtp = $project -> getAttribute ( 'smtp' , []);
$smtpEnabled = $smtp [ 'enabled' ] ? ? false ;
2023-08-29 00:21:35 +12:00
2023-08-29 21:40:30 +12:00
$senderEmail = App :: getEnv ( '_APP_SYSTEM_EMAIL_ADDRESS' , APP_EMAIL_TEAM );
$senderName = App :: getEnv ( '_APP_SYSTEM_EMAIL_NAME' , APP_NAME . ' Server' );
$replyTo = " " ;
2023-08-29 00:21:35 +12:00
2023-08-29 21:40:30 +12:00
if ( $smtpEnabled ) {
if ( ! empty ( $smtp [ 'senderEmail' ])) {
$senderEmail = $smtp [ 'senderEmail' ];
}
if ( ! empty ( $smtp [ 'senderName' ])) {
$senderName = $smtp [ 'senderName' ];
}
if ( ! empty ( $smtp [ 'replyTo' ])) {
$replyTo = $smtp [ 'replyTo' ];
}
2023-08-29 00:19:37 +12:00
2023-10-02 06:39:26 +13:00
$queueForMails
2023-08-26 03:13:25 +12:00
-> setSmtpHost ( $smtp [ 'host' ] ? ? '' )
-> setSmtpPort ( $smtp [ 'port' ] ? ? '' )
-> setSmtpUsername ( $smtp [ 'username' ] ? ? '' )
-> setSmtpPassword ( $smtp [ 'password' ] ? ? '' )
2023-08-29 21:40:30 +12:00
-> setSmtpSecure ( $smtp [ 'secure' ] ? ? '' );
2023-08-29 00:19:37 +12:00
2023-08-30 17:31:02 +12:00
if ( ! empty ( $customTemplate )) {
if ( ! empty ( $customTemplate [ 'senderEmail' ])) {
$senderEmail = $customTemplate [ 'senderEmail' ];
}
if ( ! empty ( $customTemplate [ 'senderName' ])) {
$senderName = $customTemplate [ 'senderName' ];
}
if ( ! empty ( $customTemplate [ 'replyTo' ])) {
$replyTo = $customTemplate [ 'replyTo' ];
}
$body = $customTemplate [ 'message' ] ? ? '' ;
$subject = $customTemplate [ 'subject' ] ? ? $subject ;
2023-08-29 21:40:30 +12:00
}
2023-09-28 04:51:17 +13:00
$queueForMails
2023-08-30 17:31:02 +12:00
-> setSmtpReplyTo ( $replyTo )
-> setSmtpSenderEmail ( $senderEmail )
-> setSmtpSenderName ( $senderName );
2023-04-19 20:29:29 +12:00
}
2023-08-28 17:53:02 +12:00
$emailVariables = [
'owner' => $user -> getAttribute ( 'name' ),
'direction' => $locale -> getText ( 'settings.direction' ),
2024-01-12 09:36:05 +13:00
/* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */
2023-08-31 10:36:40 +12:00
'user' => $user -> getAttribute ( 'name' ),
2023-08-31 09:48:25 +12:00
'team' => $team -> getAttribute ( 'name' ),
2024-01-12 09:36:05 +13:00
'redirect' => $url ,
2024-01-09 06:08:17 +13:00
'project' => $projectName
2023-08-28 17:53:02 +12:00
];
2023-01-13 19:05:16 +13:00
2023-09-28 04:51:17 +13:00
$queueForMails
2023-01-13 19:05:16 +13:00
-> setSubject ( $subject )
-> setBody ( $body )
-> setRecipient ( $invitee -> getAttribute ( 'email' ))
-> setName ( $invitee -> getAttribute ( 'name' ))
2023-08-28 17:53:02 +12:00
-> setVariables ( $emailVariables )
2023-01-13 19:05:16 +13:00
-> trigger ()
;
} elseif ( ! empty ( $phone )) {
2023-11-16 09:00:47 +13:00
if ( empty ( App :: getEnv ( '_APP_SMS_PROVIDER' ))) {
2023-09-06 05:10:48 +12:00
throw new Exception ( Exception :: GENERAL_PHONE_DISABLED , 'Phone provider not configured' );
}
2023-03-14 22:07:42 +13:00
$message = Template :: fromFile ( __DIR__ . '/../../config/locale/templates/sms-base.tpl' );
2023-04-19 20:29:29 +12:00
$customTemplate = $project -> getAttribute ( 'templates' , [])[ 'sms.invitation-' . $locale -> default ] ? ? [];
2023-04-19 20:44:22 +12:00
if ( ! empty ( $customTemplate )) {
2023-04-19 20:29:29 +12:00
$message = $customTemplate [ 'message' ];
}
2023-03-17 13:55:00 +13:00
$message = $message -> setParam ( '{{token}}' , $url );
2023-03-14 22:07:42 +13:00
$message = $message -> render ();
2023-11-16 09:00:47 +13:00
$messageDoc = new Document ([
'$id' => ID :: unique (),
2023-09-06 05:10:48 +12:00
'data' => [
'content' => $message ,
],
2023-11-16 09:00:47 +13:00
]);
2023-09-06 05:10:48 +12:00
2023-09-28 04:51:17 +13:00
$queueForMessaging
2024-02-21 01:06:35 +13:00
-> setType ( MESSAGE_SEND_TYPE_INTERNAL )
2023-11-16 09:00:47 +13:00
-> setMessage ( $messageDoc )
-> setRecipients ([ $phone ])
2024-02-21 01:06:35 +13:00
-> setProviderType ( 'SMS' );
2023-01-13 19:05:16 +13:00
}
2019-05-09 18:54:39 +12:00
}
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-04-14 00:39:31 +12:00
-> setParam ( 'teamId' , $team -> getId ())
-> setParam ( 'membershipId' , $membership -> getId ())
2020-06-30 23:09:28 +12:00
;
2022-09-07 23:11:10 +12:00
$response
-> setStatusCode ( Response :: STATUS_CODE_CREATED )
-> dynamic (
$membership
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $invitee -> getAttribute ( 'name' ))
2022-09-07 23:23:57 +12:00
-> setAttribute ( 'userEmail' , $invitee -> getAttribute ( 'email' )),
2022-09-07 23:14:53 +12:00
Response :: MODEL_MEMBERSHIP
);
2020-12-27 05:48:43 +13:00
});
2019-05-09 18:54:39 +12:00
2020-06-29 05:31:21 +12:00
App :: get ( '/v1/teams/:teamId/memberships' )
2023-08-02 03:26:48 +12:00
-> desc ( 'List team memberships' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2020-02-01 11:34:07 +13:00
-> label ( 'scope' , 'teams.read' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2020-02-01 11:34:07 +13:00
-> label ( 'sdk.namespace' , 'teams' )
2022-09-14 08:14:56 +12:00
-> label ( 'sdk.method' , 'listMemberships' )
-> label ( 'sdk.description' , '/docs/references/teams/list-team-members.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MEMBERSHIP_LIST )
2023-02-10 14:06:33 +13:00
-> label ( 'sdk.offline.model' , '/teams/{teamId}/memberships' )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2023-03-30 08:38:39 +13:00
-> param ( 'queries' , [], new Memberships (), '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 ( ', ' , Memberships :: ALLOWED_ATTRIBUTES ), true )
2020-09-11 02:40:14 +12:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-08-24 01:16:46 +12:00
-> action ( function ( string $teamId , array $queries , string $search , Response $response , Database $dbForProject ) {
2020-02-01 11:34:07 +13:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2020-02-01 11:34:07 +13:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2020-02-01 11:34:07 +13:00
2024-02-13 05:02:04 +13:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2022-08-12 11:53:52 +12:00
if ( ! empty ( $search )) {
2022-08-24 01:16:46 +12:00
$queries [] = Query :: search ( 'search' , $search );
2022-08-12 11:53:52 +12:00
}
2022-08-24 01:16:46 +12:00
// Set internal queries
$queries [] = Query :: equal ( 'teamId' , [ $teamId ]);
2024-02-12 22:55:45 +13:00
/**
2024-02-12 23:03:31 +13:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 22:55:45 +13:00
*/
2023-08-22 15:25:55 +12:00
$cursor = \array_filter ( $queries , function ( $query ) {
2023-12-07 03:10:40 +13:00
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
2023-08-22 15:25:55 +12:00
});
2022-08-31 11:31:43 +12:00
$cursor = reset ( $cursor );
2022-08-30 23:55:23 +12:00
if ( $cursor ) {
2022-08-24 01:16:46 +12:00
/** @var Query $cursor */
$membershipId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'memberships' , $membershipId );
2021-08-07 00:36:35 +12:00
2022-08-12 11:53:52 +12:00
if ( $cursorDocument -> isEmpty ()) {
2022-08-24 01:16:46 +12:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Membership ' { $membershipId } ' for the 'cursor' value not found. " );
2021-08-07 00:36:35 +12:00
}
2022-02-17 05:26:19 +13:00
2022-08-24 01:16:46 +12:00
$cursor -> setValue ( $cursorDocument );
2022-02-17 05:26:19 +13:00
}
2022-08-24 01:16:46 +12:00
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2022-02-17 05:26:19 +13:00
$memberships = $dbForProject -> find (
collection : 'memberships' ,
2022-08-24 01:16:46 +12:00
queries : $queries ,
2022-02-17 05:26:19 +13:00
);
2022-02-27 22:57:09 +13:00
$total = $dbForProject -> count (
2022-08-12 11:53:52 +12:00
collection : 'memberships' ,
queries : $filterQueries ,
2022-02-17 05:26:19 +13:00
max : APP_LIMIT_COUNT
);
2020-06-30 23:09:28 +12:00
2021-12-18 04:11:22 +13:00
$memberships = array_filter ( $memberships , fn ( Document $membership ) => ! empty ( $membership -> getAttribute ( 'userId' )));
2020-06-30 23:09:28 +12:00
2022-05-24 02:54:50 +12:00
$memberships = array_map ( function ( $membership ) use ( $dbForProject , $team ) {
2021-12-28 01:45:23 +13:00
$user = $dbForProject -> getDocument ( 'users' , $membership -> getAttribute ( 'userId' ));
2020-06-30 23:09:28 +12:00
2024-02-26 05:30:59 +13:00
$mfa = $user -> getAttribute ( 'mfa' , false );
if ( $mfa ) {
$totpEnabled = $user -> getAttribute ( 'totp' , false ) && $user -> getAttribute ( 'totpVerification' , false );
$emailEnabled = $user -> getAttribute ( 'email' , false ) && $user -> getAttribute ( 'emailVerification' , false );
$phoneEnabled = $user -> getAttribute ( 'phone' , false ) && $user -> getAttribute ( 'phoneVerification' , false );
if ( ! $totpEnabled && ! $emailEnabled && ! $phoneEnabled ) {
$mfa = false ;
}
}
2021-12-18 04:11:22 +13:00
$membership
2024-02-26 05:30:59 +13:00
-> setAttribute ( 'mfa' , $mfa )
2022-05-13 01:20:06 +12:00
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $user -> getAttribute ( 'name' ))
-> setAttribute ( 'userEmail' , $user -> getAttribute ( 'email' ))
2021-12-18 04:11:22 +13:00
;
return $membership ;
}, $memberships );
2020-06-30 23:09:28 +12:00
2021-07-26 02:47:18 +12:00
$response -> dynamic ( new Document ([
2021-12-18 04:11:22 +13:00
'memberships' => $memberships ,
2022-02-27 22:57:09 +13:00
'total' => $total ,
2021-05-07 10:31:05 +12:00
]), Response :: MODEL_MEMBERSHIP_LIST );
2020-12-27 05:48:43 +13:00
});
2020-02-01 11:34:07 +13:00
2021-08-04 18:57:30 +12:00
App :: get ( '/v1/teams/:teamId/memberships/:membershipId' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Get team membership' )
2021-08-04 18:57:30 +12:00
-> groups ([ 'api' , 'teams' ])
-> label ( 'scope' , 'teams.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'getMembership' )
2021-08-04 19:29:10 +12:00
-> label ( 'sdk.description' , '/docs/references/teams/get-team-member.md' )
2021-08-04 18:57:30 +12:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
2022-09-19 23:43:21 +12:00
-> label ( 'sdk.response.model' , Response :: MODEL_MEMBERSHIP )
2023-02-10 14:06:33 +13:00
-> label ( 'sdk.offline.model' , '/teams/{teamId}/memberships' )
-> label ( 'sdk.offline.key' , '{membershipId}' )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
-> param ( 'membershipId' , '' , new UID (), 'Membership ID.' )
2021-08-04 18:57:30 +12:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-05-04 01:21:25 +12:00
-> action ( function ( string $teamId , string $membershipId , Response $response , Database $dbForProject ) {
2021-08-04 18:57:30 +12:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2021-08-04 18:57:30 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2021-08-04 18:57:30 +12:00
}
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> getDocument ( 'memberships' , $membershipId );
2021-08-04 18:57:30 +12:00
2022-05-24 02:54:50 +12:00
if ( $membership -> isEmpty () || empty ( $membership -> getAttribute ( 'userId' ))) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: MEMBERSHIP_NOT_FOUND );
2021-08-04 18:57:30 +12:00
}
2021-12-28 01:45:23 +13:00
$user = $dbForProject -> getDocument ( 'users' , $membership -> getAttribute ( 'userId' ));
2021-12-18 02:59:55 +13:00
$membership
2022-05-13 01:20:06 +12:00
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $user -> getAttribute ( 'name' ))
-> setAttribute ( 'userEmail' , $user -> getAttribute ( 'email' ))
2020-10-31 21:42:41 +13:00
;
2021-08-04 18:57:30 +12:00
2022-05-24 02:54:50 +12:00
$response -> dynamic ( $membership , Response :: MODEL_MEMBERSHIP );
2021-08-04 18:57:30 +12:00
});
2021-05-13 01:44:41 +12:00
App :: patch ( '/v1/teams/:teamId/memberships/:membershipId' )
2023-09-28 04:11:58 +13:00
-> desc ( 'Update membership' )
2021-05-13 01:44:41 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].memberships.[membershipId].update' )
2021-05-14 02:47:35 +12:00
-> label ( 'scope' , 'teams.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'membership.update' )
2022-08-12 01:19:05 +12:00
-> label ( 'audits.resource' , 'team/{request.teamId}' )
2021-05-14 02:47:35 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2021-05-13 01:44:41 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2023-07-11 18:11:42 +12:00
-> label ( 'sdk.method' , 'updateMembership' )
-> label ( 'sdk.description' , '/docs/references/teams/update-team-membership.md' )
2021-05-13 01:44:41 +12:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MEMBERSHIP )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2021-05-13 01:44:41 +12:00
-> param ( 'membershipId' , '' , new UID (), 'Membership ID.' )
2022-05-01 19:54:58 +12:00
-> param ( 'roles' , [], new ArrayList ( new Key (), APP_LIMIT_ARRAY_PARAMS_SIZE ), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.' )
2021-05-13 01:44:41 +12:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'user' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $teamId , string $membershipId , array $roles , Request $request , Response $response , Document $user , Database $dbForProject , Event $queueForEvents ) {
2021-05-13 01:44:41 +12:00
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2021-05-15 13:20:12 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2021-05-13 02:47:56 +12:00
}
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> getDocument ( 'memberships' , $membershipId );
2021-05-15 13:20:12 +12:00
if ( $membership -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: MEMBERSHIP_NOT_FOUND );
2021-05-13 01:44:41 +12:00
}
2021-12-28 01:45:23 +13:00
$profile = $dbForProject -> getDocument ( 'users' , $membership -> getAttribute ( 'userId' ));
2021-05-15 13:20:12 +12:00
if ( $profile -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-05-15 13:20:12 +12:00
}
2021-05-13 01:44:41 +12:00
2021-12-11 06:52:33 +13:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
$isAppUser = Auth :: isAppUser ( Authorization :: getRoles ());
2022-05-24 02:54:50 +12:00
$isOwner = Authorization :: isRole ( 'team:' . $team -> getId () . '/owner' );
2021-12-18 02:59:55 +13:00
2021-05-13 05:00:22 +12:00
if ( ! $isOwner && ! $isPrivilegedUser && ! $isAppUser ) { // Not owner, not admin, not app (server)
2022-08-14 20:55:59 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED , 'User is not allowed to modify roles' );
2021-05-13 05:00:22 +12:00
}
2021-05-13 01:44:41 +12:00
2022-02-17 05:26:19 +13:00
/**
* Update the roles
*/
2021-05-13 05:00:22 +12:00
$membership -> setAttribute ( 'roles' , $roles );
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> updateDocument ( 'memberships' , $membership -> getId (), $membership );
2021-05-13 01:44:41 +12:00
2022-02-17 05:26:19 +13:00
/**
* Replace membership on profile
*/
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $profile -> getId ());
2021-05-13 01:44:41 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-04-14 00:39:31 +12:00
-> setParam ( 'teamId' , $team -> getId ())
-> setParam ( 'membershipId' , $membership -> getId ());
2021-05-13 01:44:41 +12:00
2022-02-17 05:26:19 +13:00
$response -> dynamic (
$membership
2022-05-13 01:20:06 +12:00
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $profile -> getAttribute ( 'name' ))
-> setAttribute ( 'userEmail' , $profile -> getAttribute ( 'email' )),
2022-02-17 05:26:19 +13:00
Response :: MODEL_MEMBERSHIP
);
2020-12-27 05:48:43 +13:00
});
2020-02-01 11:34:07 +13:00
2021-05-08 03:49:23 +12:00
App :: patch ( '/v1/teams/:teamId/memberships/:membershipId/status' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Update team membership status' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].memberships.[membershipId].update.status' )
2020-01-20 01:22:54 +13:00
-> label ( 'scope' , 'public' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'membership.update' )
2022-08-12 01:19:05 +12:00
-> label ( 'audits.resource' , 'team/{request.teamId}' )
2022-08-12 23:01:12 +12:00
-> label ( 'audits.userId' , '{request.userId}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2020-01-31 05:18:46 +13:00
-> label ( 'sdk.method' , 'updateMembershipStatus' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/update-team-membership-status.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MEMBERSHIP )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2021-05-08 03:49:23 +12:00
-> param ( 'membershipId' , '' , new UID (), 'Membership ID.' )
2021-12-11 01:27:11 +13:00
-> param ( 'userId' , '' , new UID (), 'User ID.' )
2020-09-11 02:40:14 +12:00
-> param ( 'secret' , '' , new Text ( 256 ), 'Secret key.' )
2020-12-27 05:48:43 +13:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'user' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-11-01 03:54:15 +13:00
-> inject ( 'project' )
2020-12-27 05:48:43 +13:00
-> inject ( 'geodb' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $teamId , string $membershipId , string $userId , string $secret , Request $request , Response $response , Document $user , Database $dbForProject , Document $project , Reader $geodb , Event $queueForEvents ) {
2020-06-30 23:09:28 +12:00
$protocol = $request -> getProtocol ();
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> getDocument ( 'memberships' , $membershipId );
2020-06-30 23:09:28 +12:00
2021-06-21 01:59:36 +12:00
if ( $membership -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: MEMBERSHIP_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( $membership -> getAttribute ( 'teamId' ) !== $teamId ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_MEMBERSHIP_MISMATCH );
2020-06-30 23:09:28 +12:00
}
2020-01-20 01:22:54 +13:00
2021-12-28 01:45:23 +13:00
$team = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'teams' , $teamId ));
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( Auth :: hash ( $secret ) !== $membership -> getAttribute ( 'secret' )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVALID_SECRET );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-04-14 00:39:31 +12:00
if ( $userId !== $membership -> getAttribute ( 'userId' )) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVITE_MISMATCH , 'Invite does not belong to current user (' . $user -> getAttribute ( 'email' ) . ')' );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $user -> isEmpty ()) {
2023-07-07 12:12:39 +12:00
$user -> setAttributes ( $dbForProject -> getDocument ( 'users' , $userId ) -> getArrayCopy ()); // Get user
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( $membership -> getAttribute ( 'userId' ) !== $user -> getId ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVITE_MISMATCH , 'Invite does not belong to current user (' . $user -> getAttribute ( 'email' ) . ')' );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2022-01-19 11:27:24 +13:00
if ( $membership -> getAttribute ( 'confirm' ) === true ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: MEMBERSHIP_ALREADY_CONFIRMED );
2022-01-19 11:27:24 +13:00
}
2020-06-30 23:09:28 +12:00
$membership // Attach user to team
2022-07-14 02:02:49 +12:00
-> setAttribute ( 'joined' , DateTime :: now ())
2020-06-30 23:09:28 +12:00
-> setAttribute ( 'confirm' , true )
;
2019-05-09 18:54:39 +12:00
2023-07-07 12:12:39 +12:00
Authorization :: skip ( fn () => $dbForProject -> updateDocument ( 'users' , $user -> getId (), $user -> setAttribute ( 'emailVerification' , true )));
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
// Log user in
2020-08-12 02:28:51 +12:00
2022-08-19 16:04:33 +12:00
Authorization :: setRole ( Role :: user ( $user -> getId ()) -> toString ());
2021-07-17 22:04:43 +12:00
2021-02-15 06:28:54 +13:00
$detector = new Detector ( $request -> getUserAgent ( 'UNKNOWN' ));
$record = $geodb -> get ( $request -> getIP ());
2022-11-14 22:42:18 +13:00
$authDuration = $project -> getAttribute ( 'auths' , [])[ 'duration' ] ? ? Auth :: TOKEN_EXPIRATION_LOGIN_LONG ;
2022-11-02 03:43:18 +13:00
$expire = DateTime :: addSeconds ( new \DateTime (), $authDuration );
2020-06-30 23:09:28 +12:00
$secret = Auth :: tokenGenerator ();
2021-02-15 06:28:54 +13:00
$session = new Document ( array_merge ([
2022-08-15 02:22:38 +12:00
'$id' => ID :: unique (),
2022-08-15 23:24:31 +12:00
'userId' => $user -> getId (),
'userInternalId' => $user -> getInternalId (),
2021-02-20 01:12:47 +13:00
'provider' => Auth :: SESSION_PROVIDER_EMAIL ,
2021-02-19 23:02:02 +13:00
'providerUid' => $user -> getAttribute ( 'email' ),
2020-11-13 00:54:16 +13:00
'secret' => Auth :: hash ( $secret ), // One way hash encryption to protect DB leak
2020-07-04 03:14:51 +12:00
'userAgent' => $request -> getUserAgent ( 'UNKNOWN' ),
2020-06-30 23:09:28 +12:00
'ip' => $request -> getIP (),
2024-01-19 02:19:30 +13:00
'factors' => [ 'email' ],
2021-02-15 06:28:54 +13:00
'countryCode' => ( $record ) ? \strtolower ( $record [ 'country' ][ 'iso_code' ]) : '--' ,
2024-01-16 02:43:21 +13:00
'expire' => DateTime :: addSeconds ( new \DateTime (), $authDuration )
2021-02-15 06:28:54 +13:00
], $detector -> getOS (), $detector -> getClient (), $detector -> getDevice ()));
2020-08-12 02:28:51 +12:00
2021-12-28 01:45:23 +13:00
$session = $dbForProject -> createDocument ( 'sessions' , $session
2022-08-02 21:21:53 +12:00
-> setAttribute ( '$permissions' , [
2022-08-15 23:24:31 +12:00
Permission :: read ( Role :: user ( $user -> getId ())),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
2022-08-02 21:21:53 +12:00
]));
2021-12-18 04:11:22 +13:00
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2019-05-09 18:54:39 +12:00
2022-08-19 16:04:33 +12:00
Authorization :: setRole ( Role :: user ( $userId ) -> toString ());
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> updateDocument ( 'memberships' , $membership -> getId (), $membership );
2022-05-13 01:20:06 +12:00
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2020-01-20 01:22:54 +13:00
2024-02-10 06:48:18 +13:00
Authorization :: skip ( fn () => $dbForProject -> increaseDocumentAttribute ( 'teams' , $team -> getId (), 'total' , 1 ));
2019-05-09 18:54:39 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-04-14 00:39:31 +12:00
-> setParam ( 'teamId' , $team -> getId ())
-> setParam ( 'membershipId' , $membership -> getId ())
2020-06-30 23:09:28 +12:00
;
2020-03-18 00:36:13 +13:00
2020-06-30 23:09:28 +12:00
if ( ! Config :: getParam ( 'domainVerification' )) {
2020-01-15 09:50:49 +13:00
$response
2020-06-30 23:09:28 +12:00
-> addHeader ( 'X-Fallback-Cookies' , \json_encode ([ Auth :: $cookieName => Auth :: encodeSession ( $user -> getId (), $secret )]))
2020-01-15 09:50:49 +13:00
;
2019-05-09 18:54:39 +12:00
}
2020-06-30 23:09:28 +12:00
$response
2022-07-13 01:32:39 +12:00
-> addCookie ( Auth :: $cookieName . '_legacy' , Auth :: encodeSession ( $user -> getId (), $secret ), ( new \DateTime ( $expire )) -> getTimestamp (), '/' , Config :: getParam ( 'cookieDomain' ), ( 'https' == $protocol ), true , null )
-> addCookie ( Auth :: $cookieName , Auth :: encodeSession ( $user -> getId (), $secret ), ( new \DateTime ( $expire )) -> getTimestamp (), '/' , Config :: getParam ( 'cookieDomain' ), ( 'https' == $protocol ), true , Config :: getParam ( 'cookieSamesite' ))
2020-06-30 23:09:28 +12:00
;
2020-07-03 09:48:02 +12:00
2022-05-24 02:54:50 +12:00
$response -> dynamic (
$membership
2022-05-13 01:20:06 +12:00
-> setAttribute ( 'teamName' , $team -> getAttribute ( 'name' ))
-> setAttribute ( 'userName' , $user -> getAttribute ( 'name' ))
2022-05-24 02:54:50 +12:00
-> setAttribute ( 'userEmail' , $user -> getAttribute ( 'email' )),
Response :: MODEL_MEMBERSHIP
);
2020-12-27 05:48:43 +13:00
});
2019-05-09 18:54:39 +12:00
2021-05-08 03:49:23 +12:00
App :: delete ( '/v1/teams/:teamId/memberships/:membershipId' )
2023-08-02 03:26:48 +12:00
-> desc ( 'Delete team membership' )
2020-06-26 06:32:12 +12:00
-> groups ([ 'api' , 'teams' ])
2022-04-14 00:39:31 +12:00
-> label ( 'event' , 'teams.[teamId].memberships.[membershipId].delete' )
2020-02-10 05:53:33 +13:00
-> label ( 'scope' , 'teams.write' )
2022-09-05 20:00:08 +12:00
-> label ( 'audits.event' , 'membership.delete' )
2022-08-09 02:32:54 +12:00
-> label ( 'audits.resource' , 'team/{request.teamId}' )
2021-04-16 19:22:17 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_SESSION , APP_AUTH_TYPE_KEY , APP_AUTH_TYPE_JWT ])
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.namespace' , 'teams' )
2020-01-31 05:18:46 +13:00
-> label ( 'sdk.method' , 'deleteMembership' )
2019-10-08 20:09:35 +13:00
-> label ( 'sdk.description' , '/docs/references/teams/delete-team-membership.md' )
2020-11-12 10:02:24 +13:00
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
2021-12-11 01:27:11 +13:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2021-05-08 03:49:23 +12:00
-> param ( 'membershipId' , '' , new UID (), 'Membership ID.' )
2020-12-27 05:48:43 +13:00
-> inject ( 'response' )
2021-12-28 01:45:23 +13:00
-> inject ( 'dbForProject' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $teamId , string $membershipId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
$membership = $dbForProject -> getDocument ( 'memberships' , $membershipId );
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $membership -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_INVITE_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2020-06-30 23:09:28 +12:00
if ( $membership -> getAttribute ( 'teamId' ) !== $teamId ) {
2022-08-14 18:56:12 +12:00
throw new Exception ( Exception :: TEAM_MEMBERSHIP_MISMATCH );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-12-28 01:45:23 +13:00
$user = $dbForProject -> getDocument ( 'users' , $membership -> getAttribute ( 'userId' ));
2021-05-10 09:20:57 +12:00
if ( $user -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: USER_NOT_FOUND );
2021-05-10 09:20:57 +12:00
}
2021-12-28 01:45:23 +13:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
2019-05-09 18:54:39 +12:00
2021-06-21 01:59:36 +12:00
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2021-12-07 21:01:09 +13:00
try {
$dbForProject -> deleteDocument ( 'memberships' , $membership -> getId ());
} catch ( AuthorizationException $exception ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: USER_UNAUTHORIZED );
2024-02-08 14:17:54 +13:00
} catch ( \Throwable $exception ) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove membership from DB' );
2020-06-30 23:09:28 +12:00
}
2019-05-09 18:54:39 +12:00
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $user -> getId ());
2021-05-10 09:20:57 +12:00
2020-06-30 23:09:28 +12:00
if ( $membership -> getAttribute ( 'confirm' )) { // Count only confirmed members
2024-02-10 06:48:18 +13:00
Authorization :: skip ( fn () => $dbForProject -> decreaseDocumentAttribute ( 'teams' , $team -> getId (), 'total' , 1 , 0 ));
2019-05-09 18:54:39 +12:00
}
2020-06-30 23:09:28 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-04-14 00:39:31 +12:00
-> setParam ( 'teamId' , $team -> getId ())
-> setParam ( 'membershipId' , $membership -> getId ())
-> setPayload ( $response -> output ( $membership , Response :: MODEL_MEMBERSHIP ))
2020-12-03 11:15:20 +13:00
;
2020-06-30 23:09:28 +12:00
$response -> noContent ();
2020-12-27 05:48:43 +13:00
});
2022-04-22 02:07:08 +12:00
App :: get ( '/v1/teams/:teamId/logs' )
2023-08-02 03:26:48 +12:00
-> desc ( 'List team logs' )
2022-04-22 02:07:08 +12:00
-> groups ([ 'api' , 'teams' ])
-> label ( 'scope' , 'teams.read' )
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
-> label ( 'sdk.namespace' , 'teams' )
-> label ( 'sdk.method' , 'listLogs' )
-> label ( 'sdk.description' , '/docs/references/teams/get-team-logs.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_LOG_LIST )
2022-09-19 22:05:42 +12:00
-> param ( 'teamId' , '' , new UID (), 'Team ID.' )
2023-05-17 00:56:20 +12: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-04-22 02:07:08 +12:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'locale' )
-> inject ( 'geodb' )
2022-08-24 00:50:52 +12:00
-> action ( function ( string $teamId , array $queries , Response $response , Database $dbForProject , Locale $locale , Reader $geodb ) {
2022-04-22 02:07:08 +12:00
$team = $dbForProject -> getDocument ( 'teams' , $teamId );
if ( $team -> isEmpty ()) {
2022-07-27 02:24:32 +12:00
throw new Exception ( Exception :: TEAM_NOT_FOUND );
2022-04-22 02:07:08 +12:00
}
2024-02-13 05:02:04 +13:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2022-08-24 00:50:52 +12:00
$grouped = Query :: groupByType ( $queries );
2022-08-30 23:55:23 +12:00
$limit = $grouped [ 'limit' ] ? ? APP_LIMIT_COUNT ;
2022-08-24 00:50:52 +12:00
$offset = $grouped [ 'offset' ] ? ? 0 ;
2022-08-24 01:10:27 +12:00
2022-04-22 02:07:08 +12:00
$audit = new Audit ( $dbForProject );
2022-05-24 02:54:50 +12:00
$resource = 'team/' . $team -> getId ();
2022-04-22 02:07:08 +12:00
$logs = $audit -> getLogsByResource ( $resource , $limit , $offset );
$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' ],
2023-07-13 05:27:57 +12:00
'userId' => $log [ 'data' ][ 'userId' ],
2022-04-22 02:07:08 +12: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 ) {
2022-05-24 02:54:50 +12: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' ));
2022-04-22 02:07:08 +12:00
} else {
$output [ $i ][ 'countryCode' ] = '--' ;
$output [ $i ][ 'countryName' ] = $locale -> getText ( 'locale.country.unknown' );
}
}
$response -> dynamic ( new Document ([
'total' => $audit -> countLogsByResource ( $resource ),
'logs' => $output ,
]), Response :: MODEL_LOG_LIST );
});