mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
remove legacy hackathon code
This commit is contained in:
@@ -5,7 +5,6 @@ $common = include __DIR__ . '/collections/common.php';
|
||||
$projects = include __DIR__ . '/collections/projects.php';
|
||||
$databases = include __DIR__ . '/collections/databases.php';
|
||||
$platform = include __DIR__ . '/collections/platform.php';
|
||||
$authPlans = include __DIR__ . '/collections/auth_plans.php';
|
||||
$logs = include __DIR__ . '/collections/logs.php';
|
||||
|
||||
// see - http.php#245
|
||||
@@ -28,7 +27,7 @@ $collections = [
|
||||
'buckets' => $buckets,
|
||||
'databases' => $databases,
|
||||
'projects' => array_merge($projects, $common),
|
||||
'console' => array_merge($platform, $authPlans, $common),
|
||||
'console' => array_merge($platform, $common),
|
||||
'logs' => $logs,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,676 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
return [
|
||||
'auth_plans' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('auth_plans'),
|
||||
'name' => 'Auth Plans',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('planId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripePriceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripeMeterId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripeProductId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('price'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('currency'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 3,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'usd',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('interval'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 10,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('features'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('isDefault'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('isFree'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('maxUsers'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('active'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_planId'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['projectId', 'planId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_default'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId', 'isDefault'],
|
||||
'lengths' => [Database::LENGTH_KEY, 0],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_active'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId', 'active'],
|
||||
'lengths' => [Database::LENGTH_KEY, 0],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_fulltext_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
'auth_features' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('auth_features'),
|
||||
'name' => 'Auth Features',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('featureId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('active'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_featureId'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['projectId', 'featureId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_fulltext_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
'auth_plan_features' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('auth_plan_features'),
|
||||
'name' => 'Auth Plan Features',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('planId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('featureId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('enabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('currency'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 3,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('interval'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 10,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('includedUnits'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('tiersMode'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'graduated',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('tiers'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('usageCap'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripePriceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripeMeterId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('active'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_plan'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId', 'planId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_plan_feature'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['projectId', 'planId', 'featureId'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY, Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_active'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['active'],
|
||||
'lengths' => [0],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
'auth_usage_events' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('auth_usage_events'),
|
||||
'name' => 'Auth Usage Events',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('planId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('featureId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('value'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('identifier'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('timestamp'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project_user_feature_time'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId', 'userId', 'featureId', 'timestamp'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY, Database::LENGTH_KEY, 0],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_project_plan_feature_time'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectId', 'planId', 'featureId', 'timestamp'],
|
||||
'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY, Database::LENGTH_KEY, 0],
|
||||
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_identifier'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['identifier'],
|
||||
'lengths' => [256],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -364,94 +364,6 @@ return [
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('planId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripeCustomerId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('stripeSubscriptionId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('subscriptionStatus'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 20,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'none',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('subscriptionCurrentPeriodStart'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('subscriptionCurrentPeriodEnd'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('subscriptionCancelAtPeriodEnd'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('subscriptionTrialEnd'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -524,27 +436,6 @@ return [
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_planId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['planId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_stripeCustomerId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['stripeCustomerId'],
|
||||
'lengths' => [256],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_subscriptionStatus'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['subscriptionStatus'],
|
||||
'lengths' => [20],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -342,72 +342,6 @@ return [
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authSubscriptionsEnabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authStripeSecretKey'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authStripePublishableKey'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authStripeWebhookSecret'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authStripeWebhookEndpointId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 512,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authStripeCurrency'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 3,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
||||
@@ -274,19 +274,6 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'authPlans' => [
|
||||
'key' => 'authPlans',
|
||||
'name' => 'Auth Plans',
|
||||
'subtitle' => 'The Auth Plans service allows you to manage subscription plans for authentication.',
|
||||
'description' => '',
|
||||
'controller' => 'api/auth-plans.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => true,
|
||||
'icon' => '',
|
||||
],
|
||||
'migrations' => [
|
||||
'key' => 'migrations',
|
||||
'name' => 'Migrations',
|
||||
|
||||
@@ -5183,495 +5183,3 @@ App::delete('/v1/account/identities/:identityId')
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/account/subscription')
|
||||
->desc('Get account subscription')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'subscription',
|
||||
name: 'getSubscription',
|
||||
description: '/docs/references/account/get-subscription.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (Response $response, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
$planId = (string) $user->getAttribute('planId', '');
|
||||
$plan = null;
|
||||
|
||||
if ($planId !== '') {
|
||||
$candidate = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
$plan = ($candidate instanceof \Utopia\Database\Document && !$candidate->isEmpty()) ? $candidate : null;
|
||||
}
|
||||
|
||||
if (!$plan) {
|
||||
$candidate = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('isDefault', [true]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
$plan = ($candidate instanceof \Utopia\Database\Document && !$candidate->isEmpty()) ? $candidate : null;
|
||||
}
|
||||
|
||||
// Final safeguard: return default even if not explicitly marked active
|
||||
if (!$plan) {
|
||||
$candidate = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('isDefault', [true])
|
||||
]));
|
||||
$plan = ($candidate instanceof \Utopia\Database\Document && !$candidate->isEmpty()) ? $candidate : null;
|
||||
}
|
||||
|
||||
if ($plan) {
|
||||
$planId = (string) $plan->getAttribute('planId');
|
||||
}
|
||||
|
||||
$features = [];
|
||||
if ($planId !== '') {
|
||||
$assigned = Authorization::skip(fn () => $dbForPlatform->find('auth_plan_features', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$dbForPlatform
|
||||
);
|
||||
$customerId = (string) $user->getAttribute('stripeCustomerId', '');
|
||||
$periodStart = $user->getAttribute('subscriptionCurrentPeriodStart');
|
||||
$periodEnd = $user->getAttribute('subscriptionCurrentPeriodEnd');
|
||||
$startTs = $periodStart ? strtotime($periodStart) : 0;
|
||||
$endTs = $periodEnd ? strtotime($periodEnd) : time();
|
||||
foreach ($assigned as $af) {
|
||||
$featureId = (string) $af->getAttribute('featureId');
|
||||
$usage = 0;
|
||||
if ((string)$af->getAttribute('type') === 'metered') {
|
||||
$featureStartTs = $startTs;
|
||||
$featureEndTs = $endTs;
|
||||
if ($featureStartTs <= 0 || $featureEndTs <= 0) {
|
||||
$interval = (string) $af->getAttribute('interval', 'month');
|
||||
$seconds = match ($interval) {
|
||||
'day' => 86400,
|
||||
'week' => 86400 * 7,
|
||||
'year' => 86400 * 365,
|
||||
default => 86400 * 30,
|
||||
};
|
||||
$featureEndTs = time();
|
||||
$featureStartTs = $featureEndTs - $seconds;
|
||||
}
|
||||
try {
|
||||
$usage = $stripeService->getFeatureUsageTotalLocal($user->getId(), $planId, $featureId, $featureStartTs, $featureEndTs);
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
}
|
||||
$features[] = [
|
||||
'featureId' => $featureId,
|
||||
'type' => (string) $af->getAttribute('type'),
|
||||
'enabled' => (bool) $af->getAttribute('enabled', true),
|
||||
'currency' => $af->getAttribute('currency'),
|
||||
'interval' => $af->getAttribute('interval'),
|
||||
'includedUnits' => (int) $af->getAttribute('includedUnits', 0),
|
||||
'tiersMode' => $af->getAttribute('tiersMode'),
|
||||
'tiers' => (array) $af->getAttribute('tiers', []),
|
||||
'usage' => $usage,
|
||||
'usageCap' => $af->getAttribute('usageCap'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'planId' => $planId !== '' ? $planId : null,
|
||||
'planName' => $plan ? $plan->getAttribute('name') : null,
|
||||
'status' => $user->getAttribute('subscriptionStatus', 'none'),
|
||||
'currentPeriodStart' => $user->getAttribute('subscriptionCurrentPeriodStart'),
|
||||
'currentPeriodEnd' => $user->getAttribute('subscriptionCurrentPeriodEnd'),
|
||||
'cancelAtPeriodEnd' => $user->getAttribute('subscriptionCancelAtPeriodEnd', false),
|
||||
'trialEnd' => $user->getAttribute('subscriptionTrialEnd'),
|
||||
'features' => $features
|
||||
]), Response::MODEL_ANY);
|
||||
});
|
||||
|
||||
App::post('/v1/account/subscription/checkout')
|
||||
->desc('Create checkout session')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'subscription',
|
||||
name: 'createSubscriptionCheckout',
|
||||
description: '/docs/references/account/create-checkout.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('planId', '', new Text(128), 'Plan ID to subscribe to.')
|
||||
->param('successUrl', '', new URL(), 'URL to redirect on success.')
|
||||
->param('cancelUrl', '', new URL(), 'URL to redirect on cancel.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (
|
||||
string $planId,
|
||||
string $successUrl,
|
||||
string $cancelUrl,
|
||||
Response $response,
|
||||
Document $user,
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
$plan = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
|
||||
if (!$plan) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Plan not found');
|
||||
}
|
||||
|
||||
if ($plan->getAttribute('isFree')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Cannot checkout for free plan');
|
||||
}
|
||||
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$dbForPlatform
|
||||
);
|
||||
|
||||
try {
|
||||
$additionalItems = $stripeService->buildAdditionalLineItemsForPlan($planId);
|
||||
|
||||
$session = $stripeService->createCheckoutSession(
|
||||
$user,
|
||||
$plan->getAttribute('stripePriceId'),
|
||||
$successUrl,
|
||||
$cancelUrl,
|
||||
(string) $plan->getAttribute('planId'),
|
||||
$additionalItems
|
||||
);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'checkoutUrl' => $session['url']
|
||||
]), Response::MODEL_ANY);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/account/subscription/portal')
|
||||
->desc('Create customer portal session')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'subscription',
|
||||
name: 'createSubscriptionPortal',
|
||||
description: '/docs/references/account/create-portal.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('returnUrl', '', new URL(), 'URL to return to.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (
|
||||
string $returnUrl,
|
||||
Response $response,
|
||||
Document $user,
|
||||
Document $project,
|
||||
Database $dbForProject
|
||||
) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
$customerId = $user->getAttribute('stripeCustomerId');
|
||||
|
||||
if (!$customerId) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'No active subscription found');
|
||||
}
|
||||
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
|
||||
try {
|
||||
$session = $stripeService->createPortalSession($customerId, $returnUrl);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'portalUrl' => $session['url']
|
||||
]), Response::MODEL_ANY);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::put('/v1/account/subscription')
|
||||
->desc('Update subscription')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'subscription',
|
||||
name: 'updateSubscription',
|
||||
description: '/docs/references/account/update-subscription.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('planId', '', new Text(128), 'New plan ID.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (
|
||||
string $planId,
|
||||
Response $response,
|
||||
Document $user,
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
$subscriptionId = $user->getAttribute('stripeSubscriptionId');
|
||||
|
||||
if (!$subscriptionId) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'No active subscription found');
|
||||
}
|
||||
|
||||
$plan = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
|
||||
if (!$plan) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Plan not found');
|
||||
}
|
||||
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
|
||||
try {
|
||||
$subscription = $stripeService->updateSubscription(
|
||||
$subscriptionId,
|
||||
$plan->getAttribute('stripePriceId')
|
||||
);
|
||||
|
||||
$stripeService->syncSubscriptionStatus($user->getId(), $subscription);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'success' => true
|
||||
]), Response::MODEL_ANY);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::delete('/v1/account/subscription')
|
||||
->desc('Cancel subscription')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'subscription',
|
||||
name: 'cancelSubscription',
|
||||
description: '/docs/references/account/cancel-subscription.md',
|
||||
auth: [AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('atPeriodEnd', true, new Boolean(), 'Cancel at period end.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (
|
||||
bool $atPeriodEnd,
|
||||
Response $response,
|
||||
Document $user,
|
||||
Document $project,
|
||||
Database $dbForProject
|
||||
) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
$subscriptionId = $user->getAttribute('stripeSubscriptionId');
|
||||
|
||||
if (!$subscriptionId) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'No active subscription found');
|
||||
}
|
||||
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
|
||||
try {
|
||||
$subscription = $stripeService->cancelSubscription($subscriptionId, $atPeriodEnd);
|
||||
|
||||
if ($atPeriodEnd) {
|
||||
$stripeService->syncSubscriptionStatus($user->getId(), $subscription);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'success' => true,
|
||||
'cancelAtPeriodEnd' => $atPeriodEnd
|
||||
]), Response::MODEL_ANY);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/usage/ingest')
|
||||
->desc('Ingest metered usage for a feature')
|
||||
->groups(['api'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'account',
|
||||
group: 'usage',
|
||||
name: 'ingestUsage',
|
||||
description: '/docs/references/account/ingest-usage.md',
|
||||
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('planId', '', new Text(128), 'Plan ID to attribute usage to.')
|
||||
->param('featureId', '', new Text(128), 'Feature ID to attribute usage to.')
|
||||
->param('value', 1, new Integer(), 'Usage value to ingest.')
|
||||
->param('userId', '', new UID(), 'User ID to attribute usage to. Ignored for client SDK calls.', true)
|
||||
->param('timestamp', null, new Integer(true), 'Unix timestamp for the event.', true)
|
||||
->param('identifier', null, new Text(256), 'Idempotency identifier for this event.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('apiKey')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (
|
||||
string $planId,
|
||||
string $featureId,
|
||||
int $value,
|
||||
string $userId,
|
||||
?int $timestamp,
|
||||
?string $identifier,
|
||||
Response $response,
|
||||
Document $project,
|
||||
Document $user,
|
||||
?Key $apiKey,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
if (!$project->getAttribute('authSubscriptionsEnabled')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Subscriptions not enabled for this project');
|
||||
}
|
||||
|
||||
// Determine effective userId
|
||||
$effectiveUserId = '';
|
||||
if (!empty($apiKey)) {
|
||||
// Admin/API key: honor provided userId
|
||||
if (empty($userId)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'userId is required when using API key');
|
||||
}
|
||||
$effectiveUserId = $userId;
|
||||
} else {
|
||||
// Client SDK (session/JWT): ignore provided userId, use logged-in user
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$effectiveUserId = $user->getId();
|
||||
}
|
||||
|
||||
// Validate plan & feature assignment exists and is metered
|
||||
$assignment = Authorization::skip(fn () => $dbForPlatform->findOne('auth_plan_features', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('featureId', [$featureId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
if (!$assignment || $assignment->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Feature not assigned to plan');
|
||||
}
|
||||
if ((string)$assignment->getAttribute('type') !== 'metered') {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Feature is not metered');
|
||||
}
|
||||
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$dbForPlatform
|
||||
);
|
||||
|
||||
try {
|
||||
$result = $stripeService->ingestUsage($value, $effectiveUserId, $planId, $featureId, $timestamp, $identifier);
|
||||
$response->dynamic(new Document([
|
||||
'success' => true,
|
||||
'id' => $result['id'] ?? null
|
||||
]), Response::MODEL_ANY);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,7 @@
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Subscription\Exception\SubscriptionException;
|
||||
use Appwrite\Auth\Subscription\StripeService;
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Auth\Validator\StripeKey;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
@@ -49,6 +46,7 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
@@ -63,209 +61,6 @@ App::init()
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/projects/:projectId/auth/features')
|
||||
->desc('Create auth feature')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'authFeatures',
|
||||
name: 'createAuthFeature',
|
||||
description: '/docs/references/projects/create-auth-feature.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('featureId', '', new Text(128), 'Feature unique ID.')
|
||||
->param('name', '', new Text(128), 'Feature name.')
|
||||
->param('type', '', new WhiteList(['boolean', 'metered']), 'Feature type: boolean or metered')
|
||||
->param('description', '', new Text(256), 'Feature description.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (
|
||||
string $projectId,
|
||||
string $featureId,
|
||||
string $name,
|
||||
string $type,
|
||||
string $description,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$existing = $dbForPlatform->findOne('auth_features', [
|
||||
Query::equal('projectId', [$projectId]),
|
||||
Query::equal('featureId', [$featureId])
|
||||
]);
|
||||
|
||||
if ($existing instanceof Document && !$existing->isEmpty()) {
|
||||
throw new Exception(Exception::RESOURCE_ALREADY_EXISTS, 'Feature already exists');
|
||||
}
|
||||
|
||||
$doc = $dbForPlatform->createDocument('auth_features', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $projectId,
|
||||
'featureId' => $featureId,
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'description' => $description,
|
||||
'active' => true,
|
||||
'search' => implode(' ', [$featureId, $name, $description])
|
||||
]));
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($doc, Response::MODEL_ANY);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/auth/features')
|
||||
->desc('List auth features')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'authFeatures',
|
||||
name: 'listAuthFeatures',
|
||||
description: '/docs/references/projects/list-auth-features.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DOCUMENT_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('queries', [], new ArrayList(new Text(256)), 'Array of query strings.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, array $queries, Response $response, Database $dbForPlatform) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
$queries[] = Query::equal('projectId', [$projectId]);
|
||||
$queries[] = Query::equal('active', [true]);
|
||||
$docs = $dbForPlatform->find('auth_features', $queries);
|
||||
$response->dynamic(new Document([
|
||||
'total' => count($docs),
|
||||
'documents' => $docs
|
||||
]), Response::MODEL_DOCUMENT_LIST);
|
||||
});
|
||||
|
||||
App::put('/v1/projects/:projectId/auth/features/:featureId')
|
||||
->desc('Update auth feature')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'authFeatures',
|
||||
name: 'updateAuthFeature',
|
||||
description: '/docs/references/projects/update-auth-feature.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_ANY,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('featureId', '', new Text(128), 'Feature unique ID.')
|
||||
->param('name', null, new Text(128), 'Feature name.', true)
|
||||
->param('type', null, new WhiteList(['boolean', 'metered'], true), 'Feature type.', true)
|
||||
->param('description', null, new Text(256), 'Feature description.', true)
|
||||
->param('active', null, new Boolean(), 'Active state.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (
|
||||
string $projectId,
|
||||
string $featureId,
|
||||
?string $name,
|
||||
?string $type,
|
||||
?string $description,
|
||||
?bool $active,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
$doc = $dbForPlatform->findOne('auth_features', [
|
||||
Query::equal('projectId', [$projectId]),
|
||||
Query::equal('featureId', [$featureId])
|
||||
]);
|
||||
if (!$doc) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Feature not found');
|
||||
}
|
||||
if ($name !== null) {
|
||||
$doc->setAttribute('name', $name);
|
||||
}
|
||||
if ($type !== null) {
|
||||
$doc->setAttribute('type', $type);
|
||||
}
|
||||
if ($description !== null) {
|
||||
$doc->setAttribute('description', $description);
|
||||
}
|
||||
if ($active !== null) {
|
||||
$doc->setAttribute('active', $active);
|
||||
}
|
||||
$doc->setAttribute('search', implode(' ', [
|
||||
$doc->getAttribute('featureId'),
|
||||
$doc->getAttribute('name'),
|
||||
$doc->getAttribute('description')
|
||||
]));
|
||||
$doc = $dbForPlatform->updateDocument('auth_features', $doc->getId(), $doc);
|
||||
$response->dynamic($doc, Response::MODEL_ANY);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/auth/features/:featureId')
|
||||
->desc('Delete auth feature')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'authFeatures',
|
||||
name: 'deleteAuthFeature',
|
||||
description: '/docs/references/projects/delete-auth-feature.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('featureId', '', new Text(128), 'Feature unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $featureId, Response $response, Database $dbForPlatform) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
$doc = $dbForPlatform->findOne('auth_features', [
|
||||
Query::equal('projectId', [$projectId]),
|
||||
Query::equal('featureId', [$featureId])
|
||||
]);
|
||||
if (!$doc) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Feature not found');
|
||||
}
|
||||
$dbForPlatform->deleteDocument('auth_features', $doc->getId());
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/projects')
|
||||
->desc('Create project')
|
||||
->groups(['api', 'projects'])
|
||||
@@ -527,8 +322,7 @@ App::get('/v1/projects/:projectId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform, Database $dbForProject) {
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -885,9 +679,9 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->param('appId', null, new Nullable(new Text(256)), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new Nullable(new text(512)), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForPlatform) {
|
||||
@@ -1441,10 +1235,10 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->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('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform, Database $dbForProject) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1459,7 +1253,7 @@ App::get('/v1/projects/:projectId/webhooks')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
'total' => count($webhooks),
|
||||
'total' => $includeTotal ? count($webhooks) : 0,
|
||||
]), Response::MODEL_WEBHOOK_LIST);
|
||||
});
|
||||
|
||||
@@ -1683,8 +1477,8 @@ App::post('/v1/projects/:projectId/keys')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
@@ -1739,9 +1533,10 @@ App::get('/v1/projects/:projectId/keys')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->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('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1756,7 +1551,7 @@ App::get('/v1/projects/:projectId/keys')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
'total' => count($keys),
|
||||
'total' => $includeTotal ? count($keys) : 0,
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
@@ -1821,8 +1616,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
@@ -1964,7 +1759,28 @@ App::post('/v1/projects/:projectId/platforms')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', null, new WhiteList([Platform::TYPE_WEB, Platform::TYPE_FLUTTER_WEB, Platform::TYPE_FLUTTER_IOS, Platform::TYPE_FLUTTER_ANDROID, Platform::TYPE_FLUTTER_LINUX, Platform::TYPE_FLUTTER_MACOS, Platform::TYPE_FLUTTER_WINDOWS, Platform::TYPE_APPLE_IOS, Platform::TYPE_APPLE_MACOS, Platform::TYPE_APPLE_WATCHOS, Platform::TYPE_APPLE_TVOS, Platform::TYPE_ANDROID, Platform::TYPE_UNITY, Platform::TYPE_REACT_NATIVE_IOS, Platform::TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
|
||||
->param(
|
||||
'type',
|
||||
null,
|
||||
new WhiteList([
|
||||
Platform::TYPE_WEB,
|
||||
Platform::TYPE_FLUTTER_WEB,
|
||||
Platform::TYPE_FLUTTER_IOS,
|
||||
Platform::TYPE_FLUTTER_ANDROID,
|
||||
Platform::TYPE_FLUTTER_LINUX,
|
||||
Platform::TYPE_FLUTTER_MACOS,
|
||||
Platform::TYPE_FLUTTER_WINDOWS,
|
||||
Platform::TYPE_APPLE_IOS,
|
||||
Platform::TYPE_APPLE_MACOS,
|
||||
Platform::TYPE_APPLE_WATCHOS,
|
||||
Platform::TYPE_APPLE_TVOS,
|
||||
Platform::TYPE_ANDROID,
|
||||
Platform::TYPE_UNITY,
|
||||
Platform::TYPE_REACT_NATIVE_IOS,
|
||||
Platform::TYPE_REACT_NATIVE_ANDROID,
|
||||
], true),
|
||||
'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.'
|
||||
)
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
@@ -2021,9 +1837,10 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->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('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -2038,7 +1855,7 @@ App::get('/v1/projects/:projectId/platforms')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
'total' => count($platforms),
|
||||
'total' => $includeTotal ? count($platforms) : 0,
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
@@ -2845,187 +2662,4 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::put('/v1/projects/:projectId/auth/subscriptions')
|
||||
->desc('Configure auth subscriptions')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'auth',
|
||||
name: 'configureSubscriptions',
|
||||
description: '/docs/references/projects/configure-subscriptions.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('stripeSecretKey', '', new StripeKey(), 'Stripe secret key.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $projectId, string $stripeSecretKey, Response $response, Database $dbForPlatform, Database $dbForProject) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$stripeService = new StripeService($stripeSecretKey, $dbForProject, $project);
|
||||
$config = $stripeService->initializeAccount();
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('authSubscriptionsEnabled', true)
|
||||
->setAttribute('authStripeSecretKey', $stripeSecretKey)
|
||||
->setAttribute('authStripePublishableKey', $config['publishableKey'])
|
||||
->setAttribute('authStripeWebhookSecret', $config['webhookSecret'])
|
||||
->setAttribute('authStripeWebhookEndpointId', $config['webhookEndpointId'])
|
||||
->setAttribute('authStripeCurrency', $config['currency']));
|
||||
|
||||
// Ensure a default free plan exists
|
||||
$existingDefault = $dbForPlatform->findOne('auth_plans', [
|
||||
\Utopia\Database\Query::equal('projectId', [$projectId]),
|
||||
\Utopia\Database\Query::equal('isDefault', [true])
|
||||
]);
|
||||
if (!($existingDefault instanceof \Utopia\Database\Document) || $existingDefault->isEmpty()) {
|
||||
$dbForPlatform->createDocument('auth_plans', new \Utopia\Database\Document([
|
||||
'$id' => \Utopia\Database\Helpers\ID::unique(),
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $projectId,
|
||||
'planId' => 'free',
|
||||
'name' => 'Free',
|
||||
'description' => 'Default free plan',
|
||||
'stripeProductId' => null,
|
||||
'stripePriceId' => null,
|
||||
'price' => 0,
|
||||
'currency' => $config['currency'],
|
||||
'interval' => null,
|
||||
'features' => [],
|
||||
'isDefault' => true,
|
||||
'isFree' => true,
|
||||
'maxUsers' => null,
|
||||
'active' => true,
|
||||
'search' => 'free Default free plan'
|
||||
]));
|
||||
}
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
} catch (SubscriptionException $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/auth/subscriptions')
|
||||
->desc('Get auth subscription configuration')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'auth',
|
||||
name: 'getSubscriptionsConfig',
|
||||
description: '/docs/references/projects/get-subscriptions-config.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'enabled' => $project->getAttribute('authSubscriptionsEnabled', false),
|
||||
'publishableKey' => $project->getAttribute('authStripePublishableKey'),
|
||||
'currency' => $project->getAttribute('authStripeCurrency'),
|
||||
]), Response::MODEL_ANY);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/auth/subscriptions')
|
||||
->desc('Remove auth subscription configuration')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'auth',
|
||||
name: 'removeSubscriptionsConfig',
|
||||
description: '/docs/references/projects/remove-subscriptions-config.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform, Database $dbForProject) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($project->getAttribute('authSubscriptionsEnabled')) {
|
||||
try {
|
||||
$stripe = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$dbForPlatform
|
||||
);
|
||||
$endpointId = $project->getAttribute('authStripeWebhookEndpointId');
|
||||
if (!empty($endpointId)) {
|
||||
$stripe->deleteWebhookEndpoint($endpointId);
|
||||
}
|
||||
$plans = $dbForPlatform->find('auth_plans', [
|
||||
\Utopia\Database\Query::equal('projectId', [$projectId])
|
||||
]);
|
||||
foreach ($plans as $plan) {
|
||||
$priceId = $plan->getAttribute('stripePriceId');
|
||||
$productId = $plan->getAttribute('stripeProductId');
|
||||
try {
|
||||
if ($priceId) {
|
||||
$stripe->deactivatePrice($priceId);
|
||||
}
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
try {
|
||||
if ($productId) {
|
||||
$stripe->deactivateProduct($productId);
|
||||
}
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
$dbForPlatform->deleteDocument('auth_plans', $plan->getId());
|
||||
}
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('authSubscriptionsEnabled', false)
|
||||
->setAttribute('authStripeSecretKey', null)
|
||||
->setAttribute('authStripePublishableKey', null)
|
||||
->setAttribute('authStripeWebhookSecret', null)
|
||||
->setAttribute('authStripeWebhookEndpointId', null)
|
||||
->setAttribute('authStripeCurrency', null));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
});
|
||||
+58
-247
@@ -16,7 +16,7 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Email as EmailValidator;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -49,12 +49,14 @@ use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -97,6 +99,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = new Document([
|
||||
'$id' => $userId,
|
||||
@@ -124,6 +132,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $phone, $name]),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
if ($hash === 'plaintext') {
|
||||
@@ -208,8 +221,8 @@ App::post('/v1/users')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('email', null, new Nullable(new EmailValidator()), 'User email.', true)
|
||||
->param('phone', null, new Nullable(new Phone()), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -243,7 +256,7 @@ App::post('/v1/users/bcrypt')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -278,7 +291,7 @@ App::post('/v1/users/md5')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using MD5.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -313,7 +326,7 @@ App::post('/v1/users/argon2')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Argon2.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -348,7 +361,7 @@ App::post('/v1/users/sha')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using SHA.')
|
||||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
@@ -390,7 +403,7 @@ App::post('/v1/users/phpass')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using PHPass.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -425,7 +438,7 @@ App::post('/v1/users/scrypt')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt.')
|
||||
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
|
||||
->param('passwordCpu', 8, new Integer(), 'Optional CPU cost used to hash password.')
|
||||
@@ -473,7 +486,7 @@ App::post('/v1/users/scrypt-modified')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
|
||||
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
|
||||
->param('passwordSaltSeparator', '', new Text(128), 'Salt separator used to hash password.')
|
||||
@@ -527,7 +540,7 @@ App::post('/v1/users/:userId/targets')
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
$validator = new EmailValidator();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
@@ -605,11 +618,10 @@ App::get('/v1/users')
|
||||
))
|
||||
->param('queries', [], new Users(), '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(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', 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')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
@@ -649,10 +661,10 @@ App::get('/v1/users')
|
||||
$users = [];
|
||||
$total = 0;
|
||||
|
||||
$dbForProject->skipFilters(function () use ($dbForProject, $queries, &$users, &$total) {
|
||||
$dbForProject->skipFilters(function () use ($dbForProject, $queries, $includeTotal, &$users, &$total) {
|
||||
try {
|
||||
$users = $dbForProject->find('users', $queries);
|
||||
$total = $dbForProject->count('users', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('users', $queries, 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.");
|
||||
} catch (QueryException $e) {
|
||||
@@ -660,49 +672,6 @@ App::get('/v1/users')
|
||||
}
|
||||
}, ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships']);
|
||||
|
||||
$planIds = [];
|
||||
foreach ($users as $u) {
|
||||
$pid = (string) $u->getAttribute('planId', '');
|
||||
if ($pid !== '') {
|
||||
$planIds[$pid] = true;
|
||||
}
|
||||
}
|
||||
$planMap = [];
|
||||
if (!empty($planIds)) {
|
||||
$plans = $dbForPlatform->find('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', array_keys($planIds)),
|
||||
Query::equal('active', [true])
|
||||
]);
|
||||
foreach ($plans as $p) {
|
||||
$planMap[(string) $p->getAttribute('planId')] = $p;
|
||||
}
|
||||
}
|
||||
$defaultPlan = $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('isDefault', [true]),
|
||||
Query::equal('active', [true])
|
||||
]);
|
||||
foreach ($users as $u) {
|
||||
$pid = (string) $u->getAttribute('planId', '');
|
||||
$p = null;
|
||||
if ($pid !== '' && isset($planMap[$pid])) {
|
||||
$p = $planMap[$pid];
|
||||
} elseif ($defaultPlan) {
|
||||
$p = $defaultPlan;
|
||||
}
|
||||
if ($p) {
|
||||
$u->setAttribute('plan', [
|
||||
'id' => $p->getAttribute('planId'),
|
||||
'name' => $p->getAttribute('name'),
|
||||
'price' => $p->getAttribute('price'),
|
||||
'currency' => $p->getAttribute('currency'),
|
||||
'interval' => $p->getAttribute('interval'),
|
||||
'isFree' => $p->getAttribute('isFree', false)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'users' => $users,
|
||||
'total' => $total,
|
||||
@@ -729,9 +698,7 @@ App::get('/v1/users/:userId')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project) {
|
||||
->action(function (string $userId, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -739,177 +706,6 @@ App::get('/v1/users/:userId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$pid = (string) $user->getAttribute('planId', '');
|
||||
$plan = null;
|
||||
if ($pid !== '') {
|
||||
$candidate = $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$pid]),
|
||||
Query::equal('active', [true])
|
||||
]);
|
||||
$plan = ($candidate instanceof \Utopia\Database\Document && !$candidate->isEmpty()) ? $candidate : null;
|
||||
} else {
|
||||
$candidate = $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('isDefault', [true]),
|
||||
Query::equal('active', [true])
|
||||
]);
|
||||
$plan = ($candidate instanceof \Utopia\Database\Document && !$candidate->isEmpty()) ? $candidate : null;
|
||||
}
|
||||
if ($plan instanceof \Utopia\Database\Document && !$plan->isEmpty()) {
|
||||
$features = [];
|
||||
$resolvedPlanId = (string) $plan->getAttribute('planId');
|
||||
$assigned = [];
|
||||
if ($resolvedPlanId !== '') {
|
||||
$assigned = Authorization::skip(fn () => $dbForPlatform->find('auth_plan_features', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$resolvedPlanId]),
|
||||
Query::equal('active', [true])
|
||||
]));
|
||||
}
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$dbForPlatform
|
||||
);
|
||||
$customerId = (string) $user->getAttribute('stripeCustomerId', '');
|
||||
$periodStart = $user->getAttribute('subscriptionCurrentPeriodStart');
|
||||
$periodEnd = $user->getAttribute('subscriptionCurrentPeriodEnd');
|
||||
$startTs = $periodStart ? strtotime($periodStart) : 0;
|
||||
$endTs = $periodEnd ? strtotime($periodEnd) : time();
|
||||
foreach ($assigned as $af) {
|
||||
$featureId = (string) $af->getAttribute('featureId');
|
||||
$usage = 0;
|
||||
if ((string)$af->getAttribute('type') === 'metered') {
|
||||
try {
|
||||
$usage = $stripeService->getFeatureUsageTotalLocal($user->getId(), $resolvedPlanId, $featureId, $startTs, $endTs);
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
}
|
||||
$features[] = [
|
||||
'featureId' => (string) $af->getAttribute('featureId'),
|
||||
'type' => (string) $af->getAttribute('type'),
|
||||
'enabled' => (bool) $af->getAttribute('enabled', true),
|
||||
'currency' => $af->getAttribute('currency'),
|
||||
'interval' => $af->getAttribute('interval'),
|
||||
'includedUnits' => (int) $af->getAttribute('includedUnits', 0),
|
||||
'tiersMode' => $af->getAttribute('tiersMode'),
|
||||
'tiers' => (array) $af->getAttribute('tiers', []),
|
||||
'usage' => $usage,
|
||||
'usageCap' => $af->getAttribute('usageCap'),
|
||||
];
|
||||
}
|
||||
$user->setAttribute('plan', [
|
||||
'id' => $plan->getAttribute('planId'),
|
||||
'name' => $plan->getAttribute('name'),
|
||||
'price' => $plan->getAttribute('price'),
|
||||
'currency' => $plan->getAttribute('currency'),
|
||||
'interval' => $plan->getAttribute('interval'),
|
||||
'isFree' => $plan->getAttribute('isFree', false),
|
||||
'features' => $features
|
||||
]);
|
||||
}
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/plan')
|
||||
->desc('Assign plan to user (admin)')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'users',
|
||||
group: 'users',
|
||||
name: 'assignPlan',
|
||||
description: '/docs/references/users/assign-plan.md',
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USER,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('planId', '', new Text(128), 'Plan ID to assign.')
|
||||
->param('complimentary', true, new Boolean(), 'If true, create a complimentary Stripe subscription for one billing interval.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $userId, string $planId, bool $complimentary, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$plan = $dbForPlatform->findOne('auth_plans', [
|
||||
Query::equal('projectId', [$project->getId()]),
|
||||
Query::equal('planId', [$planId]),
|
||||
Query::equal('active', [true])
|
||||
]);
|
||||
if (!$plan) {
|
||||
throw new Exception(Exception::GENERAL_NOT_FOUND, 'Plan not found');
|
||||
}
|
||||
|
||||
$user->setAttribute('planId', $planId);
|
||||
$now = time();
|
||||
$interval = (string) $plan->getAttribute('interval', 'month');
|
||||
$seconds = match ($interval) {
|
||||
'day' => 86400,
|
||||
'week' => 86400 * 7,
|
||||
'year' => 86400 * 365,
|
||||
default => 86400 * 30,
|
||||
};
|
||||
$user->setAttribute('subscriptionStatus', 'active');
|
||||
$user->setAttribute('subscriptionCurrentPeriodStart', DateTime::format(new \DateTime('@' . $now)));
|
||||
$user->setAttribute('subscriptionCurrentPeriodEnd', DateTime::format(new \DateTime('@' . ($now + $seconds))));
|
||||
$user->setAttribute('subscriptionCancelAtPeriodEnd', true);
|
||||
$user->setAttribute('subscriptionTrialEnd', DateTime::format(new \DateTime('@' . ($now + $seconds))));
|
||||
|
||||
if ($plan->getAttribute('isFree', false) && $project->getAttribute('authSubscriptionsEnabled')) {
|
||||
$stripe = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
$subscriptionId = (string) $user->getAttribute('stripeSubscriptionId', '');
|
||||
if ($subscriptionId === '') {
|
||||
$customerId = (string) $user->getAttribute('stripeCustomerId', '');
|
||||
if ($customerId !== '') {
|
||||
try {
|
||||
$found = $stripe->findActiveSubscriptionId($customerId);
|
||||
if (!empty($found)) {
|
||||
$subscriptionId = (string) $found;
|
||||
}
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($subscriptionId !== '') {
|
||||
try {
|
||||
$stripe->cancelSubscription($subscriptionId, false);
|
||||
} catch (\Throwable $_) {
|
||||
}
|
||||
$user->setAttribute('stripeSubscriptionId', null);
|
||||
}
|
||||
} elseif ($complimentary && !$plan->getAttribute('isFree', false) && $project->getAttribute('authSubscriptionsEnabled')) {
|
||||
$stripe = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
$customerId = (string) $user->getAttribute('stripeCustomerId', '');
|
||||
if ($customerId === '') {
|
||||
$customerId = $stripe->ensureCustomer($user);
|
||||
$user->setAttribute('stripeCustomerId', $customerId);
|
||||
}
|
||||
$stripe->createComplimentarySubscription($customerId, (string) $plan->getAttribute('stripePriceId'), $interval, $user->getId());
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $userId, $user);
|
||||
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
@@ -1002,10 +798,11 @@ App::get('/v1/users/:userId/sessions')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->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')
|
||||
->inject('locale')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject, Locale $locale) {
|
||||
->action(function (string $userId, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -1027,7 +824,7 @@ App::get('/v1/users/:userId/sessions')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'sessions' => $sessions,
|
||||
'total' => count($sessions),
|
||||
'total' => $includeTotal ? count($sessions) : 0,
|
||||
]), Response::MODEL_SESSION_LIST);
|
||||
});
|
||||
|
||||
@@ -1051,9 +848,10 @@ App::get('/v1/users/:userId/memberships')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->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)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', 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')
|
||||
->action(function (string $userId, array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
->action(function (string $userId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -1087,7 +885,7 @@ App::get('/v1/users/:userId/memberships')
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'memberships' => $memberships,
|
||||
'total' => count($memberships),
|
||||
'total' => $includeTotal ? count($memberships) : 0,
|
||||
]), Response::MODEL_MEMBERSHIP_LIST);
|
||||
});
|
||||
|
||||
@@ -1110,11 +908,12 @@ App::get('/v1/users/:userId/logs')
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->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')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -1183,7 +982,7 @@ App::get('/v1/users/:userId/logs')
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByUser($user->getSequence(), $queries),
|
||||
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
|
||||
'logs' => $output,
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
@@ -1207,9 +1006,10 @@ App::get('/v1/users/:userId/targets')
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('queries', [], new Targets(), '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(', ', Targets::ALLOWED_ATTRIBUTES), 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')
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject) {
|
||||
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
@@ -1249,7 +1049,7 @@ App::get('/v1/users/:userId/targets')
|
||||
}
|
||||
try {
|
||||
$targets = $dbForProject->find('targets', $queries);
|
||||
$total = $dbForProject->count('targets', $queries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('targets', $queries, 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.");
|
||||
}
|
||||
@@ -1278,9 +1078,10 @@ App::get('/v1/users/identities')
|
||||
))
|
||||
->param('queries', [], new Identities(), '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(', ', Identities::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', 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')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
@@ -1317,10 +1118,9 @@ App::get('/v1/users/identities')
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$identities = $dbForProject->find('identities', $queries);
|
||||
$total = $dbForProject->count('identities', $filterQueries, APP_LIMIT_COUNT);
|
||||
$total = $includeTotal ? $dbForProject->count('identities', $queries, 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.");
|
||||
}
|
||||
@@ -1615,7 +1415,7 @@ App::patch('/v1/users/:userId/email')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('email', '', new Email(allowEmpty: true), 'User email.')
|
||||
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
@@ -1650,9 +1450,20 @@ App::patch('/v1/users/:userId/email')
|
||||
|
||||
$oldEmail = $user->getAttribute('email');
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
|
||||
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
|
||||
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
|
||||
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
|
||||
->setAttribute('emailIsFree', $emailCanonical?->isFree())
|
||||
;
|
||||
|
||||
try {
|
||||
@@ -1913,7 +1724,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
$validator = new EmailValidator();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
@@ -2898,4 +2709,4 @@ App::get('/v1/users/usage')
|
||||
'users' => $usage[$metrics[0]]['data'],
|
||||
'sessions' => $usage[$metrics[1]]['data'],
|
||||
]), Response::MODEL_USAGE_USERS);
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,6 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Locale\Locale;
|
||||
@@ -192,7 +191,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$resourceId = $rule->getAttribute('deploymentResourceId', '');
|
||||
$type = ($resourceType === 'site') ? 'sites' : 'functions';
|
||||
$exception = new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, view: $errorView);
|
||||
$exception->addCTA('View deployments', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $project->getId() . '/' . $type . '/' . $resourceType . '-' . $resourceId);
|
||||
$exception->addCTA('View deployments', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $projectId . '/' . $type . '/' . $resourceType . '-' . $resourceId);
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
@@ -1640,61 +1639,4 @@ if (!empty(Method::getErrors())) {
|
||||
// Modules
|
||||
|
||||
$platform = new Appwrite();
|
||||
$platform->init(Service::TYPE_HTTP);
|
||||
|
||||
App::post('/v1/webhooks/stripe/auth/:projectId')
|
||||
->desc('Handle Stripe webhook for auth subscriptions')
|
||||
->groups(['webhooks'])
|
||||
->label('scope', 'public')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->action(function (string $projectId, $request, $response, $dbForPlatform, callable $getProjectDB) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signature = $request->getHeader('stripe-signature') ?: $request->getHeader('Stripe-Signature') ?: ($_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '');
|
||||
|
||||
if (empty($signature)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_BAD_REQUEST, 'Missing Stripe signature');
|
||||
}
|
||||
|
||||
$event = json_decode($payload, true);
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$webhookSecret = $project->getAttribute('authStripeWebhookSecret');
|
||||
if (empty($webhookSecret)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_BAD_REQUEST, 'Webhook not configured');
|
||||
}
|
||||
|
||||
$tmpService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project
|
||||
);
|
||||
if (!$tmpService->verifyWebhookSignature($payload, $signature, $webhookSecret)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Invalid webhook signature');
|
||||
}
|
||||
|
||||
$platformDb = $dbForPlatform;
|
||||
$stripeService = new \Appwrite\Auth\Subscription\StripeService(
|
||||
$project->getAttribute('authStripeSecretKey'),
|
||||
$dbForProject,
|
||||
$project,
|
||||
$platformDb
|
||||
);
|
||||
|
||||
|
||||
|
||||
try {
|
||||
$stripeService->handleWebhook($event ?? []);
|
||||
$response->json(['success' => true]);
|
||||
} catch (\Appwrite\Auth\Subscription\Exception\SubscriptionException $e) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||
}
|
||||
});
|
||||
$platform->init(Service::TYPE_HTTP);
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Subscription\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SubscriptionException extends Exception
|
||||
{
|
||||
public function __construct(string $message = '', int $code = 0, Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class PlanData extends Validator
|
||||
{
|
||||
/**
|
||||
* Get Description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Value must be a valid plan data structure';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($value['name']) || empty($value['name'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($value['planId']) || empty($value['planId'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $value['planId'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($value['price']) && (!is_int($value['price']) || $value['price'] < 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($value['currency']) && !preg_match('/^[a-z]{3}$/', $value['currency'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($value['interval']) && !in_array($value['interval'], ['month', 'year', 'week', 'day'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($value['maxUsers']) && (!is_int($value['maxUsers']) || $value['maxUsers'] < 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($value['features']) && !is_array($value['features'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class StripeKey extends Validator
|
||||
{
|
||||
/**
|
||||
* Get Description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Value must be a valid Stripe API key';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/^sk_(test|live)_[0-9a-zA-Z]{24,}$/', $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preg_match('/^rk_(test|live)_[0-9a-zA-Z]{24,}$/', $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class SubscriptionStatus extends Validator
|
||||
{
|
||||
private const VALID_STATUSES = [
|
||||
'none',
|
||||
'active',
|
||||
'canceled',
|
||||
'incomplete',
|
||||
'incomplete_expired',
|
||||
'past_due',
|
||||
'trialing',
|
||||
'unpaid',
|
||||
'paused'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Value must be a valid subscription status';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($value, self::VALID_STATUSES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -133,20 +133,6 @@ class User extends Model
|
||||
'array' => true,
|
||||
'example' => [],
|
||||
])
|
||||
->addRule('plan', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'User subscription plan details.',
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'example' => [
|
||||
'id' => 'basic',
|
||||
'name' => 'Basic',
|
||||
'price' => 999,
|
||||
'currency' => 'usd',
|
||||
'interval' => 'month',
|
||||
'isFree' => false
|
||||
],
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCESS / 60 / 60 . ' hours.',
|
||||
|
||||
Reference in New Issue
Block a user