Merge branch 'feat-sites' into feat-add-detection-logic

This commit is contained in:
Khushboo Verma 2025-02-18 20:19:11 +05:30
commit 34b8453666
21 changed files with 1712 additions and 943 deletions

View file

@ -5033,7 +5033,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 326,
"weight": 324,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5084,7 +5084,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 325,
"weight": 323,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5543,7 +5543,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 371,
"weight": 369,
"cookies": false,
"type": "",
"deprecated": false,
@ -5625,7 +5625,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 375,
"weight": 373,
"cookies": false,
"type": "",
"deprecated": false,

File diff suppressed because it is too large Load diff

View file

@ -8138,7 +8138,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 389,
"weight": 387,
"cookies": false,
"type": "",
"deprecated": false,
@ -8211,7 +8211,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 387,
"weight": 385,
"cookies": false,
"type": "",
"deprecated": false,
@ -8402,26 +8402,6 @@
"description": "Path to function code in the linked repo.",
"x-example": "<PROVIDER_ROOT_DIRECTORY>"
},
"templateRepository": {
"type": "string",
"description": "Repository name of the template.",
"x-example": "<TEMPLATE_REPOSITORY>"
},
"templateOwner": {
"type": "string",
"description": "The name of the owner of the template.",
"x-example": "<TEMPLATE_OWNER>"
},
"templateRootDirectory": {
"type": "string",
"description": "Path to function code in the template repo.",
"x-example": "<TEMPLATE_ROOT_DIRECTORY>"
},
"templateVersion": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"x-example": "<TEMPLATE_VERSION>"
},
"specification": {
"type": "string",
"description": "Runtime specification for the function and builds.",
@ -8461,7 +8441,7 @@
},
"x-appwrite": {
"method": "listRuntimes",
"weight": 390,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@ -8619,7 +8599,7 @@
},
"x-appwrite": {
"method": "update",
"weight": 388,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@ -8990,7 +8970,7 @@
},
"x-appwrite": {
"method": "createDeployment",
"weight": 391,
"weight": 389,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -9064,6 +9044,109 @@
}
}
},
"\/functions\/{functionId}\/deployments\/template": {
"post": {
"summary": "Create template deployment",
"operationId": "functionsCreateTemplateDeployment",
"tags": [
"functions"
],
"description": "",
"responses": {
"202": {
"description": "Deployment",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/deployment"
}
}
}
}
},
"x-appwrite": {
"method": "createTemplateDeployment",
"weight": 390,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "functions\/create-template-deployment.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.write",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "functionId",
"description": "Function ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<FUNCTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "Repository name of the template.",
"x-example": "<REPOSITORY>"
},
"owner": {
"type": "string",
"description": "The name of the owner of the template.",
"x-example": "<OWNER>"
},
"rootDirectory": {
"type": "string",
"description": "Path to function code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"x-example": "<VERSION>"
},
"activate": {
"type": "boolean",
"description": "Automatically activate the deployment when it is finished building.",
"x-example": false
}
},
"required": [
"repository",
"owner",
"rootDirectory",
"version"
]
}
}
}
}
}
},
"\/functions\/{functionId}\/deployments\/{deploymentId}": {
"get": {
"summary": "Get deployment",
@ -9819,7 +9902,7 @@
},
"secret": {
"type": "boolean",
"description": "Is secret? Secret variables can only be updated or deleted, they cannot be read.",
"description": "Secret variables can be updated or deleted, but only functions can read them during build and runtime.",
"x-example": false
}
},
@ -9855,7 +9938,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 326,
"weight": 324,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -9908,7 +9991,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 325,
"weight": 323,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -11680,7 +11763,7 @@
},
"x-appwrite": {
"method": "listMessages",
"weight": 379,
"weight": 377,
"cookies": false,
"type": "",
"deprecated": false,
@ -11756,7 +11839,7 @@
},
"x-appwrite": {
"method": "createEmail",
"weight": 376,
"weight": 374,
"cookies": false,
"type": "",
"deprecated": false,
@ -11900,7 +11983,7 @@
},
"x-appwrite": {
"method": "updateEmail",
"weight": 383,
"weight": 381,
"cookies": false,
"type": "",
"deprecated": false,
@ -12046,7 +12129,7 @@
},
"x-appwrite": {
"method": "createPush",
"weight": 378,
"weight": 376,
"cookies": false,
"type": "",
"deprecated": false,
@ -12220,7 +12303,7 @@
},
"x-appwrite": {
"method": "updatePush",
"weight": 385,
"weight": 383,
"cookies": false,
"type": "",
"deprecated": false,
@ -12398,7 +12481,7 @@
},
"x-appwrite": {
"method": "createSms",
"weight": 377,
"weight": 375,
"cookies": false,
"type": "",
"deprecated": false,
@ -12507,7 +12590,7 @@
},
"x-appwrite": {
"method": "updateSms",
"weight": 384,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -12619,7 +12702,7 @@
},
"x-appwrite": {
"method": "getMessage",
"weight": 382,
"weight": 380,
"cookies": false,
"type": "",
"deprecated": false,
@ -12672,7 +12755,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 386,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@ -12734,7 +12817,7 @@
},
"x-appwrite": {
"method": "listMessageLogs",
"weight": 380,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -12809,7 +12892,7 @@
},
"x-appwrite": {
"method": "listTargets",
"weight": 381,
"weight": 379,
"cookies": false,
"type": "",
"deprecated": false,
@ -12884,7 +12967,7 @@
},
"x-appwrite": {
"method": "listProviders",
"weight": 351,
"weight": 349,
"cookies": false,
"type": "",
"deprecated": false,
@ -12960,7 +13043,7 @@
},
"x-appwrite": {
"method": "createApnsProvider",
"weight": 350,
"weight": 348,
"cookies": false,
"type": "",
"deprecated": false,
@ -13065,7 +13148,7 @@
},
"x-appwrite": {
"method": "updateApnsProvider",
"weight": 363,
"weight": 361,
"cookies": false,
"type": "",
"deprecated": false,
@ -13173,7 +13256,7 @@
},
"x-appwrite": {
"method": "createFcmProvider",
"weight": 349,
"weight": 347,
"cookies": false,
"type": "",
"deprecated": false,
@ -13258,7 +13341,7 @@
},
"x-appwrite": {
"method": "updateFcmProvider",
"weight": 362,
"weight": 360,
"cookies": false,
"type": "",
"deprecated": false,
@ -13346,7 +13429,7 @@
},
"x-appwrite": {
"method": "createMailgunProvider",
"weight": 341,
"weight": 339,
"cookies": false,
"type": "",
"deprecated": false,
@ -13461,7 +13544,7 @@
},
"x-appwrite": {
"method": "updateMailgunProvider",
"weight": 354,
"weight": 352,
"cookies": false,
"type": "",
"deprecated": false,
@ -13579,7 +13662,7 @@
},
"x-appwrite": {
"method": "createMsg91Provider",
"weight": 344,
"weight": 342,
"cookies": false,
"type": "",
"deprecated": false,
@ -13674,7 +13757,7 @@
},
"x-appwrite": {
"method": "updateMsg91Provider",
"weight": 357,
"weight": 355,
"cookies": false,
"type": "",
"deprecated": false,
@ -13772,7 +13855,7 @@
},
"x-appwrite": {
"method": "createSendgridProvider",
"weight": 342,
"weight": 340,
"cookies": false,
"type": "",
"deprecated": false,
@ -13877,7 +13960,7 @@
},
"x-appwrite": {
"method": "updateSendgridProvider",
"weight": 355,
"weight": 353,
"cookies": false,
"type": "",
"deprecated": false,
@ -13985,7 +14068,7 @@
},
"x-appwrite": {
"method": "createSmtpProvider",
"weight": 343,
"weight": 341,
"cookies": false,
"type": "",
"deprecated": false,
@ -14128,7 +14211,7 @@
},
"x-appwrite": {
"method": "updateSmtpProvider",
"weight": 356,
"weight": 354,
"cookies": false,
"type": "",
"deprecated": false,
@ -14273,7 +14356,7 @@
},
"x-appwrite": {
"method": "createTelesignProvider",
"weight": 345,
"weight": 343,
"cookies": false,
"type": "",
"deprecated": false,
@ -14368,7 +14451,7 @@
},
"x-appwrite": {
"method": "updateTelesignProvider",
"weight": 358,
"weight": 356,
"cookies": false,
"type": "",
"deprecated": false,
@ -14466,7 +14549,7 @@
},
"x-appwrite": {
"method": "createTextmagicProvider",
"weight": 346,
"weight": 344,
"cookies": false,
"type": "",
"deprecated": false,
@ -14561,7 +14644,7 @@
},
"x-appwrite": {
"method": "updateTextmagicProvider",
"weight": 359,
"weight": 357,
"cookies": false,
"type": "",
"deprecated": false,
@ -14659,7 +14742,7 @@
},
"x-appwrite": {
"method": "createTwilioProvider",
"weight": 347,
"weight": 345,
"cookies": false,
"type": "",
"deprecated": false,
@ -14754,7 +14837,7 @@
},
"x-appwrite": {
"method": "updateTwilioProvider",
"weight": 360,
"weight": 358,
"cookies": false,
"type": "",
"deprecated": false,
@ -14852,7 +14935,7 @@
},
"x-appwrite": {
"method": "createVonageProvider",
"weight": 348,
"weight": 346,
"cookies": false,
"type": "",
"deprecated": false,
@ -14947,7 +15030,7 @@
},
"x-appwrite": {
"method": "updateVonageProvider",
"weight": 361,
"weight": 359,
"cookies": false,
"type": "",
"deprecated": false,
@ -15045,7 +15128,7 @@
},
"x-appwrite": {
"method": "getProvider",
"weight": 353,
"weight": 351,
"cookies": false,
"type": "",
"deprecated": false,
@ -15098,7 +15181,7 @@
},
"x-appwrite": {
"method": "deleteProvider",
"weight": 364,
"weight": 362,
"cookies": false,
"type": "",
"deprecated": false,
@ -15160,7 +15243,7 @@
},
"x-appwrite": {
"method": "listProviderLogs",
"weight": 352,
"weight": 350,
"cookies": false,
"type": "",
"deprecated": false,
@ -15235,7 +15318,7 @@
},
"x-appwrite": {
"method": "listSubscriberLogs",
"weight": 373,
"weight": 371,
"cookies": false,
"type": "",
"deprecated": false,
@ -15310,7 +15393,7 @@
},
"x-appwrite": {
"method": "listTopics",
"weight": 366,
"weight": 364,
"cookies": false,
"type": "",
"deprecated": false,
@ -15384,7 +15467,7 @@
},
"x-appwrite": {
"method": "createTopic",
"weight": 365,
"weight": 363,
"cookies": false,
"type": "",
"deprecated": false,
@ -15467,7 +15550,7 @@
},
"x-appwrite": {
"method": "getTopic",
"weight": 368,
"weight": 366,
"cookies": false,
"type": "",
"deprecated": false,
@ -15527,7 +15610,7 @@
},
"x-appwrite": {
"method": "updateTopic",
"weight": 369,
"weight": 367,
"cookies": false,
"type": "",
"deprecated": false,
@ -15604,7 +15687,7 @@
},
"x-appwrite": {
"method": "deleteTopic",
"weight": 370,
"weight": 368,
"cookies": false,
"type": "",
"deprecated": false,
@ -15666,7 +15749,7 @@
},
"x-appwrite": {
"method": "listTopicLogs",
"weight": 367,
"weight": 365,
"cookies": false,
"type": "",
"deprecated": false,
@ -15741,7 +15824,7 @@
},
"x-appwrite": {
"method": "listSubscribers",
"weight": 372,
"weight": 370,
"cookies": false,
"type": "",
"deprecated": false,
@ -15825,7 +15908,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 371,
"weight": 369,
"cookies": false,
"type": "",
"deprecated": false,
@ -15916,7 +15999,7 @@
},
"x-appwrite": {
"method": "getSubscriber",
"weight": 374,
"weight": 372,
"cookies": false,
"type": "",
"deprecated": false,
@ -15979,7 +16062,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 375,
"weight": 373,
"cookies": false,
"type": "",
"deprecated": false,
@ -16055,7 +16138,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 394,
"weight": 393,
"cookies": false,
"type": "",
"deprecated": false,
@ -16125,7 +16208,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 392,
"weight": 391,
"cookies": false,
"type": "",
"deprecated": false,
@ -16207,11 +16290,6 @@
"description": "Output Directory for site.",
"x-example": "<OUTPUT_DIRECTORY>"
},
"subdomain": {
"type": "string",
"description": "Unique custom sub-domain. 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.",
"x-example": "<SUBDOMAIN>"
},
"buildRuntime": {
"type": "string",
"description": "Runtime to use during build step.",
@ -16318,26 +16396,6 @@
"description": "Path to site code in the linked repo.",
"x-example": "<PROVIDER_ROOT_DIRECTORY>"
},
"templateRepository": {
"type": "string",
"description": "Repository name of the template.",
"x-example": "<TEMPLATE_REPOSITORY>"
},
"templateOwner": {
"type": "string",
"description": "The name of the owner of the template.",
"x-example": "<TEMPLATE_OWNER>"
},
"templateRootDirectory": {
"type": "string",
"description": "Path to site code in the template repo.",
"x-example": "<TEMPLATE_ROOT_DIRECTORY>"
},
"templateVersion": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"x-example": "<TEMPLATE_VERSION>"
},
"specification": {
"type": "string",
"description": "Framework specification for the site and builds.",
@ -16378,7 +16436,7 @@
},
"x-appwrite": {
"method": "listFrameworks",
"weight": 397,
"weight": 396,
"cookies": false,
"type": "",
"deprecated": false,
@ -16427,7 +16485,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 393,
"weight": 392,
"cookies": false,
"type": "",
"deprecated": false,
@ -16486,7 +16544,7 @@
},
"x-appwrite": {
"method": "update",
"weight": 395,
"weight": 394,
"cookies": false,
"type": "",
"deprecated": false,
@ -16710,7 +16768,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 396,
"weight": 395,
"cookies": false,
"type": "",
"deprecated": false,
@ -16854,7 +16912,7 @@
},
"x-appwrite": {
"method": "createDeployment",
"weight": 398,
"weight": 397,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -16933,6 +16991,109 @@
}
}
},
"\/sites\/{siteId}\/deployments\/template": {
"post": {
"summary": "Create deployment",
"operationId": "sitesCreateTemplateDeployment",
"tags": [
"sites"
],
"description": "",
"responses": {
"202": {
"description": "Deployment",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/deployment"
}
}
}
}
},
"x-appwrite": {
"method": "createTemplateDeployment",
"weight": 398,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "sites\/create-template-deployment.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.write",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "siteId",
"description": "Site ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<SITE_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "Repository name of the template.",
"x-example": "<REPOSITORY>"
},
"owner": {
"type": "string",
"description": "The name of the owner of the template.",
"x-example": "<OWNER>"
},
"rootDirectory": {
"type": "string",
"description": "Path to site code in the template repo.",
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"x-example": "<VERSION>"
},
"activate": {
"type": "boolean",
"description": "Automatically activate the deployment when it is finished building.",
"x-example": false
}
},
"required": [
"repository",
"owner",
"rootDirectory",
"version"
]
}
}
}
}
}
},
"\/sites\/{siteId}\/deployments\/{deploymentId}": {
"get": {
"summary": "Get deployment",
@ -17751,7 +17912,7 @@
},
"secret": {
"type": "boolean",
"description": "Is secret? Secret variables can only be updated or deleted, they cannot be read.",
"description": "Secret variables can be updated or deleted, but only sites can read them during build and runtime.",
"x-example": false
}
},
@ -17918,6 +18079,11 @@
"type": "string",
"description": "Variable value. Max length: 8192 chars.",
"x-example": "<VALUE>"
},
"secret": {
"type": "boolean",
"description": "Secret variables can be updated or deleted, but only sites can read them during build and runtime.",
"x-example": false
}
},
"required": [

View file

@ -5198,7 +5198,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 326,
"weight": 324,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5271,7 +5271,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 325,
"weight": 323,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5768,7 +5768,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 371,
"weight": 369,
"cookies": false,
"type": "",
"deprecated": false,
@ -5852,7 +5852,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 375,
"weight": 373,
"cookies": false,
"type": "",
"deprecated": false,

File diff suppressed because it is too large Load diff

View file

@ -8284,7 +8284,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 389,
"weight": 387,
"cookies": false,
"type": "",
"deprecated": false,
@ -8356,7 +8356,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 387,
"weight": 385,
"cookies": false,
"type": "",
"deprecated": false,
@ -8565,30 +8565,6 @@
"default": "",
"x-example": "<PROVIDER_ROOT_DIRECTORY>"
},
"templateRepository": {
"type": "string",
"description": "Repository name of the template.",
"default": "",
"x-example": "<TEMPLATE_REPOSITORY>"
},
"templateOwner": {
"type": "string",
"description": "The name of the owner of the template.",
"default": "",
"x-example": "<TEMPLATE_OWNER>"
},
"templateRootDirectory": {
"type": "string",
"description": "Path to function code in the template repo.",
"default": "",
"x-example": "<TEMPLATE_ROOT_DIRECTORY>"
},
"templateVersion": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"default": "",
"x-example": "<TEMPLATE_VERSION>"
},
"specification": {
"type": "string",
"description": "Runtime specification for the function and builds.",
@ -8630,7 +8606,7 @@
},
"x-appwrite": {
"method": "listRuntimes",
"weight": 390,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@ -8792,7 +8768,7 @@
},
"x-appwrite": {
"method": "update",
"weight": 388,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@ -9177,7 +9153,7 @@
},
"x-appwrite": {
"method": "createDeployment",
"weight": 391,
"weight": 389,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -9245,6 +9221,112 @@
]
}
},
"\/functions\/{functionId}\/deployments\/template": {
"post": {
"summary": "Create template deployment",
"operationId": "functionsCreateTemplateDeployment",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"functions"
],
"description": "",
"responses": {
"202": {
"description": "Deployment",
"schema": {
"$ref": "#\/definitions\/deployment"
}
}
},
"x-appwrite": {
"method": "createTemplateDeployment",
"weight": 390,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "functions\/create-template-deployment.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/functions#listTemplates) to find the template details.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.write",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "functionId",
"description": "Function ID.",
"required": true,
"type": "string",
"x-example": "<FUNCTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "Repository name of the template.",
"default": null,
"x-example": "<REPOSITORY>"
},
"owner": {
"type": "string",
"description": "The name of the owner of the template.",
"default": null,
"x-example": "<OWNER>"
},
"rootDirectory": {
"type": "string",
"description": "Path to function code in the template repo.",
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": "string",
"description": "Version (tag) for the repo linked to the function template.",
"default": null,
"x-example": "<VERSION>"
},
"activate": {
"type": "boolean",
"description": "Automatically activate the deployment when it is finished building.",
"default": false,
"x-example": false
}
},
"required": [
"repository",
"owner",
"rootDirectory",
"version"
]
}
}
]
}
},
"\/functions\/{functionId}\/deployments\/{deploymentId}": {
"get": {
"summary": "Get deployment",
@ -10004,8 +10086,8 @@
},
"secret": {
"type": "boolean",
"description": "Is secret? Secret variables can only be updated or deleted, they cannot be read.",
"default": false,
"description": "Secret variables can be updated or deleted, but only functions can read them during build and runtime.",
"default": true,
"x-example": false
}
},
@ -10042,7 +10124,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 326,
"weight": 324,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -10117,7 +10199,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 325,
"weight": 323,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -11939,7 +12021,7 @@
},
"x-appwrite": {
"method": "listMessages",
"weight": 379,
"weight": 377,
"cookies": false,
"type": "",
"deprecated": false,
@ -12014,7 +12096,7 @@
},
"x-appwrite": {
"method": "createEmail",
"weight": 376,
"weight": 374,
"cookies": false,
"type": "",
"deprecated": false,
@ -12172,7 +12254,7 @@
},
"x-appwrite": {
"method": "updateEmail",
"weight": 383,
"weight": 381,
"cookies": false,
"type": "",
"deprecated": false,
@ -12327,7 +12409,7 @@
},
"x-appwrite": {
"method": "createPush",
"weight": 378,
"weight": 376,
"cookies": false,
"type": "",
"deprecated": false,
@ -12522,7 +12604,7 @@
},
"x-appwrite": {
"method": "updatePush",
"weight": 385,
"weight": 383,
"cookies": false,
"type": "",
"deprecated": false,
@ -12716,7 +12798,7 @@
},
"x-appwrite": {
"method": "createSms",
"weight": 377,
"weight": 375,
"cookies": false,
"type": "",
"deprecated": false,
@ -12834,7 +12916,7 @@
},
"x-appwrite": {
"method": "updateSms",
"weight": 384,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -12950,7 +13032,7 @@
},
"x-appwrite": {
"method": "getMessage",
"weight": 382,
"weight": 380,
"cookies": false,
"type": "",
"deprecated": false,
@ -13005,7 +13087,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 386,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@ -13067,7 +13149,7 @@
},
"x-appwrite": {
"method": "listMessageLogs",
"weight": 380,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -13141,7 +13223,7 @@
},
"x-appwrite": {
"method": "listTargets",
"weight": 381,
"weight": 379,
"cookies": false,
"type": "",
"deprecated": false,
@ -13215,7 +13297,7 @@
},
"x-appwrite": {
"method": "listProviders",
"weight": 351,
"weight": 349,
"cookies": false,
"type": "",
"deprecated": false,
@ -13290,7 +13372,7 @@
},
"x-appwrite": {
"method": "createApnsProvider",
"weight": 350,
"weight": 348,
"cookies": false,
"type": "",
"deprecated": false,
@ -13405,7 +13487,7 @@
},
"x-appwrite": {
"method": "updateApnsProvider",
"weight": 363,
"weight": 361,
"cookies": false,
"type": "",
"deprecated": false,
@ -13518,7 +13600,7 @@
},
"x-appwrite": {
"method": "createFcmProvider",
"weight": 349,
"weight": 347,
"cookies": false,
"type": "",
"deprecated": false,
@ -13609,7 +13691,7 @@
},
"x-appwrite": {
"method": "updateFcmProvider",
"weight": 362,
"weight": 360,
"cookies": false,
"type": "",
"deprecated": false,
@ -13698,7 +13780,7 @@
},
"x-appwrite": {
"method": "createMailgunProvider",
"weight": 341,
"weight": 339,
"cookies": false,
"type": "",
"deprecated": false,
@ -13825,7 +13907,7 @@
},
"x-appwrite": {
"method": "updateMailgunProvider",
"weight": 354,
"weight": 352,
"cookies": false,
"type": "",
"deprecated": false,
@ -13950,7 +14032,7 @@
},
"x-appwrite": {
"method": "createMsg91Provider",
"weight": 344,
"weight": 342,
"cookies": false,
"type": "",
"deprecated": false,
@ -14053,7 +14135,7 @@
},
"x-appwrite": {
"method": "updateMsg91Provider",
"weight": 357,
"weight": 355,
"cookies": false,
"type": "",
"deprecated": false,
@ -14154,7 +14236,7 @@
},
"x-appwrite": {
"method": "createSendgridProvider",
"weight": 342,
"weight": 340,
"cookies": false,
"type": "",
"deprecated": false,
@ -14269,7 +14351,7 @@
},
"x-appwrite": {
"method": "updateSendgridProvider",
"weight": 355,
"weight": 353,
"cookies": false,
"type": "",
"deprecated": false,
@ -14382,7 +14464,7 @@
},
"x-appwrite": {
"method": "createSmtpProvider",
"weight": 343,
"weight": 341,
"cookies": false,
"type": "",
"deprecated": false,
@ -14541,7 +14623,7 @@
},
"x-appwrite": {
"method": "updateSmtpProvider",
"weight": 356,
"weight": 354,
"cookies": false,
"type": "",
"deprecated": false,
@ -14697,7 +14779,7 @@
},
"x-appwrite": {
"method": "createTelesignProvider",
"weight": 345,
"weight": 343,
"cookies": false,
"type": "",
"deprecated": false,
@ -14800,7 +14882,7 @@
},
"x-appwrite": {
"method": "updateTelesignProvider",
"weight": 358,
"weight": 356,
"cookies": false,
"type": "",
"deprecated": false,
@ -14901,7 +14983,7 @@
},
"x-appwrite": {
"method": "createTextmagicProvider",
"weight": 346,
"weight": 344,
"cookies": false,
"type": "",
"deprecated": false,
@ -15004,7 +15086,7 @@
},
"x-appwrite": {
"method": "updateTextmagicProvider",
"weight": 359,
"weight": 357,
"cookies": false,
"type": "",
"deprecated": false,
@ -15105,7 +15187,7 @@
},
"x-appwrite": {
"method": "createTwilioProvider",
"weight": 347,
"weight": 345,
"cookies": false,
"type": "",
"deprecated": false,
@ -15208,7 +15290,7 @@
},
"x-appwrite": {
"method": "updateTwilioProvider",
"weight": 360,
"weight": 358,
"cookies": false,
"type": "",
"deprecated": false,
@ -15309,7 +15391,7 @@
},
"x-appwrite": {
"method": "createVonageProvider",
"weight": 348,
"weight": 346,
"cookies": false,
"type": "",
"deprecated": false,
@ -15412,7 +15494,7 @@
},
"x-appwrite": {
"method": "updateVonageProvider",
"weight": 361,
"weight": 359,
"cookies": false,
"type": "",
"deprecated": false,
@ -15513,7 +15595,7 @@
},
"x-appwrite": {
"method": "getProvider",
"weight": 353,
"weight": 351,
"cookies": false,
"type": "",
"deprecated": false,
@ -15568,7 +15650,7 @@
},
"x-appwrite": {
"method": "deleteProvider",
"weight": 364,
"weight": 362,
"cookies": false,
"type": "",
"deprecated": false,
@ -15630,7 +15712,7 @@
},
"x-appwrite": {
"method": "listProviderLogs",
"weight": 352,
"weight": 350,
"cookies": false,
"type": "",
"deprecated": false,
@ -15704,7 +15786,7 @@
},
"x-appwrite": {
"method": "listSubscriberLogs",
"weight": 373,
"weight": 371,
"cookies": false,
"type": "",
"deprecated": false,
@ -15778,7 +15860,7 @@
},
"x-appwrite": {
"method": "listTopics",
"weight": 366,
"weight": 364,
"cookies": false,
"type": "",
"deprecated": false,
@ -15851,7 +15933,7 @@
},
"x-appwrite": {
"method": "createTopic",
"weight": 365,
"weight": 363,
"cookies": false,
"type": "",
"deprecated": false,
@ -15941,7 +16023,7 @@
},
"x-appwrite": {
"method": "getTopic",
"weight": 368,
"weight": 366,
"cookies": false,
"type": "",
"deprecated": false,
@ -16001,7 +16083,7 @@
},
"x-appwrite": {
"method": "updateTopic",
"weight": 369,
"weight": 367,
"cookies": false,
"type": "",
"deprecated": false,
@ -16080,7 +16162,7 @@
},
"x-appwrite": {
"method": "deleteTopic",
"weight": 370,
"weight": 368,
"cookies": false,
"type": "",
"deprecated": false,
@ -16142,7 +16224,7 @@
},
"x-appwrite": {
"method": "listTopicLogs",
"weight": 367,
"weight": 365,
"cookies": false,
"type": "",
"deprecated": false,
@ -16216,7 +16298,7 @@
},
"x-appwrite": {
"method": "listSubscribers",
"weight": 372,
"weight": 370,
"cookies": false,
"type": "",
"deprecated": false,
@ -16297,7 +16379,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 371,
"weight": 369,
"cookies": false,
"type": "",
"deprecated": false,
@ -16388,7 +16470,7 @@
},
"x-appwrite": {
"method": "getSubscriber",
"weight": 374,
"weight": 372,
"cookies": false,
"type": "",
"deprecated": false,
@ -16451,7 +16533,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 375,
"weight": 373,
"cookies": false,
"type": "",
"deprecated": false,
@ -16525,7 +16607,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 394,
"weight": 393,
"cookies": false,
"type": "",
"deprecated": false,
@ -16597,7 +16679,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 392,
"weight": 391,
"cookies": false,
"type": "",
"deprecated": false,
@ -16688,12 +16770,6 @@
"default": "",
"x-example": "<OUTPUT_DIRECTORY>"
},
"subdomain": {
"type": "string",
"description": "Unique custom sub-domain. 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.",
"default": "",
"x-example": "<SUBDOMAIN>"
},
"buildRuntime": {
"type": "string",
"description": "Runtime to use during build step.",
@ -16808,30 +16884,6 @@
"default": "",
"x-example": "<PROVIDER_ROOT_DIRECTORY>"
},
"templateRepository": {
"type": "string",
"description": "Repository name of the template.",
"default": "",
"x-example": "<TEMPLATE_REPOSITORY>"
},
"templateOwner": {
"type": "string",
"description": "The name of the owner of the template.",
"default": "",
"x-example": "<TEMPLATE_OWNER>"
},
"templateRootDirectory": {
"type": "string",
"description": "Path to site code in the template repo.",
"default": "",
"x-example": "<TEMPLATE_ROOT_DIRECTORY>"
},
"templateVersion": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"default": "",
"x-example": "<TEMPLATE_VERSION>"
},
"specification": {
"type": "string",
"description": "Framework specification for the site and builds.",
@ -16874,7 +16926,7 @@
},
"x-appwrite": {
"method": "listFrameworks",
"weight": 397,
"weight": 396,
"cookies": false,
"type": "",
"deprecated": false,
@ -16925,7 +16977,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 393,
"weight": 392,
"cookies": false,
"type": "",
"deprecated": false,
@ -16984,7 +17036,7 @@
},
"x-appwrite": {
"method": "update",
"weight": 395,
"weight": 394,
"cookies": false,
"type": "",
"deprecated": false,
@ -17224,7 +17276,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 396,
"weight": 395,
"cookies": false,
"type": "",
"deprecated": false,
@ -17365,7 +17417,7 @@
},
"x-appwrite": {
"method": "createDeployment",
"weight": 398,
"weight": 397,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -17441,6 +17493,112 @@
]
}
},
"\/sites\/{siteId}\/deployments\/template": {
"post": {
"summary": "Create deployment",
"operationId": "sitesCreateTemplateDeployment",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"sites"
],
"description": "",
"responses": {
"202": {
"description": "Deployment",
"schema": {
"$ref": "#\/definitions\/deployment"
}
}
},
"x-appwrite": {
"method": "createTemplateDeployment",
"weight": 398,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "sites\/create-template-deployment.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterCreate a deployment based on a template.\n\nUse this endpoint with combination of [listTemplates](https:\/\/appwrite.io\/docs\/server\/sites#listTemplates) to find the template details.",
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.write",
"platforms": [
"server"
],
"packaging": false,
"auth": {
"Project": [],
"Key": []
}
},
"security": [
{
"Project": [],
"Key": []
}
],
"parameters": [
{
"name": "siteId",
"description": "Site ID.",
"required": true,
"type": "string",
"x-example": "<SITE_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "Repository name of the template.",
"default": null,
"x-example": "<REPOSITORY>"
},
"owner": {
"type": "string",
"description": "The name of the owner of the template.",
"default": null,
"x-example": "<OWNER>"
},
"rootDirectory": {
"type": "string",
"description": "Path to site code in the template repo.",
"default": null,
"x-example": "<ROOT_DIRECTORY>"
},
"version": {
"type": "string",
"description": "Version (tag) for the repo linked to the site template.",
"default": null,
"x-example": "<VERSION>"
},
"activate": {
"type": "boolean",
"description": "Automatically activate the deployment when it is finished building.",
"default": false,
"x-example": false
}
},
"required": [
"repository",
"owner",
"rootDirectory",
"version"
]
}
}
]
}
},
"\/sites\/{siteId}\/deployments\/{deploymentId}": {
"get": {
"summary": "Get deployment",
@ -18266,8 +18424,8 @@
},
"secret": {
"type": "boolean",
"description": "Is secret? Secret variables can only be updated or deleted, they cannot be read.",
"default": false,
"description": "Secret variables can be updated or deleted, but only sites can read them during build and runtime.",
"default": true,
"x-example": false
}
},
@ -18430,6 +18588,12 @@
"description": "Variable value. Max length: 8192 chars.",
"default": null,
"x-example": "<VALUE>"
},
"secret": {
"type": "boolean",
"description": "Secret variables can be updated or deleted, but only sites can read them during build and runtime.",
"default": null,
"x-example": false
}
},
"required": [

View file

@ -15,176 +15,13 @@ use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Logger\Log;
use Utopia\System\System;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
App::post('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('Create rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk', new Method(
namespace: 'proxy',
name: 'createRule',
description: '/docs/references/proxy/create-rule.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_PROXY_RULE,
)
]
))
->param('domain', null, new ValidatorDomain(), 'Domain name.')
->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"')
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true)
->inject('response')
->inject('project')
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) {
$mainDomain = System::getEnv('_APP_DOMAIN', '');
if ($domain === $mainDomain) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.');
}
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (
($functionsDomain !== '' && str_ends_with($domain, $functionsDomain)) ||
($sitesDomain !== '' && str_ends_with($domain, $sitesDomain))
) {
// TODO: Refactor later
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain or their subdomains to a specific resource. Please use a different domain.');
}
if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
}
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$document = $dbForPlatform->getDocument('rules', md5($domain));
} else {
$document = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
}
if (!$document->isEmpty()) {
if ($document->getAttribute('projectId') === $project->getId()) {
$resourceType = $document->getAttribute('resourceType');
$resourceId = $document->getAttribute('resourceId');
$message = "Domain already assigned to '{$resourceType}' service";
if (!empty($resourceId)) {
$message .= " with ID '{$resourceId}'";
}
$message .= '.';
} else {
$message = 'Domain already assigned to different project.';
}
throw new Exception(Exception::RULE_ALREADY_EXISTS, $message);
}
$resourceInternalId = '';
switch ($resourceType) {
case 'function':
if (empty($resourceId)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$function = $dbForProject->getDocument('functions', $resourceId);
if ($function->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$resourceInternalId = $function->getInternalId();
break;
case 'site':
if (empty($resourceId)) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
$site = $dbForProject->getDocument('sites', $resourceId);
if ($site->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$resourceInternalId = $site->getInternalId();
break;
}
try {
$domain = new Domain($domain);
} catch (\Throwable) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
$rule = new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain->get(),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'certificateId' => '',
]);
$status = 'created';
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
$status = 'verified';
}
if ($status === 'created') {
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if ($validator->isValid($domain->get())) {
$status = 'verifying';
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain')
]))
->trigger();
}
}
$rule->setAttribute('status', $status);
$rule = $dbForPlatform->createDocument('rules', $rule);
$queueForEvents->setParam('ruleId', $rule->getId());
$rule->setAttribute('logs', '');
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
});
App::get('/v1/proxy/rules')
->groups(['api', 'proxy'])
@ -411,34 +248,3 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
});
App::get('/v1/proxy/subdomains')
->desc('Check if subdomain is available')
->groups(['api', 'proxy'])
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
->label('sdk.method', 'checkSubdomain')
->label('sdk.description', '/docs/references/proxy/check-subdomain.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_NONE)
->param('resourceType', null, new WhiteList(['function', 'site']), 'Action definition for the rule. Possible values are "function" and "site"')
->param('subdomain', '', new Text(256), 'Subdomain name.')
->inject('response')
->inject('dbForPlatform')
->action(function (string $resourceType, string $subdomain, Response $response, Database $dbForPlatform) {
//TODO: Add tests for this endpoint
$resourceDomain = $resourceType === 'site' ? System::getEnv('_APP_DOMAIN_SITES', '') : System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$domain = $subdomain . '.' . $resourceDomain;
$document = $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]);
if ($document && !$document->isEmpty()) {
throw new Exception(Exception::RULE_ALREADY_EXISTS, 'Subdomain already assigned to different project.');
}
$response->noContent();
});

