Merge pull request #1129 from appwrite/feat-refactor-inviteid
feat: refactor inviteId to membershipId
This commit is contained in:
commit
934b9aec27
8 changed files with 30 additions and 29 deletions
|
@ -38,8 +38,9 @@
|
||||||
- Function execution environment variable `APPWRITE_FUNCTION_EVENT_PAYLOAD` renamed to `APPWRITE_FUNCTION_EVENT_DATA` (#1045)
|
- Function execution environment variable `APPWRITE_FUNCTION_EVENT_PAYLOAD` renamed to `APPWRITE_FUNCTION_EVENT_DATA` (#1045)
|
||||||
- Function execution environment variable `APPWRITE_FUNCTION_ENV_NAME` renamed to `APPWRITE_FUNCTION_RUNTIME_NAME` (#1101)
|
- Function execution environment variable `APPWRITE_FUNCTION_ENV_NAME` renamed to `APPWRITE_FUNCTION_RUNTIME_NAME` (#1101)
|
||||||
- Function execution environment variable `APPWRITE_FUNCTION_ENV_VERSION` renamed to `APPWRITE_FUNCTION_RUNTIME_VERSION` (#1101)
|
- Function execution environment variable `APPWRITE_FUNCTION_ENV_VERSION` renamed to `APPWRITE_FUNCTION_RUNTIME_VERSION` (#1101)
|
||||||
- Introdcues rate limits for:
|
- Introduces rate limits for:
|
||||||
- Team invite (10 requests in every 60 minutes per IP address) (#1088)
|
- Team invite (10 requests in every 60 minutes per IP address) (#1088)
|
||||||
|
- Rename param `inviteId` to the more accurate `membershipId` in the Teams API (#1129)
|
||||||
|
|
||||||
# Version 0.7.2
|
# Version 0.7.2
|
||||||
|
|
||||||
|
|
|
@ -419,7 +419,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = Template::parseURL($url);
|
$url = Template::parseURL($url);
|
||||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||||
$url = Template::unParseURL($url);
|
$url = Template::unParseURL($url);
|
||||||
|
|
||||||
$body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl');
|
$body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl');
|
||||||
|
@ -524,7 +524,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||||
$response->dynamic(new Document(['sum' => $projectDB->getSum(), 'memberships' => $users]), Response::MODEL_MEMBERSHIP_LIST);
|
$response->dynamic(new Document(['sum' => $projectDB->getSum(), 'memberships' => $users]), Response::MODEL_MEMBERSHIP_LIST);
|
||||||
});
|
});
|
||||||
|
|
||||||
App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
|
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||||
->desc('Update Team Membership Status')
|
->desc('Update Team Membership Status')
|
||||||
->groups(['api', 'teams'])
|
->groups(['api', 'teams'])
|
||||||
->label('event', 'teams.memberships.update.status')
|
->label('event', 'teams.memberships.update.status')
|
||||||
|
@ -537,7 +537,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
|
||||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||||
->param('teamId', '', new UID(), 'Team unique ID.')
|
->param('teamId', '', new UID(), 'Team unique ID.')
|
||||||
->param('inviteId', '', new UID(), 'Invite unique ID.')
|
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||||
->param('userId', '', new UID(), 'User unique ID.')
|
->param('userId', '', new UID(), 'User unique ID.')
|
||||||
->param('secret', '', new Text(256), 'Secret key.')
|
->param('secret', '', new Text(256), 'Secret key.')
|
||||||
->inject('request')
|
->inject('request')
|
||||||
|
@ -546,7 +546,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
|
||||||
->inject('projectDB')
|
->inject('projectDB')
|
||||||
->inject('geodb')
|
->inject('geodb')
|
||||||
->inject('audits')
|
->inject('audits')
|
||||||
->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $projectDB, $geodb, $audits) {
|
->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $projectDB, $geodb, $audits) {
|
||||||
/** @var Utopia\Swoole\Request $request */
|
/** @var Utopia\Swoole\Request $request */
|
||||||
/** @var Appwrite\Utopia\Response $response */
|
/** @var Appwrite\Utopia\Response $response */
|
||||||
/** @var Appwrite\Database\Document $user */
|
/** @var Appwrite\Database\Document $user */
|
||||||
|
@ -555,7 +555,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
|
||||||
/** @var Appwrite\Event\Event $audits */
|
/** @var Appwrite\Event\Event $audits */
|
||||||
|
|
||||||
$protocol = $request->getProtocol();
|
$protocol = $request->getProtocol();
|
||||||
$membership = $projectDB->getDocument($inviteId);
|
$membership = $projectDB->getDocument($membershipId);
|
||||||
|
|
||||||
if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
|
if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
|
||||||
throw new Exception('Invite not found', 404);
|
throw new Exception('Invite not found', 404);
|
||||||
|
@ -671,7 +671,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
|
||||||
])), Response::MODEL_MEMBERSHIP);
|
])), Response::MODEL_MEMBERSHIP);
|
||||||
});
|
});
|
||||||
|
|
||||||
App::delete('/v1/teams/:teamId/memberships/:inviteId')
|
App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||||
->desc('Delete Team Membership')
|
->desc('Delete Team Membership')
|
||||||
->groups(['api', 'teams'])
|
->groups(['api', 'teams'])
|
||||||
->label('event', 'teams.memberships.delete')
|
->label('event', 'teams.memberships.delete')
|
||||||
|
@ -683,18 +683,18 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId')
|
||||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||||
->label('sdk.response.model', Response::MODEL_NONE)
|
->label('sdk.response.model', Response::MODEL_NONE)
|
||||||
->param('teamId', '', new UID(), 'Team unique ID.')
|
->param('teamId', '', new UID(), 'Team unique ID.')
|
||||||
->param('inviteId', '', new UID(), 'Invite unique ID.')
|
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('projectDB')
|
->inject('projectDB')
|
||||||
->inject('audits')
|
->inject('audits')
|
||||||
->inject('events')
|
->inject('events')
|
||||||
->action(function ($teamId, $inviteId, $response, $projectDB, $audits, $events) {
|
->action(function ($teamId, $membershipId, $response, $projectDB, $audits, $events) {
|
||||||
/** @var Appwrite\Utopia\Response $response */
|
/** @var Appwrite\Utopia\Response $response */
|
||||||
/** @var Appwrite\Database\Database $projectDB */
|
/** @var Appwrite\Database\Database $projectDB */
|
||||||
/** @var Appwrite\Event\Event $audits */
|
/** @var Appwrite\Event\Event $audits */
|
||||||
/** @var Appwrite\Event\Event $events */
|
/** @var Appwrite\Event\Event $events */
|
||||||
|
|
||||||
$membership = $projectDB->getDocument($inviteId);
|
$membership = $projectDB->getDocument($membershipId);
|
||||||
|
|
||||||
if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
|
if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
|
||||||
throw new Exception('Invite not found', 404);
|
throw new Exception('Invite not found', 404);
|
||||||
|
|
|
@ -415,7 +415,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||||
data-failure-param-alert-classname="error">
|
data-failure-param-alert-classname="error">
|
||||||
|
|
||||||
<input name="teamId" data-ls-attrs="id=leave-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
|
<input name="teamId" data-ls-attrs="id=leave-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
|
||||||
<input name="inviteId" data-ls-attrs="id=leave-inviteId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
||||||
|
|
||||||
<button class="danger">Leave</button>
|
<button class="danger">Leave</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -437,7 +437,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
|
||||||
data-failure-param-alert-classname="error">
|
data-failure-param-alert-classname="error">
|
||||||
|
|
||||||
<input name="teamId" data-ls-attrs="id=resend-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
|
<input name="teamId" data-ls-attrs="id=resend-teamId-{{member.$id}}" type="hidden" data-ls-bind="{{console-project.teamId}}">
|
||||||
<input name="inviteId" data-ls-attrs="id=resend-inviteId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
<input name="membershipId" data-ls-attrs="id=resend-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
||||||
|
|
||||||
<button class="reverse">Resend</button>
|
<button class="reverse">Resend</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
data-failure-param-alert-classname="error">
|
data-failure-param-alert-classname="error">
|
||||||
|
|
||||||
<input name="teamId" data-ls-attrs="id={{member.$id}}" type="hidden" data-ls-bind="{{router.params.id}}">
|
<input name="teamId" data-ls-attrs="id={{member.$id}}" type="hidden" data-ls-bind="{{router.params.id}}">
|
||||||
<input name="inviteId" data-ls-attrs="id=leave-inviteId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
<input name="membershipId" data-ls-attrs="id=leave-membershipId-{{member.$id}}" type="hidden" data-ls-bind="{{member.$id}}">
|
||||||
|
|
||||||
<button class="danger">Remove</button>
|
<button class="danger">Remove</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
data-scope="console"
|
data-scope="console"
|
||||||
data-event="submit"
|
data-event="submit"
|
||||||
data-param-team-id="{{router.params.teamId}}"
|
data-param-team-id="{{router.params.teamId}}"
|
||||||
data-param-invite-id="{{router.params.inviteId}}"
|
data-param-invite-id="{{router.params.membershipId}}"
|
||||||
data-param-user-id="{{router.params.userId}}"
|
data-param-user-id="{{router.params.userId}}"
|
||||||
data-param-secret="{{router.params.secret}}"
|
data-param-secret="{{router.params.secret}}"
|
||||||
data-success="redirect,alert,trigger"
|
data-success="redirect,alert,trigger"
|
||||||
|
|
|
@ -72,7 +72,7 @@ trait TeamsBaseClient
|
||||||
$this->assertEquals('Invitation to '.$teamName.' Team at '.$this->getProject()['name'], $lastEmail['subject']);
|
$this->assertEquals('Invitation to '.$teamName.' Team at '.$this->getProject()['name'], $lastEmail['subject']);
|
||||||
|
|
||||||
$secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
$secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
||||||
$inviteUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?inviteId=', 0) + 10, 13);
|
$membershipUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?membershipId=', 0) + 14, 13);
|
||||||
$userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13);
|
$userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +117,7 @@ trait TeamsBaseClient
|
||||||
return [
|
return [
|
||||||
'teamUid' => $teamUid,
|
'teamUid' => $teamUid,
|
||||||
'secret' => $secret,
|
'secret' => $secret,
|
||||||
'inviteUid' => $inviteUid,
|
'membershipUid' => $membershipUid,
|
||||||
'userUid' => $userUid,
|
'userUid' => $userUid,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -129,13 +129,13 @@ trait TeamsBaseClient
|
||||||
{
|
{
|
||||||
$teamUid = $data['teamUid'] ?? '';
|
$teamUid = $data['teamUid'] ?? '';
|
||||||
$secret = $data['secret'] ?? '';
|
$secret = $data['secret'] ?? '';
|
||||||
$inviteUid = $data['inviteUid'] ?? '';
|
$membershipUid = $data['membershipUid'] ?? '';
|
||||||
$userUid = $data['userUid'] ?? '';
|
$userUid = $data['userUid'] ?? '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for SUCCESS
|
* Test for SUCCESS
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -155,7 +155,7 @@ trait TeamsBaseClient
|
||||||
/**
|
/**
|
||||||
* Test for FAILURE
|
* Test for FAILURE
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -166,7 +166,7 @@ trait TeamsBaseClient
|
||||||
|
|
||||||
$this->assertEquals(401, $response['headers']['status-code']);
|
$this->assertEquals(401, $response['headers']['status-code']);
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -177,7 +177,7 @@ trait TeamsBaseClient
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
$this->assertEquals(400, $response['headers']['status-code']);
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -188,7 +188,7 @@ trait TeamsBaseClient
|
||||||
|
|
||||||
$this->assertEquals(401, $response['headers']['status-code']);
|
$this->assertEquals(401, $response['headers']['status-code']);
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$response = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -208,12 +208,12 @@ trait TeamsBaseClient
|
||||||
public function testDeleteTeamMembership($data):array
|
public function testDeleteTeamMembership($data):array
|
||||||
{
|
{
|
||||||
$teamUid = $data['teamUid'] ?? '';
|
$teamUid = $data['teamUid'] ?? '';
|
||||||
$inviteUid = $data['inviteUid'] ?? '';
|
$membershipUid = $data['membershipUid'] ?? '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for SUCCESS
|
* Test for SUCCESS
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$inviteUid, array_merge([
|
$response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
@ -225,7 +225,7 @@ trait TeamsBaseClient
|
||||||
/**
|
/**
|
||||||
* Test for FAILURE
|
* Test for FAILURE
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$inviteUid, array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships/'.$membershipUid, array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
|
|
@ -464,7 +464,7 @@ trait WebhooksBase
|
||||||
$lastEmail = $this->getLastEmail();
|
$lastEmail = $this->getLastEmail();
|
||||||
|
|
||||||
$secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
$secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256);
|
||||||
$inviteUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?inviteId=', 0) + 10, 13);
|
$membershipUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?membershipId=', 0) + 14, 13);
|
||||||
$userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13);
|
$userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 13);
|
||||||
|
|
||||||
$webhook = $this->getLastRequest();
|
$webhook = $this->getLastRequest();
|
||||||
|
@ -490,7 +490,7 @@ trait WebhooksBase
|
||||||
return [
|
return [
|
||||||
'teamId' => $teamUid,
|
'teamId' => $teamUid,
|
||||||
'secret' => $secret,
|
'secret' => $secret,
|
||||||
'inviteId' => $inviteUid,
|
'membershipId' => $membershipUid,
|
||||||
'userId' => $webhook['data']['userId'],
|
'userId' => $webhook['data']['userId'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -714,13 +714,13 @@ class WebhooksCustomClientTest extends Scope
|
||||||
{
|
{
|
||||||
$teamUid = $data['teamId'] ?? '';
|
$teamUid = $data['teamId'] ?? '';
|
||||||
$secret = $data['secret'] ?? '';
|
$secret = $data['secret'] ?? '';
|
||||||
$inviteUid = $data['inviteId'] ?? '';
|
$membershipUid = $data['membershipId'] ?? '';
|
||||||
$userUid = $data['userId'] ?? '';
|
$userUid = $data['userId'] ?? '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for SUCCESS
|
* Test for SUCCESS
|
||||||
*/
|
*/
|
||||||
$team = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$inviteUid.'/status', array_merge([
|
$team = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
|
Loading…
Reference in a new issue