View file

@ -737,9 +737,19 @@ App::init()
$validator = new Hostname($clients);
if ($validator->isValid($origin)) {
$refDomainOrigin = $origin;
} else {
} elseif (!empty($origin)) {
// Auto-allow domains with linked rule
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin)));
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin)));
} else {
$rule = Authorization::skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$origin]),
Query::limit(1)
])
)[0] ?? new Document();
}
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getInternalId()) {
$refDomainOrigin = $origin;
}

View file

@ -1 +0,0 @@
Create a new proxy rule.

View file

@ -5,6 +5,7 @@ namespace Appwrite\Platform;
use Appwrite\Platform\Modules\Console;
use Appwrite\Platform\Modules\Core;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Proxy;
use Appwrite\Platform\Modules\Sites;
use Utopia\Platform\Platform;
@ -16,5 +17,6 @@ class Appwrite extends Platform
$this->addModule(new Functions\Module());
$this->addModule(new Sites\Module());
$this->addModule(new Console\Module());
$this->addModule(new Proxy\Module());
}
}

View file

@ -6,7 +6,6 @@ use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@ -14,7 +13,6 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Config\Config;
@ -229,72 +227,6 @@ class Create extends Base
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (!empty($functionsDomain)) {
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
$rule = Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Functions */
$ruleCreate
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
$queueForEvents->setParam('functionId', $function->getId());
$response

View file

@ -0,0 +1,179 @@
<?php
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\CNAME;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\WhiteList;
class Create extends Action
{
use HTTP;
public static function getName()
{
return 'createRule';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/proxy/rules')
->groups(['api', 'proxy'])
->desc('Create rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
->label('audits.resource', 'rule/{response.$id}')
->label('sdk', new Method(
namespace: 'proxy',
name: 'createRule',
description: <<<EOT
Create a new proxy rule.
EOT,
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_PROXY_RULE,
)
]
))
->label('abuse-limit', 10)
->label('abuse-key', 'userId:{userId}, url:{url}')
->label('abuse-time', 60)
->param('domain', null, new ValidatorDomain(), 'Domain name.')
->param('resourceType', null, new WhiteList(['api', 'function', 'site']), 'Action definition for the rule. Possible values are "api", "function" and "site"')
->param('resourceId', '', new UID(), 'ID of resource for the action type. If resourceType is "api", leave empty. If resourceType is "function", provide ID of the function.', true)
->inject('response')
->inject('project')
->inject('queueForCertificates')
->inject('queueForEvents')
->inject('dbForPlatform')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
{
$mainDomain = System::getEnv('_APP_DOMAIN', '');
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$deniedDomains = [
$mainDomain,
$sitesDomain,
$functionsDomain,
'localhost',
APP_HOSTNAME_INTERNAL,
];
if (in_array($domain, $deniedDomains, true)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
}
$resourceInternalId = '';
switch ($resourceType) {
case 'function':
case 'site':
if (empty($resourceId)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'resourceId cannot be empty for resourceType "' . $resourceType . '".');
}
$expectedDomain = ($resourceType === 'function') ? $functionsDomain : $sitesDomain;
if (!\str_ends_with($domain, $expectedDomain)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must end with ' . $expectedDomain . ' for resourceType "' . $resourceType . '".');
}
$collection = ($resourceType === 'function') ? 'functions' : 'sites';
$document = $dbForProject->getDocument($collection, $resourceId);
if ($document->isEmpty()) {
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$resourceInternalId = $document->getInternalId();
break;
case 'api':
if (\str_ends_with($domain, $functionsDomain) || \str_ends_with($domain, $sitesDomain)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain must not end with ' . $functionsDomain . ' or ' . $sitesDomain . ' for resourceType "api".');
}
break;
}
try {
$domain = new Domain($domain);
} catch (\Throwable) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
try {
$rule = new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain->get(),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => $resourceInternalId,
'certificateId' => '',
]);
} catch (\Throwable $e) {
if ($e->getCode() === Exception::DOCUMENT_ALREADY_EXISTS) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'An unexpected error occurred: ' . $e->getMessage());
}
$status = 'created';
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
$status = 'verified';
}
if ($status === 'created') {
$target = new Domain(System::getEnv('_APP_DOMAIN_TARGET', ''));
$validator = new CNAME($target->get()); // Verify Domain with DNS records
if ($validator->isValid($domain->get())) {
$status = 'verifying';
$queueForCertificates
->setDomain(new Document([
'domain' => $rule->getAttribute('domain')
]))
->trigger();
}
}
$rule->setAttribute('status', $status);
$rule = $dbForPlatform->createDocument('rules', $rule);
$queueForEvents->setParam('ruleId', $rule->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($rule, Response::MODEL_PROXY_RULE);
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Proxy;
use Appwrite\Platform\Modules\Proxy\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Appwrite\Platform\Modules\Proxy\Services;
use Appwrite\Platform\Modules\Proxy\Http\Rules\Create as CreateRule;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
// Rules
$this->addAction(CreateRule::getName(), new CreateRule());
}
}

View file

@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@ -12,7 +11,6 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
@ -20,7 +18,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
@ -71,7 +68,6 @@ class Create extends Base
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
->param('subdomain', '', new CustomId(), 'Unique custom sub-domain. 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.', true)
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.')
->param('adapter', '', new Text(8192, 0), 'Framework adapter. Allows: static, ssr', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
@ -94,7 +90,7 @@ class Create extends Base
->callback([$this, 'action']);
}
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $subdomain, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Database $dbForPlatform)
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Database $dbForPlatform)
{
if (!empty($adapter)) {
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
@ -105,21 +101,6 @@ class Create extends Base
}
}
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$routeSubdomain = '';
$domain = '';
if (!empty($sitesDomain)) {
$routeSubdomain = $subdomain ?: ID::unique();
$domain = "{$routeSubdomain}.{$sitesDomain}";
$subdomain = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', \md5($domain)));
if ($subdomain && !$subdomain->isEmpty()) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Subdomain already exists. Please choose a different subdomain.');
}
}
$siteId = ($siteId == 'unique()') ? ID::unique() : $siteId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', '')));
@ -195,67 +176,6 @@ class Create extends Base
$site = $dbForProject->updateDocument('sites', $site->getId(), $site);
if (!empty($sitesDomain)) {
$rule = Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => \md5($domain),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'site',
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Sites */
$ruleCreate
->setClass(Event::SITES_CLASS_NAME)
->setQueue(Event::SITES_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
$queueForEvents->setParam('siteId', $site->getId());
$response

View file

@ -12,10 +12,12 @@ use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Tests\E2E\Services\Functions\FunctionsBase;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\System\System;
class UsageTest extends Scope
{
@ -1090,22 +1092,25 @@ class UsageTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
$rule = $this->client->call(
Client::METHOD_POST,
'/proxy/rules',
array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()),
[
'domain' => 'test-' . ID::unique() . System::getEnv('_APP_DOMAIN_FUNCTIONS'),
'resourceType' => 'function',
'resourceId' => $functionId,
],
]);
);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertNotEmpty($rule['body']['$id']);
$this->assertNotEmpty($rule['body']['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$domain = $rule['body']['domain'];
$response = $this->client->call(
Client::METHOD_GET,

View file

@ -6,6 +6,9 @@ use Appwrite\Tests\Async;
use CURLFile;
use Tests\E2E\Client;
use Utopia\CLI\Console;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\System\System;
trait FunctionsBase
{
@ -255,4 +258,47 @@ trait FunctionsBase
return $function;
}
protected function setupFunctionDomain(string $functionId, string $subdomain = ''): string
{
$subdomain = $subdomain ? $subdomain : ID::unique();
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''),
'resourceType' => 'function',
'resourceId' => $functionId,
]);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertNotEmpty($rule['body']['$id']);
$this->assertNotEmpty($rule['body']['domain']);
$domain = $rule['body']['domain'];
return $domain;
}
protected function getFunctionDomain(string $functionId): string
{
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertGreaterThanOrEqual(1, $rules['body']['total']);
$this->assertGreaterThanOrEqual(1, \count($rules['body']['rules']));
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
return $domain;
}
}

View file

@ -1654,22 +1654,7 @@ class FunctionsCustomServerTest extends Scope
'execute' => ['any']
]);
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
@ -1730,22 +1715,7 @@ class FunctionsCustomServerTest extends Scope
'execute' => ['any']
]);
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
@ -1780,22 +1750,7 @@ class FunctionsCustomServerTest extends Scope
'execute' => ['any']
]);
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
@ -1915,6 +1870,8 @@ class FunctionsCustomServerTest extends Scope
$functionId = $function['body']['$id'] ?? '';
$domain = $this->setupFunctionDomain($functionId);
$this->setupDeployment($functionId, [
'code' => $this->packageFunction('node'),
'activate' => true
@ -1949,20 +1906,7 @@ class FunctionsCustomServerTest extends Scope
}, 10000, 500);
// Domain Executions test
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('resourceId', [$functionId])->toString(),
Query::equal('resourceType', ['function'])->toString(),
],
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
$domain = $rules['body']['rules'][0]['domain'];
$domain = $this->getFunctionDomain($functionId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);

View file

@ -31,11 +31,25 @@ class ProjectsCustomServerTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
'resourceType' => 'api',
'domain' => 'abc.test.io',
]);
$this->assertEquals(201, $response['headers']['status-code']);
// duplicate rule
$response2 = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
'resourceType' => 'api',
'domain' => 'abc.test.io',
]);
$this->assertEquals(409, $response2['headers']['status-code']);
$response = $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $response['body']['$id'], $headers);
$this->assertEquals(204, $response['headers']['status-code']);
// prevent functions domain
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
@ -45,7 +59,7 @@ class ProjectsCustomServerTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// prevent sites domain
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
@ -54,5 +68,42 @@ class ProjectsCustomServerTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
// prevent functions domain
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
'resourceType' => 'function',
'domain' => $functionsDomain,
]);
$this->assertEquals(400, $response['headers']['status-code']);
// prevent sites domain
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
'resourceType' => 'site',
'domain' => $sitesDomain,
]);
$this->assertEquals(400, $response['headers']['status-code']);
$mainDomain = System::getEnv('_APP_DOMAIN', '');
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$deniedDomains = [
$mainDomain,
$sitesDomain,
$functionsDomain,
'localhost',
APP_HOSTNAME_INTERNAL,
];
foreach ($deniedDomains as $deniedDomain) {
$response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [
'resourceType' => 'api',
'domain' => $deniedDomain,
]);
$this->assertEquals(400, $response['headers']['status-code']);
}
}
}

View file

@ -6,7 +6,9 @@ use Appwrite\Tests\Async;
use CURLFile;
use Tests\E2E\Client;
use Utopia\CLI\Console;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\System\System;
trait SitesBase
{
@ -267,6 +269,27 @@ trait SitesBase
return $site;
}
protected function setupSiteDomain(string $siteId, string $subdomain = ''): string
{
$subdomain = $subdomain ? $subdomain : ID::unique();
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''),
'resourceType' => 'site',
'resourceId' => $siteId,
]);
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertNotEmpty($rule['body']['$id']);
$this->assertNotEmpty($rule['body']['domain']);
$domain = $rule['body']['domain'];
return $domain;
}
protected function getSiteDomain(string $siteId): string
{
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([

View file

@ -11,6 +11,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\System\System;
class SitesCustomServerTest extends Scope
{
@ -73,13 +74,12 @@ class SitesCustomServerTest extends Scope
'framework' => 'other',
'buildRuntime' => 'ssr-22',
'outputDirectory' => './',
'subdomain' => 'test-site',
'fallbackFile' => null,
]);
$this->assertNotEmpty($siteId);
$rule = $this->getSiteDomain($siteId);
$rule = $this->setupSiteDomain($siteId);
$response = $this->client->call(Client::METHOD_GET, '/console/resources', [
'origin' => 'http://localhost',
@ -289,6 +289,8 @@ class SitesCustomServerTest extends Scope
$this->assertNotEmpty($siteId);
$domain = $this->setupSiteDomain($siteId);
$secretVariable = $this->createVariable($siteId, [
'key' => 'name',
'value' => 'Appwrite',
@ -1243,7 +1245,7 @@ class SitesCustomServerTest extends Scope
$this->assertNotEmpty($site['body']['deploymentId']);
}, 50000, 500);
$domain = $this->getSiteDomain($siteId);
$domain = $this->setupSiteDomain($siteId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
@ -1270,8 +1272,6 @@ class SitesCustomServerTest extends Scope
public function testSiteDomainReclaiming(): void
{
$subdomain = 'startup' . \uniqid();
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Startup site',
@ -1282,11 +1282,13 @@ class SitesCustomServerTest extends Scope
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
'subdomain' => $subdomain
]);
$this->assertNotEmpty($siteId);
$subdomain = 'startup' . \uniqid();
$domain = $this->setupSiteDomain($siteId, $subdomain);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
@ -1306,7 +1308,7 @@ class SitesCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']);
$site = $this->createSite([
$site2 = $this->createSite([
'siteId' => ID::unique(),
'name' => 'Startup 2 site',
'framework' => 'other',
@ -1316,11 +1318,21 @@ class SitesCustomServerTest extends Scope
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
'subdomain' => $subdomain
]);
$this->assertEquals(400, $site['headers']['status-code']);
$this->assertStringContainsString("Subdomain already exists.", $site['body']['message']);
$siteId2 = $site2['body']['$id'];
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''),
'resourceType' => 'site',
'resourceId' => $siteId2,
]);
$this->assertEquals(409, $rule['headers']['status-code']);
$this->assertStringContainsString("Document with the requested ID already exists. Try again with a different ID or use ID.unique() to generate a unique ID.", $rule['body']['message']);
$this->cleanupSite($siteId);
@ -1356,10 +1368,16 @@ class SitesCustomServerTest extends Scope
'buildCommand' => '',
'installCommand' => '',
'fallbackFile' => '',
'subdomain' => $subdomain
]);
$this->assertEquals(201, $site['headers']['status-code']);
$this->assertNotEmpty($site['body']['$id']);
$siteId = $site['body']['$id'];
$domain = $this->setupSiteDomain($siteId, $subdomain);
$this->assertNotEmpty($domain);
$this->cleanupSite($site['body']['$id']);
}
@ -1380,6 +1398,8 @@ class SitesCustomServerTest extends Scope
$this->assertNotEmpty($siteId);
$domain = $this->setupSiteDomain($siteId);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
@ -1443,6 +1463,7 @@ class SitesCustomServerTest extends Scope
$this->assertNotEmpty($siteId);
$this->setupSiteDomain($siteId, $subdomain);
$domain = $this->getSiteDomain($siteId);
$this->assertNotEmpty($domain);