mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.4.x' of github.com:appwrite/appwrite into disallow-personal-data
This commit is contained in:
commit
c26194e189
38 files changed
+1939
-441
No files matched your search
@@ -698,6 +698,28 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('smtp'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json', 'encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('templates'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1000000, // TODO make sure size fits
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('auths'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1441,7 +1463,18 @@ $collections = [
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['userSearch'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('accessedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1506,7 +1539,14 @@ $collections = [
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -559,6 +559,16 @@ return [
|
||||
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::PROJECT_SMTP_CONFIG_INVALID => [
|
||||
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
|
||||
'description' => 'Provided SMTP config is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PROJECT_TEMPLATE_DEFAULT_DELETION => [
|
||||
'name' => Exception::PROJECT_TEMPLATE_DEFAULT_DELETION,
|
||||
'description' => 'The default template for the project cannot be deleted.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::WEBHOOK_NOT_FOUND => [
|
||||
'name' => Exception::WEBHOOK_NOT_FOUND,
|
||||
'description' => 'Webhook with the requested ID could not be found.',
|
||||
|
||||
+537
-72
@@ -1,76 +1,541 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ISO 639-1 standard language codes
|
||||
* https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||
*
|
||||
* Source:
|
||||
* https://www.andiamo.co.uk/resources/iso-language-codes/
|
||||
*
|
||||
*/
|
||||
|
||||
return [
|
||||
'af', // Afrikaans
|
||||
'ar', // Arabic
|
||||
'as', // Assamese
|
||||
'az', // Azerbaijani
|
||||
'be', // Belarusian
|
||||
'bg', // Bulgarian
|
||||
'bh', // Bihari
|
||||
'bn', // Bengali
|
||||
'bs', // Bosnian
|
||||
'ca', // Catalan
|
||||
'cs', // Czech
|
||||
'da', // Danish
|
||||
'de', // German
|
||||
'en', // English
|
||||
'eo', // Esperanto
|
||||
'es', // Spanish
|
||||
'fa', // Farsi/Persian
|
||||
'fi', // Finnish
|
||||
'fo', // Faroese
|
||||
'fr', // French
|
||||
'el', // Greek
|
||||
'ga', // Irish
|
||||
'gu', // Gujrati
|
||||
'he', // Hebrew
|
||||
'hi', // Hindi,
|
||||
'hr', // Croatian
|
||||
'hu', // Hungarian
|
||||
'hy', // Armenian
|
||||
'id', // Indonesian
|
||||
'is', // Icelandic
|
||||
'it', // Italian
|
||||
'ja', // Japanese
|
||||
'jv', // Javanese
|
||||
'kn', // Kannada
|
||||
'km', // Khmer
|
||||
'ko', // Korean
|
||||
'la', // Latin
|
||||
'lb', // Luxembourgish
|
||||
'lt', // Lithuanian
|
||||
'lv', // Latvian
|
||||
'ml', // Malayalam
|
||||
'mr', // Marathi
|
||||
'ms', // Malay
|
||||
'nb', // Norwegian bokmål
|
||||
'nl', // Dutch
|
||||
'nn', // Norwegian nynorsk
|
||||
'ne', // Nepali
|
||||
'or', // Oriya
|
||||
'tl', // Filipino
|
||||
'pl', // Polish
|
||||
'pt-br', // Portuguese - Brazil
|
||||
'pt-pt', // Portuguese - Portugal
|
||||
'pa', // Punjabi
|
||||
'ro', // Romanian
|
||||
'ru', // Russian
|
||||
'sa', //Sanskrit
|
||||
'sd', // Sindhi
|
||||
'si', // Sinhala
|
||||
'sk', // Slovakia
|
||||
'sl', // Slovenian
|
||||
'sn', // Shona
|
||||
'sq', // Albanian
|
||||
'sv', // Swedish
|
||||
'ta', // Tamil
|
||||
'te', // Telugu
|
||||
'th', // Thai
|
||||
'tr', // Turkish
|
||||
'uk', // Ukrainian
|
||||
'ur', // Urdu
|
||||
'vi', // Vietnamese
|
||||
'zh-cn', // Chinese - China
|
||||
'zh-tw', // Chinese - Taiwan
|
||||
[
|
||||
"code" => "af",
|
||||
"name" => "Afrikaans",
|
||||
],
|
||||
[
|
||||
"code" => "ar-ae",
|
||||
"name" => "Arabic (U.A.E.)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-bh",
|
||||
"name" => "Arabic (Bahrain)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-dz",
|
||||
"name" => "Arabic (Algeria)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-eg",
|
||||
"name" => "Arabic (Egypt)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-iq",
|
||||
"name" => "Arabic (Iraq)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-jo",
|
||||
"name" => "Arabic (Jordan)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-kw",
|
||||
"name" => "Arabic (Kuwait)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-lb",
|
||||
"name" => "Arabic (Lebanon)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-ly",
|
||||
"name" => "Arabic (Libya)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-ma",
|
||||
"name" => "Arabic (Morocco)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-om",
|
||||
"name" => "Arabic (Oman)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-qa",
|
||||
"name" => "Arabic (Qatar)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-sa",
|
||||
"name" => "Arabic (Saudi Arabia)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-sy",
|
||||
"name" => "Arabic (Syria)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-tn",
|
||||
"name" => "Arabic (Tunisia)",
|
||||
],
|
||||
[
|
||||
"code" => "ar-ye",
|
||||
"name" => "Arabic (Yemen)",
|
||||
],
|
||||
[
|
||||
"code" => "as",
|
||||
"name" => "Assamese",
|
||||
],
|
||||
[
|
||||
"code" => "az",
|
||||
"name" => "Azerbaijani",
|
||||
],
|
||||
[
|
||||
"code" => "be",
|
||||
"name" => "Belarusian",
|
||||
],
|
||||
[
|
||||
"code" => "bg",
|
||||
"name" => "Bulgarian",
|
||||
],
|
||||
[
|
||||
"code" => "bh",
|
||||
"name" => "Bihari",
|
||||
],
|
||||
[
|
||||
"code" => "bn",
|
||||
"name" => "Bengali",
|
||||
],
|
||||
[
|
||||
"code" => "bs",
|
||||
"name" => "Bosnian",
|
||||
],
|
||||
[
|
||||
"code" => "ca",
|
||||
"name" => "Catalan",
|
||||
],
|
||||
[
|
||||
"code" => "cs",
|
||||
"name" => "Czech",
|
||||
],
|
||||
[
|
||||
"code" => "cy",
|
||||
"name" => "Welsh",
|
||||
],
|
||||
[
|
||||
"code" => "da",
|
||||
"name" => "Danish",
|
||||
],
|
||||
[
|
||||
"code" => "de",
|
||||
"name" => "German (Standard)",
|
||||
],
|
||||
[
|
||||
"code" => "de-at",
|
||||
"name" => "German (Austria)",
|
||||
],
|
||||
[
|
||||
"code" => "de-ch",
|
||||
"name" => "German (Switzerland)",
|
||||
],
|
||||
[
|
||||
"code" => "de-li",
|
||||
"name" => "German (Liechtenstein)",
|
||||
],
|
||||
[
|
||||
"code" => "de-lu",
|
||||
"name" => "German (Luxembourg)",
|
||||
],
|
||||
[
|
||||
"code" => "el",
|
||||
"name" => "Greek",
|
||||
],
|
||||
[
|
||||
"code" => "en",
|
||||
"name" => "English",
|
||||
],
|
||||
[
|
||||
"code" => "en-au",
|
||||
"name" => "English (Australia)",
|
||||
],
|
||||
[
|
||||
"code" => "en-bz",
|
||||
"name" => "English (Belize)",
|
||||
],
|
||||
[
|
||||
"code" => "en-ca",
|
||||
"name" => "English (Canada)",
|
||||
],
|
||||
[
|
||||
"code" => "en-gb",
|
||||
"name" => "English (United Kingdom)",
|
||||
],
|
||||
[
|
||||
"code" => "en-ie",
|
||||
"name" => "English (Ireland)",
|
||||
],
|
||||
[
|
||||
"code" => "en-jm",
|
||||
"name" => "English (Jamaica)",
|
||||
],
|
||||
[
|
||||
"code" => "en-nz",
|
||||
"name" => "English (New Zealand)",
|
||||
],
|
||||
[
|
||||
"code" => "en-tt",
|
||||
"name" => "English (Trinidad)",
|
||||
],
|
||||
[
|
||||
"code" => "en-us",
|
||||
"name" => "English (United States)",
|
||||
],
|
||||
[
|
||||
"code" => "en-za",
|
||||
"name" => "English (South Africa)",
|
||||
],
|
||||
[
|
||||
"code" => "eo",
|
||||
"name" => "Esperanto",
|
||||
],
|
||||
[
|
||||
"code" => "es",
|
||||
"name" => "Spanish (Spain)",
|
||||
],
|
||||
[
|
||||
"code" => "es-ar",
|
||||
"name" => "Spanish (Argentina)",
|
||||
],
|
||||
[
|
||||
"code" => "es-bo",
|
||||
"name" => "Spanish (Bolivia)",
|
||||
],
|
||||
[
|
||||
"code" => "es-cl",
|
||||
"name" => "Spanish (Chile)",
|
||||
],
|
||||
[
|
||||
"code" => "es-co",
|
||||
"name" => "Spanish (Colombia)",
|
||||
],
|
||||
[
|
||||
"code" => "es-cr",
|
||||
"name" => "Spanish (Costa Rica)",
|
||||
],
|
||||
[
|
||||
"code" => "es-do",
|
||||
"name" => "Spanish (Dominican Republic)",
|
||||
],
|
||||
[
|
||||
"code" => "es-ec",
|
||||
"name" => "Spanish (Ecuador)",
|
||||
],
|
||||
[
|
||||
"code" => "es-gt",
|
||||
"name" => "Spanish (Guatemala)",
|
||||
],
|
||||
[
|
||||
"code" => "es-hn",
|
||||
"name" => "Spanish (Honduras)",
|
||||
],
|
||||
[
|
||||
"code" => "es-mx",
|
||||
"name" => "Spanish (Mexico)",
|
||||
],
|
||||
[
|
||||
"code" => "es-ni",
|
||||
"name" => "Spanish (Nicaragua)",
|
||||
],
|
||||
[
|
||||
"code" => "es-pa",
|
||||
"name" => "Spanish (Panama)",
|
||||
],
|
||||
[
|
||||
"code" => "es-pe",
|
||||
"name" => "Spanish (Peru)",
|
||||
],
|
||||
[
|
||||
"code" => "es-pr",
|
||||
"name" => "Spanish (Puerto Rico)",
|
||||
],
|
||||
[
|
||||
"code" => "es-py",
|
||||
"name" => "Spanish (Paraguay)",
|
||||
],
|
||||
[
|
||||
"code" => "es-sv",
|
||||
"name" => "Spanish (El Salvador)",
|
||||
],
|
||||
[
|
||||
"code" => "es-uy",
|
||||
"name" => "Spanish (Uruguay)",
|
||||
],
|
||||
[
|
||||
"code" => "es-ve",
|
||||
"name" => "Spanish (Venezuela)",
|
||||
],
|
||||
[
|
||||
"code" => "et",
|
||||
"name" => "Estonian",
|
||||
],
|
||||
[
|
||||
"code" => "eu",
|
||||
"name" => "Basque",
|
||||
],
|
||||
[
|
||||
"code" => "fa",
|
||||
"name" => "Farsi",
|
||||
],
|
||||
[
|
||||
"code" => "fi",
|
||||
"name" => "Finnish",
|
||||
],
|
||||
[
|
||||
"code" => "fo",
|
||||
"name" => "Faeroese",
|
||||
],
|
||||
[
|
||||
"code" => "fr",
|
||||
"name" => "French (Standard)",
|
||||
],
|
||||
[
|
||||
"code" => "fr-be",
|
||||
"name" => "French (Belgium)",
|
||||
],
|
||||
[
|
||||
"code" => "fr-ca",
|
||||
"name" => "French (Canada)",
|
||||
],
|
||||
[
|
||||
"code" => "fr-ch",
|
||||
"name" => "French (Switzerland)",
|
||||
],
|
||||
[
|
||||
"code" => "fr-lu",
|
||||
"name" => "French (Luxembourg)",
|
||||
],
|
||||
[
|
||||
"code" => "ga",
|
||||
"name" => "Irish",
|
||||
],
|
||||
[
|
||||
"code" => "gd",
|
||||
"name" => "Gaelic (Scotland)",
|
||||
],
|
||||
[
|
||||
"code" => "he",
|
||||
"name" => "Hebrew",
|
||||
],
|
||||
[
|
||||
"code" => "hi",
|
||||
"name" => "Hindi",
|
||||
],
|
||||
[
|
||||
"code" => "hr",
|
||||
"name" => "Croatian",
|
||||
],
|
||||
[
|
||||
"code" => "hu",
|
||||
"name" => "Hungarian",
|
||||
],
|
||||
[
|
||||
"code" => "id",
|
||||
"name" => "Indonesian",
|
||||
],
|
||||
[
|
||||
"code" => "is",
|
||||
"name" => "Icelandic",
|
||||
],
|
||||
[
|
||||
"code" => "it",
|
||||
"name" => "Italian (Standard)",
|
||||
],
|
||||
[
|
||||
"code" => "it-ch",
|
||||
"name" => "Italian (Switzerland)",
|
||||
],
|
||||
[
|
||||
"code" => "ja",
|
||||
"name" => "Japanese",
|
||||
],
|
||||
[
|
||||
"code" => "ji",
|
||||
"name" => "Yiddish",
|
||||
],
|
||||
[
|
||||
"code" => "ko",
|
||||
"name" => "Korean",
|
||||
],
|
||||
[
|
||||
"code" => "ko",
|
||||
"name" => "Korean (Johab)",
|
||||
],
|
||||
[
|
||||
"code" => "ku",
|
||||
"name" => "Kurdish",
|
||||
],
|
||||
[
|
||||
"code" => "lt",
|
||||
"name" => "Lithuanian",
|
||||
],
|
||||
[
|
||||
"code" => "lv",
|
||||
"name" => "Latvian",
|
||||
],
|
||||
[
|
||||
"code" => "mk",
|
||||
"name" => "Macedonian (FYROM)",
|
||||
],
|
||||
[
|
||||
"code" => "ml",
|
||||
"name" => "Malayalam",
|
||||
],
|
||||
[
|
||||
"code" => "ms",
|
||||
"name" => "Malaysian",
|
||||
],
|
||||
[
|
||||
"code" => "mt",
|
||||
"name" => "Maltese",
|
||||
],
|
||||
[
|
||||
"code" => "nb",
|
||||
"name" => "Norwegian (Bokmål)",
|
||||
],
|
||||
[
|
||||
"code" => "ne",
|
||||
"name" => "Nepali",
|
||||
],
|
||||
[
|
||||
"code" => "nl",
|
||||
"name" => "Dutch (Standard)",
|
||||
],
|
||||
[
|
||||
"code" => "nl-be",
|
||||
"name" => "Dutch (Belgium)",
|
||||
],
|
||||
[
|
||||
"code" => "nn",
|
||||
"name" => "Norwegian (Nynorsk)",
|
||||
],
|
||||
[
|
||||
"code" => "no",
|
||||
"name" => "Norwegian",
|
||||
],
|
||||
[
|
||||
"code" => "pa",
|
||||
"name" => "Punjabi",
|
||||
],
|
||||
[
|
||||
"code" => "pl",
|
||||
"name" => "Polish",
|
||||
],
|
||||
[
|
||||
"code" => "pt",
|
||||
"name" => "Portuguese (Portugal)",
|
||||
],
|
||||
[
|
||||
"code" => "pt-br",
|
||||
"name" => "Portuguese (Brazil)",
|
||||
],
|
||||
[
|
||||
"code" => "rm",
|
||||
"name" => "Rhaeto-Romanic",
|
||||
],
|
||||
[
|
||||
"code" => "ro",
|
||||
"name" => "Romanian",
|
||||
],
|
||||
[
|
||||
"code" => "ro-md",
|
||||
"name" => "Romanian (Republic of Moldova)",
|
||||
],
|
||||
[
|
||||
"code" => "ru",
|
||||
"name" => "Russian",
|
||||
],
|
||||
[
|
||||
"code" => "ru-md",
|
||||
"name" => "Russian (Republic of Moldova)",
|
||||
],
|
||||
[
|
||||
"code" => "sb",
|
||||
"name" => "Sorbian",
|
||||
],
|
||||
[
|
||||
"code" => "sk",
|
||||
"name" => "Slovak",
|
||||
],
|
||||
[
|
||||
"code" => "sl",
|
||||
"name" => "Slovenian",
|
||||
],
|
||||
[
|
||||
"code" => "sq",
|
||||
"name" => "Albanian",
|
||||
],
|
||||
[
|
||||
"code" => "sr",
|
||||
"name" => "Serbian",
|
||||
],
|
||||
[
|
||||
"code" => "sv",
|
||||
"name" => "Swedish",
|
||||
],
|
||||
[
|
||||
"code" => "sv-fi",
|
||||
"name" => "Swedish (Finland)",
|
||||
],
|
||||
[
|
||||
"code" => "th",
|
||||
"name" => "Thai",
|
||||
],
|
||||
[
|
||||
"code" => "tn",
|
||||
"name" => "Tswana",
|
||||
],
|
||||
[
|
||||
"code" => "tr",
|
||||
"name" => "Turkish",
|
||||
],
|
||||
[
|
||||
"code" => "ts",
|
||||
"name" => "Tsonga",
|
||||
],
|
||||
[
|
||||
"code" => "ua",
|
||||
"name" => "Ukrainian",
|
||||
],
|
||||
[
|
||||
"code" => "ur",
|
||||
"name" => "Urdu",
|
||||
],
|
||||
[
|
||||
"code" => "ve",
|
||||
"name" => "Venda",
|
||||
],
|
||||
[
|
||||
"code" => "vi",
|
||||
"name" => "Vietnamese",
|
||||
],
|
||||
[
|
||||
"code" => "xh",
|
||||
"name" => "Xhosa",
|
||||
],
|
||||
[
|
||||
"code" => "zh-cn",
|
||||
"name" => "Chinese (PRC)",
|
||||
],
|
||||
[
|
||||
"code" => "zh-hk",
|
||||
"name" => "Chinese (Hong Kong)",
|
||||
],
|
||||
[
|
||||
"code" => "zh-sg",
|
||||
"name" => "Chinese (Singapore)",
|
||||
],
|
||||
[
|
||||
"code" => "zh-tw",
|
||||
"name" => "Chinese (Taiwan)",
|
||||
],
|
||||
[
|
||||
"code" => "zu",
|
||||
"name" => "Zulu",
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'email' => [
|
||||
'verification',
|
||||
'magicSession',
|
||||
'recovery',
|
||||
'invitation',
|
||||
],
|
||||
'sms' => [
|
||||
'verification',
|
||||
'login',
|
||||
'invitation'
|
||||
]
|
||||
];
|
||||
@@ -2,164 +2,84 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>{{subject}}</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: {{bg-body}};
|
||||
color: {{text-content}};
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Poppins:wght@500;600&display=swap"
|
||||
rel="stylesheet">
|
||||
<style>
|
||||
a { color:currentColor; }
|
||||
body {
|
||||
padding: 32px;
|
||||
color: #616B7C;
|
||||
font-size: 15px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-spacing: 0 !important;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
table,
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.body {
|
||||
background-color: {{bg-body}};
|
||||
width: 100%;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
h* {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
color: {{text-content}};
|
||||
}
|
||||
|
||||
.main {
|
||||
background: {{bg-content}};
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 30px 30px 15px 30px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #E8E9F0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="direction: {{direction}}">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table role="presentation" class="main">
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p>{{hello}}</p>
|
||||
<p>{{body}}</p>
|
||||
<a href="{{redirect}}" target="_blank">{{redirect}}</a>
|
||||
<p></br>{{footer}}</p>
|
||||
<p>{{thanks}}
|
||||
</br>
|
||||
{{signature}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- <div style="text-align: center; line-height: 25px; margin: 15px 0; font-size: 12px; color: #40404c;">
|
||||
<a href="https://appwrite.io" style="text-decoration: none; color: #40404c;">Powered by <img src="https://appwrite.io/images/appwrite-footer-light.svg" height="15" style="margin: -3px 0" /></a>
|
||||
</div> -->
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="max-width:650px; word-wrap: break-wrod; overflow-wrap: break-word;
|
||||
word-break: break-all; margin:0 auto;">
|
||||
<table style="margin-top: 32px">
|
||||
<tr>
|
||||
<td>
|
||||
<h1>
|
||||
{{subject}}
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table style="margin-top: 40px">
|
||||
<tr>
|
||||
<td>
|
||||
<p>{{hello}}</p>
|
||||
|
||||
<p>{{body}}</p>
|
||||
|
||||
<a href="{{redirect}}" target="_blank">{{redirect}}</a>
|
||||
|
||||
<p><br />{{footer}}</p>
|
||||
<br />
|
||||
|
||||
<p>{{thanks}}
|
||||
<br />
|
||||
{{signature}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
{{token}}
|
||||
@@ -1 +1 @@
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","x-example":"[NAME]"}},"required":["userId","email","password"]}}}}}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/jwt":{"post":{"summary":"Create JWT","operationId":"accountCreateJWT","tags":["account"],"description":"Use this endpoint to create a JSON Web Token. You can use the resulting JWT to autLine truncated
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","x-example":"[NAME]"}},"required":["userId","email","password"]}}}}}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/jwt":{"post":{"summary":"Create JWT","operationId":"accountCreateJWT","tags":["account"],"description":"Use this endpoint to create a JSON Web Token. You can use the resulting JWT to autLine truncated
|
||||
@@ -1 +1 @@
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","x-example":"[NAME]"}},"required":["userId","email","password"]}}}}}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/jwt":{"post":{"summary":"Create JWT","operationId":"accountCreateJWT","tags":["account"],"description":"Use this endpoint to create a JSON Web Token. You can use the resulting JWT to autLine truncated
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","x-example":"[NAME]"}},"required":["userId","email","password"]}}}}}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/jwt":{"post":{"summary":"Create JWT","operationId":"accountCreateJWT","tags":["account"],"description":"Use this endpoint to create a JSON Web Token. You can use the resulting JWT to autLine truncated
|
||||
@@ -1 +1 @@
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/logs":{"get":{"summary":"List Logs","operationId":"accountListLogs","tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","responses":{"200":{"description":"Logs List","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/logList"}}}}},"x-appwrite":{"method":"listLogs","weight":21,"cookies":false,"type":"","demo":"account\/list-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/list-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"queries","description":"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","required":false,"schema":{"type":"array","items":{"type":"string"},"default":[]},"in":"query"}]}},"\/account\/name":{"patch":{"summary":"Update Name","operationId":"accountUpdateName","tags":["account"],"description":"Update currently logged in user account name.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateName","weight":23,"cookies":false,"type":"","demo":"account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"Line truncated
|
||||
{"openapi":"3.0.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"servers":[{"url":"https:\/\/HOSTNAME\/v1"}],"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"requestBody":{"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","x-example":"password"}},"required":["email","password"]}}}}}},"\/account\/logs":{"get":{"summary":"List Logs","operationId":"accountListLogs","tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","responses":{"200":{"description":"Logs List","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/logList"}}}}},"x-appwrite":{"method":"listLogs","weight":21,"cookies":false,"type":"","demo":"account\/list-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/list-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"queries","description":"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","required":false,"schema":{"type":"array","items":{"type":"string"},"default":[]},"in":"query"}]}},"\/account\/name":{"patch":{"summary":"Update Name","operationId":"accountUpdateName","tags":["account"],"description":"Update currently logged in user account name.","responses":{"200":{"description":"User","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/user"}}}}},"x-appwrite":{"method":"updateName","weight":23,"cookies":false,"type":"","demo":"account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"Line truncated
|
||||
@@ -1 +1 @@
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","default":null,"x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","default":null,"x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["userId","email","password"]}}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packLine truncated
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","default":null,"x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","default":null,"x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["userId","email","password"]}}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packLine truncated
|
||||
@@ -1 +1 @@
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","x-appwrite":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}},"Mode":{"type":"apiKey","name":"X-Appwrite-Mode","description":"","in":"header","x-appwrite":{"demo":""}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","default":null,"x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","default":null,"x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["userId","email","password"]}}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"Line truncated
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","x-appwrite":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}},"Mode":{"type":"apiKey","name":"X-Appwrite-Mode","description":"","in":"header","x-appwrite":{"demo":""}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[],"JWT":[]}]},"post":{"summary":"Create Account","operationId":"accountCreate","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](\/docs\/client\/account#accountCreateSession).","responses":{"201":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"create","weight":6,"cookies":false,"type":"","demo":"account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[]}},"security":[{"Project":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"userId":{"type":"string","description":"Unique 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.","default":null,"x-example":"[USER_ID]"},"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"New user password. Must be at least 8 chars.","default":null,"x-example":null},"name":{"type":"string","description":"User name. Max length: 128 chars.","default":"","x-example":"[NAME]"}},"required":["userId","email","password"]}}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"Line truncated
|
||||
@@ -1 +1 @@
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","x-appwrite":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","default":null,"x-example":"password"}},"required":["email","password"]}}]}},"\/account\/logs":{"get":{"summary":"List Logs","operationId":"accountListLogs","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","responses":{"200":{"description":"Logs List","schema":{"$ref":"#\/definitions\/logList"}}},"x-appwrite":{"method":"listLogs","weight":21,"cookies":false,"type":"","demo":"account\/list-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/list-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"queries","description":"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","required":false,"type":"array","collectionFormat"Line truncated
|
||||
{"swagger":"2.0","info":{"version":"1.3.7","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@appwrite.io"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"HOSTNAME","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","x-appwrite":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","x-appwrite":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"JWT":{"type":"apiKey","name":"X-Appwrite-JWT","description":"Your secret JSON Web Token","in":"header","x-appwrite":{"demo":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","x-appwrite":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"accountGet","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"get","weight":18,"cookies":false,"type":"","demo":"account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}]}},"\/account\/email":{"patch":{"summary":"Update Email","operationId":"accountUpdateEmail","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n","responses":{"200":{"description":"User","schema":{"$ref":"#\/definitions\/user"}}},"x-appwrite":{"method":"updateEmail","weight":25,"cookies":false,"type":"","demo":"account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"\/account","offline-key":"current","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"payload","in":"body","schema":{"type":"object","properties":{"email":{"type":"string","description":"User email.","default":null,"x-example":"email@example.com"},"password":{"type":"string","description":"User password. Must be at least 8 chars.","default":null,"x-example":"password"}},"required":["email","password"]}}]}},"\/account\/logs":{"get":{"summary":"List Logs","operationId":"accountListLogs","consumes":["application\/json"],"produces":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","responses":{"200":{"description":"Logs List","schema":{"$ref":"#\/definitions\/logList"}}},"x-appwrite":{"method":"listLogs","weight":21,"cookies":false,"type":"","demo":"account\/list-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/list-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client","server"],"packaging":false,"offline-model":"","offline-key":"","offline-response-key":"$id","auth":{"Project":[],"JWT":[]}},"security":[{"Project":[],"JWT":[]}],"parameters":[{"name":"queries","description":"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","required":false,"type":"array","collectionFormat"Line truncated
|
||||
+146
-60
@@ -71,10 +71,11 @@ App::post('/v1/account')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) {
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
@@ -110,7 +111,7 @@ App::post('/v1/account')
|
||||
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -132,8 +133,10 @@ App::post('/v1/account')
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name])
|
||||
])));
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'accessedAt' => DateTime::now(), // Add this here to make sure it's returned in the response
|
||||
]);
|
||||
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -174,12 +177,13 @@ App::post('/v1/account/sessions/email')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('events')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
@@ -196,6 +200,8 @@ App::post('/v1/account/sessions/email')
|
||||
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
|
||||
}
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
@@ -205,8 +211,8 @@ App::post('/v1/account/sessions/email')
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $profile->getId(),
|
||||
'userInternalId' => $profile->getInternalId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $email,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
@@ -219,35 +225,35 @@ App::post('/v1/account/sessions/email')
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
// Re-hash if not using recommended algo
|
||||
if ($profile->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
|
||||
$profile
|
||||
if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$dbForProject->updateDocument('users', $profile->getId(), $profile);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $profile->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($profile->getId())),
|
||||
Permission::update(Role::user($profile->getId())),
|
||||
Permission::delete(Role::user($profile->getId())),
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
|
||||
;
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($profile->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
@@ -260,7 +266,7 @@ App::post('/v1/account/sessions/email')
|
||||
;
|
||||
|
||||
$events
|
||||
->setParam('userId', $profile->getId())
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $session->getId())
|
||||
;
|
||||
|
||||
@@ -484,10 +490,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
}
|
||||
|
||||
$user = ($user->isEmpty()) ? $dbForProject->findOne('sessions', [ // Get user by provider id
|
||||
Query::equal('provider', [$provider]),
|
||||
Query::equal('providerUid', [$oauth2ID]),
|
||||
]) : $user;
|
||||
if ($user->isEmpty()) {
|
||||
$session = $dbForProject->findOne('sessions', [ // Get user by provider id
|
||||
Query::equal('provider', [$provider]),
|
||||
Query::equal('providerUid', [$oauth2ID]),
|
||||
]);
|
||||
if ($session !== false && !$session->isEmpty()) {
|
||||
$user->setAttributes($dbForProject->getDocument('users', $session->getAttribute('userId'))->getArrayCopy());
|
||||
}
|
||||
}
|
||||
|
||||
if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
|
||||
$name = $oauth2->getUserName($accessToken);
|
||||
@@ -498,9 +509,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
*/
|
||||
$isVerified = $oauth2->isEmailVerified($accessToken);
|
||||
|
||||
$user = $dbForProject->findOne('users', [
|
||||
$userWithEmail = $dbForProject->findOne('users', [
|
||||
Query::equal('email', [$email]),
|
||||
]);
|
||||
if ($userWithEmail !== false && !$userWithEmail->isEmpty()) {
|
||||
$user->setAttributes($userWithEmail->getArrayCopy());
|
||||
}
|
||||
|
||||
if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
@@ -518,7 +532,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$password = Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -541,7 +555,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name])
|
||||
])));
|
||||
]);
|
||||
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -653,12 +668,13 @@ App::post('/v1/account/sessions/magic-url')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('events')
|
||||
->inject('mails')
|
||||
->action(function (string $userId, string $email, string $url, Request $request, Response $response, Document $project, Database $dbForProject, Locale $locale, Event $events, Mail $mails) {
|
||||
->action(function (string $userId, string $email, string $url, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $events, Mail $mails) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
@@ -668,9 +684,10 @@ App::post('/v1/account/sessions/magic-url')
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$user = $dbForProject->findOne('users', [Query::equal('email', [$email])]);
|
||||
|
||||
if (!$user) {
|
||||
$result = $dbForProject->findOne('users', [Query::equal('email', [$email])]);
|
||||
if ($result !== false && !$result->isEmpty()) {
|
||||
$user->setAttributes($result->getArrayCopy());
|
||||
} else {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
||||
if ($limit !== 0) {
|
||||
@@ -683,7 +700,7 @@ App::post('/v1/account/sessions/magic-url')
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -704,7 +721,9 @@ App::post('/v1/account/sessions/magic-url')
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email])
|
||||
])));
|
||||
]);
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
}
|
||||
|
||||
$loginSecret = Auth::tokenGenerator();
|
||||
@@ -741,10 +760,17 @@ App::post('/v1/account/sessions/magic-url')
|
||||
$url = Template::unParseURL($url);
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project->getAttribute('name'));
|
||||
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.magicSession.subject");
|
||||
|
||||
$smtpEnabled = $project->getAttribute('smtp', [])['enabled'] ?? false;
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.magicSession-' . $locale->default] ?? [];
|
||||
if ($smtpEnabled && !empty($customTemplate)) {
|
||||
$body = $customTemplate['message'] ?? $body;
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
$from = $customTemplate['senderName'] ?? $from;
|
||||
}
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
|
||||
@@ -809,27 +835,30 @@ App::put('/v1/account/sessions/magic-url')
|
||||
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
|
||||
/** @var Utopia\Database\Document $user */
|
||||
|
||||
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
$userFromRequest = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
if ($userFromRequest->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret);
|
||||
$token = Auth::tokenVerify($userFromRequest->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret);
|
||||
|
||||
if (!$token) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
$user->setAttributes($userFromRequest->getArrayCopy());
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -874,9 +903,9 @@ App::put('/v1/account/sessions/magic-url')
|
||||
|
||||
$user->setAttribute('emailVerification', true);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
if (false === $user) {
|
||||
try {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
|
||||
}
|
||||
|
||||
@@ -929,11 +958,13 @@ App::post('/v1/account/sessions/phone')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('messaging')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $project, Database $dbForProject, Event $events, EventPhone $messaging) {
|
||||
->inject('locale')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events, EventPhone $messaging, Locale $locale) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
@@ -943,9 +974,10 @@ App::post('/v1/account/sessions/phone')
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
$user = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
|
||||
if (!$user) {
|
||||
$result = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
if ($result !== false && !$result->isEmpty()) {
|
||||
$user->setAttributes($result->getArrayCopy());
|
||||
} else {
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
||||
if ($limit !== 0) {
|
||||
@@ -957,8 +989,7 @@ App::post('/v1/account/sessions/phone')
|
||||
}
|
||||
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -979,7 +1010,9 @@ App::post('/v1/account/sessions/phone')
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $phone])
|
||||
])));
|
||||
]);
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
}
|
||||
|
||||
$secret = Auth::codeGenerator();
|
||||
@@ -1007,9 +1040,19 @@ App::post('/v1/account/sessions/phone')
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.login-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
}
|
||||
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($secret)
|
||||
->setMessage($message)
|
||||
->trigger();
|
||||
|
||||
$events->setPayload(
|
||||
@@ -1048,25 +1091,28 @@ App::put('/v1/account/sessions/phone')
|
||||
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
->action(function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $events) {
|
||||
|
||||
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
$userFromRequest = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
if ($userFromRequest->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$token = Auth::phoneTokenVerify($user->getAttribute('tokens', []), $secret);
|
||||
$token = Auth::phoneTokenVerify($userFromRequest->getAttribute('tokens', []), $secret);
|
||||
|
||||
if (!$token) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
$user->setAttributes($userFromRequest->getArrayCopy());
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -1109,7 +1155,7 @@ App::put('/v1/account/sessions/phone')
|
||||
|
||||
$user->setAttribute('phoneVerification', true);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
if (false === $user) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
|
||||
@@ -1194,7 +1240,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
}
|
||||
|
||||
$userId = ID::unique();
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -1215,8 +1261,9 @@ App::post('/v1/account/sessions/anonymous')
|
||||
'sessions' => null,
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => $userId
|
||||
])));
|
||||
'search' => $userId,
|
||||
]);
|
||||
Authorization::skip(fn() => $dbForProject->createDocument('users', $user));
|
||||
|
||||
// Create session token
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
@@ -2064,12 +2111,13 @@ App::post('/v1/account/recovery')
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('mails')
|
||||
->inject('events')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Mail $mails, Event $events) {
|
||||
->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $mails, Event $events) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -2089,6 +2137,8 @@ App::post('/v1/account/recovery')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
if (false === $profile->getAttribute('status')) { // Account is blocked
|
||||
throw new Exception(Exception::USER_BLOCKED);
|
||||
}
|
||||
@@ -2127,6 +2177,14 @@ App::post('/v1/account/recovery')
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.recovery.subject");
|
||||
|
||||
$smtpEnabled = $project->getAttribute('smtp', [])['enabled'] ?? false;
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.recovery-' . $locale->default] ?? [];
|
||||
if ($smtpEnabled && !empty($customTemplate)) {
|
||||
$body = $customTemplate['message'] ?? $body;
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
$from = $customTemplate['senderName'] ?? $from;
|
||||
}
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
|
||||
@@ -2195,10 +2253,11 @@ App::put('/v1/account/recovery')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->param('passwordAgain', '', new Password(), 'Repeat new user password. Must be at least 8 chars.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Database $dbForProject, Document $project, Event $events) {
|
||||
->action(function (string $userId, string $secret, string $password, string $passwordAgain, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
if ($password !== $passwordAgain) {
|
||||
throw new Exception(Exception::USER_PASSWORD_MISMATCH);
|
||||
}
|
||||
@@ -2240,6 +2299,8 @@ App::put('/v1/account/recovery')
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('emailVerification', true));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
|
||||
/**
|
||||
@@ -2326,6 +2387,15 @@ App::post('/v1/account/verification')
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = $locale->getText("emails.verification.subject");
|
||||
|
||||
$smtpEnabled = $project->getAttribute('smtp', [])['enabled'] ?? false;
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.verification-' . $locale->default] ?? [];
|
||||
if ($smtpEnabled && !empty($customTemplate)) {
|
||||
$body = $customTemplate['message'] ?? $body;
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
$from = $customTemplate['senderName'] ?? $from;
|
||||
}
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
|
||||
@@ -2412,6 +2482,8 @@ App::put('/v1/account/verification')
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
|
||||
/**
|
||||
@@ -2453,7 +2525,9 @@ App::post('/v1/account/verification/phone')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('messaging')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events, EventPhone $messaging) {
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events, EventPhone $messaging, Document $project, Locale $locale) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED);
|
||||
@@ -2491,9 +2565,19 @@ App::post('/v1/account/verification/phone')
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.verification-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
}
|
||||
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
$messaging
|
||||
->setRecipient($user->getAttribute('phone'))
|
||||
->setMessage($secret)
|
||||
->setMessage($message)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
@@ -2556,6 +2640,8 @@ App::put('/v1/account/verification/phone')
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
$verificationDocument = $dbForProject->getDocument('tokens', $verification);
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ App::get('/v1/locale')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOCALE)
|
||||
->label('sdk.offline.model', '/locale')
|
||||
->label('sdk.offline.model', '/localed')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -68,6 +68,28 @@ App::get('/v1/locale')
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/codes')
|
||||
->desc('List Locale Codes')
|
||||
->groups(['api', 'locale'])
|
||||
->label('scope', 'locale.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'locale')
|
||||
->label('sdk.method', 'listCodes')
|
||||
->label('sdk.description', '/docs/references/locale/list-locale-codes.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOCALE_CODE_LIST)
|
||||
->label('sdk.offline.model', '/locale/localeCode')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$codes = Config::getParam('locale-codes');
|
||||
$response->dynamic(new Document([
|
||||
'localeCodes' => $codes,
|
||||
'total' => count($codes),
|
||||
]), Response::MODEL_LOCALE_CODE_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/locale/countries')
|
||||
->desc('List Countries')
|
||||
->groups(['api', 'locale'])
|
||||
|
||||
@@ -28,14 +28,19 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Registry\Registry;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Appwrite\Template\Template;
|
||||
use Utopia\Locale\Locale;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
App::init()
|
||||
->groups(['projects'])
|
||||
@@ -1705,3 +1710,332 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
// CUSTOM SMTP and Templates
|
||||
App::patch('/v1/projects/:projectId/smtp')
|
||||
->desc('Update SMTP configuration')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateSmtpConfiguration')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(), 'Enable custom SMTP service')
|
||||
->param('sender', '', new Email(), 'SMTP sender email')
|
||||
->param('host', '', new HostName(), 'SMTP server host name')
|
||||
->param('port', null, new Integer(), 'SMTP server port')
|
||||
->param('username', null, new Text(0), 'SMTP server username')
|
||||
->param('password', null, new Text(0), 'SMTP server password')
|
||||
->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, string $sender, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// validate SMTP settings
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->Host = $host;
|
||||
$mail->Port = $port;
|
||||
$mail->SMTPSecure = $secure;
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$valid = $mail->SmtpConnect();
|
||||
|
||||
if (!$valid) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
|
||||
}
|
||||
|
||||
$smtp = [
|
||||
'enabled' => $enabled,
|
||||
'sender' => $sender,
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'secure' => $secure,
|
||||
];
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('smtp', $smtp));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Get custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'getSmsTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SMS_TEMPLATE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
$template = [
|
||||
'message' => Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl')->render(),
|
||||
];
|
||||
}
|
||||
|
||||
$template['type'] = $type;
|
||||
$template['locale'] = $locale;
|
||||
|
||||
$response->dynamic(new Document($template), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
App::get('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Get custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'getEmailTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_EMAIL_TEMPLATE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
$localeObj = new Locale($locale);
|
||||
if (is_null($template)) {
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$message = $message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
|
||||
->setParam('{{name}}', '')
|
||||
->setParam('{{body}}', $localeObj->getText("emails.{$type}.body"))
|
||||
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
|
||||
->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
|
||||
->setParam('{{direction}}', $localeObj->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000')
|
||||
->render();
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($localeObj->getText('emails.sender'), $project->getAttribute('name'));
|
||||
$from = empty($from) ? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')) : $from;
|
||||
$template = [
|
||||
'message' => $message,
|
||||
'subject' => $localeObj->getText('emails.' . $type . '.subject'),
|
||||
'senderEmail' => App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', ''),
|
||||
'senderName' => $from
|
||||
];
|
||||
}
|
||||
|
||||
$template['type'] = $type;
|
||||
$template['locale'] = $locale;
|
||||
|
||||
$response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Update custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateSmsTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SMS_TEMPLATE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->param('message', '', new Text(0), 'Template message')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$templates['sms.' . $type . '-' . $locale] = [
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
'locale' => $locale,
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Update custom email templates')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateEmailTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->param('senderName', '', new Text(255), 'Name of the email sender')
|
||||
->param('senderEmail', '', new Email(), 'Email of the sender')
|
||||
->param('subject', '', new Text(255), 'Email Subject')
|
||||
->param('message', '', new Text(0), 'Template message')
|
||||
->param('replyTo', '', new Email(), 'Reply to email', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, string $senderName, string $senderEmail, string $subject, string $message, string $replyTo, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$templates['email.' . $type . '-' . $locale] = [
|
||||
'senderName' => $senderName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'subject' => $subject,
|
||||
'replyTo' => $replyTo,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'type' => $type,
|
||||
'locale' => $locale,
|
||||
'senderName' => $senderName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'subject' => $subject,
|
||||
'replyTo' => $replyTo,
|
||||
'message' => $message
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
->desc('Reset custom SMS template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'deleteSmsTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SMS_TEMPLATE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['sms'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['sms.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
|
||||
}
|
||||
|
||||
unset($template['sms.' . $type . '-' . $locale]);
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'type' => $type,
|
||||
'locale' => $locale,
|
||||
'message' => $template['message']
|
||||
]), Response::MODEL_SMS_TEMPLATE);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
|
||||
->desc('Reset custom email template')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'deleteEmailTemplate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_EMAIL_TEMPLATE)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? []), 'Template type')
|
||||
->param('locale', '', fn($localeCodes) => new WhiteList($localeCodes), 'Template locale', false, ['localeCodes'])
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$templates = $project->getAttribute('templates', []);
|
||||
$template = $templates['email.' . $type . '-' . $locale] ?? null;
|
||||
|
||||
if (is_null($template)) {
|
||||
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
|
||||
}
|
||||
|
||||
unset($templates['email.' . $type . '-' . $locale]);
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('templates', $templates));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'type' => $type,
|
||||
'locale' => $locale,
|
||||
'senderName' => $template['senderName'],
|
||||
'senderEmail' => $template['senderEmail'],
|
||||
'subject' => $template['subject'],
|
||||
'replyTo' => $template['replyTo'],
|
||||
'message' => $template['message']
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
});
|
||||
@@ -547,6 +547,15 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
|
||||
$smtpEnabled = $project->getAttribute('smtp', [])['enabled'] ?? false;
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
|
||||
if ($smtpEnabled && !empty($customTemplate)) {
|
||||
$body = $customTemplate['message'];
|
||||
$subject = $customTemplate['subject'];
|
||||
$from = $customTemplate['senderName'];
|
||||
}
|
||||
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
|
||||
@@ -576,9 +585,19 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'];
|
||||
}
|
||||
|
||||
$message = $message->setParam('{{token}}', $url);
|
||||
$message = $message->render();
|
||||
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($url)
|
||||
->setMessage($message)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
@@ -848,7 +867,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
}
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
$user = $dbForProject->getDocument('users', $userId); // Get user
|
||||
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('userId') !== $user->getId()) {
|
||||
@@ -864,7 +883,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
$user = Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
Authorization::skip(fn() => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Log user in
|
||||
|
||||
|
||||
@@ -50,9 +50,10 @@ App::init()
|
||||
->inject('dbForConsole')
|
||||
->inject('user')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
->inject('clients')
|
||||
->inject('servers')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $clients, array $servers) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, Document $user, Locale $locale, array $localeCodes, array $clients, array $servers) {
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
@@ -136,7 +137,7 @@ App::init()
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, Config::getParam('locale-codes'))) {
|
||||
if (\in_array($localeParam, $localeCodes)) {
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Usage\Stats;
|
||||
@@ -101,7 +102,8 @@ App::init()
|
||||
->inject('database')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
->inject('mails')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, Database $dbForProject, string $mode, Mail $mails) use ($databaseListener) {
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
@@ -167,9 +169,9 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
$events
|
||||
->setEvent($route->getLabel('event', ''))
|
||||
->setProject($project)
|
||||
@@ -191,6 +193,17 @@ App::init()
|
||||
->setParam('project.{scope}.network.inbound', 0)
|
||||
->setParam('project.{scope}.network.outbound', 0);
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
if (!empty($smtp) && ($smtp['enabled'] ?? false)) {
|
||||
$mails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? 25)
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSenderEmail($smtp['sender'] ?? '')
|
||||
->setSmtpReplyTo($smtp['replyTo'] ?? '');
|
||||
}
|
||||
|
||||
$deletes->setProject($project);
|
||||
$database->setProject($project);
|
||||
|
||||
@@ -356,6 +369,7 @@ App::shutdown()
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('events')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
@@ -363,8 +377,8 @@ App::shutdown()
|
||||
->inject('database')
|
||||
->inject('mode')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject) use ($parseLabel) {
|
||||
|
||||
->inject('dbForConsole')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $events, Audit $audits, Stats $usage, Delete $deletes, EventDatabase $database, string $mode, Database $dbForProject, Database $dbForConsole) use ($parseLabel) {
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
if (!empty($events->getEvent())) {
|
||||
@@ -424,7 +438,6 @@ App::shutdown()
|
||||
|
||||
$route = $utopia->match($request);
|
||||
$requestParams = $route->getParamsValues();
|
||||
$user = $audits->getUser();
|
||||
|
||||
/**
|
||||
* Audit labels
|
||||
@@ -437,10 +450,7 @@ App::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
$pattern = $route->getLabel('audits.userId', null);
|
||||
if (!empty($pattern)) {
|
||||
$userId = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if (!$user->isEmpty()) {
|
||||
$audits->setUser($user);
|
||||
}
|
||||
|
||||
@@ -551,6 +561,22 @@ App::shutdown()
|
||||
->setParam('project.{scope}.network.outbound', $response->getSize())
|
||||
->submit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user last activity
|
||||
*/
|
||||
if (!$user->isEmpty()) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} else {
|
||||
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
|
||||
+25
-72
@@ -47,6 +47,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
@@ -99,6 +100,7 @@ const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 506;
|
||||
const APP_VERSION_STABLE = '1.3.7';
|
||||
@@ -205,6 +207,7 @@ Config::load('locale-languages', __DIR__ . '/config/locale/languages.php');
|
||||
Config::load('locale-phones', __DIR__ . '/config/locale/phones.php');
|
||||
Config::load('locale-countries', __DIR__ . '/config/locale/countries.php');
|
||||
Config::load('locale-continents', __DIR__ . '/config/locale/continents.php');
|
||||
Config::load('locale-templates', __DIR__ . '/config/locale/templates.php');
|
||||
Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
|
||||
@@ -683,78 +686,24 @@ $register->set('promiseAdapter', function () {
|
||||
* Localization
|
||||
*/
|
||||
Locale::$exceptions = false;
|
||||
Locale::setLanguageFromJSON('af', __DIR__ . '/config/locale/translations/af.json');
|
||||
Locale::setLanguageFromJSON('ar', __DIR__ . '/config/locale/translations/ar.json');
|
||||
Locale::setLanguageFromJSON('as', __DIR__ . '/config/locale/translations/as.json');
|
||||
Locale::setLanguageFromJSON('az', __DIR__ . '/config/locale/translations/az.json');
|
||||
Locale::setLanguageFromJSON('be', __DIR__ . '/config/locale/translations/be.json');
|
||||
Locale::setLanguageFromJSON('bg', __DIR__ . '/config/locale/translations/bg.json');
|
||||
Locale::setLanguageFromJSON('bh', __DIR__ . '/config/locale/translations/bh.json');
|
||||
Locale::setLanguageFromJSON('bn', __DIR__ . '/config/locale/translations/bn.json');
|
||||
Locale::setLanguageFromJSON('bs', __DIR__ . '/config/locale/translations/bs.json');
|
||||
Locale::setLanguageFromJSON('ca', __DIR__ . '/config/locale/translations/ca.json');
|
||||
Locale::setLanguageFromJSON('cs', __DIR__ . '/config/locale/translations/cs.json');
|
||||
Locale::setLanguageFromJSON('da', __DIR__ . '/config/locale/translations/da.json');
|
||||
Locale::setLanguageFromJSON('de', __DIR__ . '/config/locale/translations/de.json');
|
||||
Locale::setLanguageFromJSON('el', __DIR__ . '/config/locale/translations/el.json');
|
||||
Locale::setLanguageFromJSON('en', __DIR__ . '/config/locale/translations/en.json');
|
||||
Locale::setLanguageFromJSON('eo', __DIR__ . '/config/locale/translations/eo.json');
|
||||
Locale::setLanguageFromJSON('es', __DIR__ . '/config/locale/translations/es.json');
|
||||
Locale::setLanguageFromJSON('fa', __DIR__ . '/config/locale/translations/fa.json');
|
||||
Locale::setLanguageFromJSON('fi', __DIR__ . '/config/locale/translations/fi.json');
|
||||
Locale::setLanguageFromJSON('fo', __DIR__ . '/config/locale/translations/fo.json');
|
||||
Locale::setLanguageFromJSON('fr', __DIR__ . '/config/locale/translations/fr.json');
|
||||
Locale::setLanguageFromJSON('ga', __DIR__ . '/config/locale/translations/ga.json');
|
||||
Locale::setLanguageFromJSON('gu', __DIR__ . '/config/locale/translations/gu.json');
|
||||
Locale::setLanguageFromJSON('he', __DIR__ . '/config/locale/translations/he.json');
|
||||
Locale::setLanguageFromJSON('hi', __DIR__ . '/config/locale/translations/hi.json');
|
||||
Locale::setLanguageFromJSON('hr', __DIR__ . '/config/locale/translations/hr.json');
|
||||
Locale::setLanguageFromJSON('hu', __DIR__ . '/config/locale/translations/hu.json');
|
||||
Locale::setLanguageFromJSON('hy', __DIR__ . '/config/locale/translations/hy.json');
|
||||
Locale::setLanguageFromJSON('id', __DIR__ . '/config/locale/translations/id.json');
|
||||
Locale::setLanguageFromJSON('is', __DIR__ . '/config/locale/translations/is.json');
|
||||
Locale::setLanguageFromJSON('it', __DIR__ . '/config/locale/translations/it.json');
|
||||
Locale::setLanguageFromJSON('ja', __DIR__ . '/config/locale/translations/ja.json');
|
||||
Locale::setLanguageFromJSON('jv', __DIR__ . '/config/locale/translations/jv.json');
|
||||
Locale::setLanguageFromJSON('kn', __DIR__ . '/config/locale/translations/kn.json');
|
||||
Locale::setLanguageFromJSON('km', __DIR__ . '/config/locale/translations/km.json');
|
||||
Locale::setLanguageFromJSON('ko', __DIR__ . '/config/locale/translations/ko.json');
|
||||
Locale::setLanguageFromJSON('la', __DIR__ . '/config/locale/translations/la.json');
|
||||
Locale::setLanguageFromJSON('lb', __DIR__ . '/config/locale/translations/lb.json');
|
||||
Locale::setLanguageFromJSON('lt', __DIR__ . '/config/locale/translations/lt.json');
|
||||
Locale::setLanguageFromJSON('lv', __DIR__ . '/config/locale/translations/lv.json');
|
||||
Locale::setLanguageFromJSON('ml', __DIR__ . '/config/locale/translations/ml.json');
|
||||
Locale::setLanguageFromJSON('mr', __DIR__ . '/config/locale/translations/mr.json');
|
||||
Locale::setLanguageFromJSON('ms', __DIR__ . '/config/locale/translations/ms.json');
|
||||
Locale::setLanguageFromJSON('nb', __DIR__ . '/config/locale/translations/nb.json');
|
||||
Locale::setLanguageFromJSON('ne', __DIR__ . '/config/locale/translations/ne.json');
|
||||
Locale::setLanguageFromJSON('nl', __DIR__ . '/config/locale/translations/nl.json');
|
||||
Locale::setLanguageFromJSON('nn', __DIR__ . '/config/locale/translations/nn.json');
|
||||
Locale::setLanguageFromJSON('or', __DIR__ . '/config/locale/translations/or.json');
|
||||
Locale::setLanguageFromJSON('pa', __DIR__ . '/config/locale/translations/pa.json');
|
||||
Locale::setLanguageFromJSON('pl', __DIR__ . '/config/locale/translations/pl.json');
|
||||
Locale::setLanguageFromJSON('pt-br', __DIR__ . '/config/locale/translations/pt-br.json');
|
||||
Locale::setLanguageFromJSON('pt-pt', __DIR__ . '/config/locale/translations/pt-pt.json');
|
||||
Locale::setLanguageFromJSON('ro', __DIR__ . '/config/locale/translations/ro.json');
|
||||
Locale::setLanguageFromJSON('ru', __DIR__ . '/config/locale/translations/ru.json');
|
||||
Locale::setLanguageFromJSON('sa', __DIR__ . '/config/locale/translations/sa.json');
|
||||
Locale::setLanguageFromJSON('sd', __DIR__ . '/config/locale/translations/sd.json');
|
||||
Locale::setLanguageFromJSON('si', __DIR__ . '/config/locale/translations/si.json');
|
||||
Locale::setLanguageFromJSON('sk', __DIR__ . '/config/locale/translations/sk.json');
|
||||
Locale::setLanguageFromJSON('sl', __DIR__ . '/config/locale/translations/sl.json');
|
||||
Locale::setLanguageFromJSON('sn', __DIR__ . '/config/locale/translations/sn.json');
|
||||
Locale::setLanguageFromJSON('sq', __DIR__ . '/config/locale/translations/sq.json');
|
||||
Locale::setLanguageFromJSON('sv', __DIR__ . '/config/locale/translations/sv.json');
|
||||
Locale::setLanguageFromJSON('ta', __DIR__ . '/config/locale/translations/ta.json');
|
||||
Locale::setLanguageFromJSON('te', __DIR__ . '/config/locale/translations/te.json');
|
||||
Locale::setLanguageFromJSON('th', __DIR__ . '/config/locale/translations/th.json');
|
||||
Locale::setLanguageFromJSON('tl', __DIR__ . '/config/locale/translations/tl.json');
|
||||
Locale::setLanguageFromJSON('tr', __DIR__ . '/config/locale/translations/tr.json');
|
||||
Locale::setLanguageFromJSON('uk', __DIR__ . '/config/locale/translations/uk.json');
|
||||
Locale::setLanguageFromJSON('ur', __DIR__ . '/config/locale/translations/ur.json');
|
||||
Locale::setLanguageFromJSON('vi', __DIR__ . '/config/locale/translations/vi.json');
|
||||
Locale::setLanguageFromJSON('zh-cn', __DIR__ . '/config/locale/translations/zh-cn.json');
|
||||
Locale::setLanguageFromJSON('zh-tw', __DIR__ . '/config/locale/translations/zh-tw.json');
|
||||
|
||||
$locales = Config::getParam('locale-codes', []);
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$code = $locale['code'];
|
||||
|
||||
$path = __DIR__ . '/config/locale/translations/' . $code . '.json';
|
||||
|
||||
if (!\file_exists($path)) {
|
||||
$path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
|
||||
if (!\file_exists($path)) {
|
||||
var_dump('Unable to find tralsnations for ' . $locale['code'] . ' so using en.json');
|
||||
$path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
|
||||
}
|
||||
}
|
||||
|
||||
Locale::setLanguageFromJSON($code, $path);
|
||||
}
|
||||
|
||||
\stream_context_set_default([ // Set global user agent and http settings
|
||||
'http' => [
|
||||
@@ -781,6 +730,10 @@ App::setResource('register', fn() => $register);
|
||||
|
||||
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
|
||||
|
||||
App::setResource('localeCodes', function () {
|
||||
return array_map(fn($locale) => $locale['code'], Config::getParam('locale-codes', []));
|
||||
});
|
||||
|
||||
// Queues
|
||||
App::setResource('events', fn() => new Event('', ''));
|
||||
App::setResource('audits', fn() => new Audit());
|
||||
|
||||
+39
-13
@@ -3,6 +3,7 @@
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
@@ -24,8 +25,10 @@ class MailsV1 extends Worker
|
||||
{
|
||||
global $register;
|
||||
|
||||
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
Console::info('Skipped mail processing. No SMTP server hostname has been set.');
|
||||
$smtp = $this->args['smtp'];
|
||||
|
||||
if (empty($smtp) && empty(App::getEnv('_APP_SMTP_HOST'))) {
|
||||
Console::info('Skipped mail processing. No SMTP configuration has been set.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,17 +40,7 @@ class MailsV1 extends Worker
|
||||
$from = $this->args['from'];
|
||||
|
||||
/** @var \PHPMailer\PHPMailer\PHPMailer $mail */
|
||||
$mail = $register->get('smtp');
|
||||
|
||||
// Set project mail
|
||||
/*$register->get('smtp')
|
||||
->setFrom(
|
||||
App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
|
||||
($project->getId() === 'console')
|
||||
? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'))
|
||||
: \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name')
|
||||
)
|
||||
);*/
|
||||
$mail = empty($smtp) ? $register->get('smtp') : $this->getMailer($smtp);
|
||||
|
||||
$mail->clearAddresses();
|
||||
$mail->clearAllRecipients();
|
||||
@@ -58,6 +51,9 @@ class MailsV1 extends Worker
|
||||
|
||||
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), (empty($from) ? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')) : $from));
|
||||
$mail->addAddress($recipient, $name);
|
||||
if (isset($smtp['replyTo'])) {
|
||||
$mail->addReplyTo($smtp['replyTo']);
|
||||
}
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
$mail->AltBody = \strip_tags($body);
|
||||
@@ -69,6 +65,36 @@ class MailsV1 extends Worker
|
||||
}
|
||||
}
|
||||
|
||||
protected function getMailer(array $smtp): PHPMailer
|
||||
{
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
$mail->isSMTP();
|
||||
|
||||
$username = $smtp['username'];
|
||||
$password = $smtp['password'];
|
||||
|
||||
$mail->XMailer = 'Appwrite Mailer';
|
||||
$mail->Host = $smtp['host'];
|
||||
$mail->Port = $smtp['port'];
|
||||
$mail->SMTPAuth = (!empty($username) && !empty($password));
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = $smtp['secure'] === 'tls';
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
|
||||
$from = \urldecode($smtp['senderName'] ?? App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$email = $smtp['senderEmail'] ?? App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
|
||||
$mail->setFrom($email, $from);
|
||||
$mail->addReplyTo($email, $from);
|
||||
|
||||
$mail->isHTML(true);
|
||||
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
}
|
||||
|
||||
+3
-3
@@ -43,13 +43,13 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.26.*",
|
||||
"utopia-php/abuse": "0.27.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.28.*",
|
||||
"utopia-php/audit": "0.29.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.37.*",
|
||||
"utopia-php/database": "0.38.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.28.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
|
||||
Generated
+21
-21
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0f20fb41e9b250b6763af1b734bb8d2d",
|
||||
"content-hash": "0299a46dacaa5ae6607931f91b459db4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -1802,23 +1802,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.26.0",
|
||||
"version": "0.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "fb73180f0588bc8826b85d433393b983bdc37cfa"
|
||||
"reference": "d1115f5843e903ffaba9c23e450b33c0fe265ae0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/fb73180f0588bc8826b85d433393b983bdc37cfa",
|
||||
"reference": "fb73180f0588bc8826b85d433393b983bdc37cfa",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/d1115f5843e903ffaba9c23e450b33c0fe265ae0",
|
||||
"reference": "d1115f5843e903ffaba9c23e450b33c0fe265ae0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.37.*"
|
||||
"utopia-php/database": "0.38.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1845,9 +1845,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.26.0"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.27.0"
|
||||
},
|
||||
"time": "2023-06-15T00:53:36+00:00"
|
||||
"time": "2023-07-15T00:53:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -1906,21 +1906,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.28.0",
|
||||
"version": "0.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "abf4124bec20b6ab3555869b64afe5b274e37165"
|
||||
"reference": "5318538f457bf73623629345c98ea06371ca5dd4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/abf4124bec20b6ab3555869b64afe5b274e37165",
|
||||
"reference": "abf4124bec20b6ab3555869b64afe5b274e37165",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/5318538f457bf73623629345c98ea06371ca5dd4",
|
||||
"reference": "5318538f457bf73623629345c98ea06371ca5dd4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.37.*"
|
||||
"utopia-php/database": "0.38.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1947,9 +1947,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.28.0"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.29.0"
|
||||
},
|
||||
"time": "2023-06-15T00:52:49+00:00"
|
||||
"time": "2023-07-15T00:51:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
@@ -2106,16 +2106,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.37.1",
|
||||
"version": "0.38.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "4035d3f7e3393385eabc7816055047659c6fb4d3"
|
||||
"reference": "59e4684cf87e03c12dab9240158c1dfc6888e534"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/4035d3f7e3393385eabc7816055047659c6fb4d3",
|
||||
"reference": "4035d3f7e3393385eabc7816055047659c6fb4d3",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/59e4684cf87e03c12dab9240158c1dfc6888e534",
|
||||
"reference": "59e4684cf87e03c12dab9240158c1dfc6888e534",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2156,9 +2156,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.37.1"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.38.0"
|
||||
},
|
||||
"time": "2023-06-15T06:36:27+00:00"
|
||||
"time": "2023-07-14T07:49:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
List of all locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
|
||||
+135
-1
@@ -12,6 +12,7 @@ class Mail extends Event
|
||||
protected string $name = '';
|
||||
protected string $subject = '';
|
||||
protected string $body = '';
|
||||
protected array $smtp = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -32,7 +33,7 @@ class Mail extends Event
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set team for the mail event.
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -133,6 +134,138 @@ class Mail extends Event
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP Host
|
||||
*
|
||||
* @param string $host
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpHost(string $host): self
|
||||
{
|
||||
$this->smtp['host'] = $host;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP port
|
||||
*
|
||||
* @param int port
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPort(int $port): self
|
||||
{
|
||||
$this->smtp['port'] = $port;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP username
|
||||
*
|
||||
* @param string $username
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpUsername(string $username): self
|
||||
{
|
||||
$this->smtp['username'];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP password
|
||||
*
|
||||
* @param string $password
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPassword(string $password): self
|
||||
{
|
||||
$this->smtp['password'];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP sender email
|
||||
*
|
||||
* @param string $senderEmail
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSenderEmail(string $senderEmail): self
|
||||
{
|
||||
$this->smtp['senderEmail'] = $senderEmail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP reply to
|
||||
*
|
||||
* @param string $replyTo
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpReplyTo(string $replyTo): self
|
||||
{
|
||||
$this->smtp['replyTo'] = $replyTo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpHost(): string
|
||||
{
|
||||
return $this->smtp['host'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP port
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSmtpPort(): int
|
||||
{
|
||||
return $this->smtp['port'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP username
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpUsername(): string
|
||||
{
|
||||
return $this->smtp['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP password
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpPassword(): string
|
||||
{
|
||||
return $this->smtp['password'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP sender email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSenderEmail(): string
|
||||
{
|
||||
return $this->smtp['senderEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP reply to
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpReplyTo(): string
|
||||
{
|
||||
return $this->smtp['replyTo'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the mails worker.
|
||||
*
|
||||
@@ -147,6 +280,7 @@ class Mail extends Event
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'body' => $this->body,
|
||||
'smtp' => $this->smtp,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,10 @@ class Exception extends \Exception
|
||||
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
public const PROJECT_KEY_EXPIRED = 'project_key_expired';
|
||||
|
||||
public const PROJECT_SMTP_CONFIG_INVALID = 'project_smtp_config_invalid';
|
||||
|
||||
public const PROJECT_TEMPLATE_DEFAULT_DELETION = 'project_template_default_deletion';
|
||||
|
||||
/** Webhooks */
|
||||
public const WEBHOOK_NOT_FOUND = 'webhook_not_found';
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Utopia\Config\Config;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
@@ -76,6 +78,24 @@ class V19 extends Migration
|
||||
|
||||
Console::log("Migrating Collection \"{$id}\"");
|
||||
|
||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
switch ($id) {
|
||||
case 'projects':
|
||||
try {
|
||||
/**
|
||||
* Create 'passwordHistory' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'smtp');
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'templates');
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'SMTP and Templates' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!in_array($id, ['files', 'collections'])) {
|
||||
$this->alterPermissionIndex($id);
|
||||
}
|
||||
@@ -98,6 +118,10 @@ class V19 extends Migration
|
||||
* Bump version number.
|
||||
*/
|
||||
$document->setAttribute('version', '1.4.0');
|
||||
|
||||
$document->setAttribute('smtp', []);
|
||||
$document->setAttribute('templates', []);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ use Appwrite\Utopia\Response\Model\Platform;
|
||||
use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Utopia\Response\Model\TemplateEmail;
|
||||
use Appwrite\Utopia\Response\Model\Token;
|
||||
use Appwrite\Utopia\Response\Model\Webhook;
|
||||
use Appwrite\Utopia\Response\Model\Preferences;
|
||||
@@ -72,9 +73,11 @@ use Appwrite\Utopia\Response\Model\HealthQueue;
|
||||
use Appwrite\Utopia\Response\Model\HealthStatus;
|
||||
use Appwrite\Utopia\Response\Model\HealthTime;
|
||||
use Appwrite\Utopia\Response\Model\HealthVersion;
|
||||
use Appwrite\Utopia\Response\Model\LocaleCode;
|
||||
use Appwrite\Utopia\Response\Model\Mock; // Keep last
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSMS;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
use Appwrite\Utopia\Response\Model\UsageDatabase;
|
||||
@@ -163,6 +166,8 @@ class Response extends SwooleResponse
|
||||
|
||||
// Locale
|
||||
public const MODEL_LOCALE = 'locale';
|
||||
public const MODEL_LOCALE_CODE = 'localeCode';
|
||||
public const MODEL_LOCALE_CODE_LIST = 'localeCodeList';
|
||||
public const MODEL_COUNTRY = 'country';
|
||||
public const MODEL_COUNTRY_LIST = 'countryList';
|
||||
public const MODEL_CONTINENT = 'continent';
|
||||
@@ -208,6 +213,8 @@ class Response extends SwooleResponse
|
||||
public const MODEL_DOMAIN_LIST = 'domainList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
public const MODEL_VARIABLE_LIST = 'variableList';
|
||||
public const MODEL_SMS_TEMPLATE = 'smsTemplate';
|
||||
public const MODEL_EMAIL_TEMPLATE = 'emailTemplate';
|
||||
|
||||
// Health
|
||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
@@ -280,6 +287,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
|
||||
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
|
||||
->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
|
||||
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
|
||||
// Entities
|
||||
->setModel(new Database())
|
||||
->setModel(new Collection())
|
||||
@@ -312,6 +320,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new Token())
|
||||
->setModel(new JWT())
|
||||
->setModel(new Locale())
|
||||
->setModel(new LocaleCode())
|
||||
->setModel(new File())
|
||||
->setModel(new Bucket())
|
||||
->setModel(new Team())
|
||||
@@ -348,6 +357,8 @@ class Response extends SwooleResponse
|
||||
->setModel(new UsageFunctions())
|
||||
->setModel(new UsageFunction())
|
||||
->setModel(new UsageProject())
|
||||
->setModel(new TemplateSMS())
|
||||
->setModel(new TemplateEmail())
|
||||
->setModel(new ConsoleVariables())
|
||||
// Verification
|
||||
// Recovery
|
||||
@@ -418,7 +429,7 @@ class Response extends SwooleResponse
|
||||
*/
|
||||
public function dynamic(Document $document, string $model): void
|
||||
{
|
||||
$output = $this->output($document, $model);
|
||||
$output = $this->output(new Document($document->getArrayCopy()), $model);
|
||||
|
||||
// If filter is set, parse the output
|
||||
if (self::hasFilter()) {
|
||||
@@ -459,14 +470,14 @@ class Response extends SwooleResponse
|
||||
*/
|
||||
public function output(Document $document, string $model): array
|
||||
{
|
||||
$data = $document;
|
||||
$data = new Document($document->getArrayCopy());
|
||||
$model = $this->getModel($model);
|
||||
$output = [];
|
||||
|
||||
$document = $model->filter($document);
|
||||
$data = $model->filter($document);
|
||||
|
||||
if ($model->isAny()) {
|
||||
$this->payload = $document->getArrayCopy();
|
||||
$this->payload = $data->getArrayCopy();
|
||||
|
||||
return $this->payload;
|
||||
}
|
||||
@@ -474,18 +485,18 @@ class Response extends SwooleResponse
|
||||
foreach ($model->getRules() as $key => $rule) {
|
||||
if (!$document->isSet($key) && $rule['required']) { // do not set attribute in response if not required
|
||||
if (\array_key_exists('default', $rule)) {
|
||||
$document->setAttribute($key, $rule['default']);
|
||||
$data->setAttribute($key, $rule['default']);
|
||||
} else {
|
||||
throw new Exception('Model ' . $model->getName() . ' is missing response key: ' . $key);
|
||||
}
|
||||
}
|
||||
|
||||
if ($rule['array']) {
|
||||
if (!is_array($data[$key])) {
|
||||
if (!is_array($document[$key])) {
|
||||
throw new Exception($key . ' must be an array of type ' . $rule['type']);
|
||||
}
|
||||
|
||||
foreach ($data[$key] as $index => $item) {
|
||||
foreach ($document[$key] as $index => $item) {
|
||||
if ($item instanceof Document) {
|
||||
if (\is_array($rule['type'])) {
|
||||
foreach ($rule['type'] as $type) {
|
||||
@@ -513,7 +524,7 @@ class Response extends SwooleResponse
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($data[$key] instanceof Document) {
|
||||
if ($document[$key] instanceof Document) {
|
||||
$data[$key] = $this->output($data[$key], $rule['type']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class LocaleCode extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('code', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Locale codes in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)',
|
||||
'default' => '',
|
||||
'example' => 'en-us',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Locale name',
|
||||
'default' => '',
|
||||
'example' => 'US',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'LocaleCode';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_LOCALE_CODE;
|
||||
}
|
||||
}
|
||||
@@ -173,6 +173,49 @@ class Project extends Model
|
||||
'example' => new \stdClass(),
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('smtpEnabled', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Status for custom SMTP',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
'array' => false
|
||||
])
|
||||
->addRule('smtpSender', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'SMTP sender email',
|
||||
'default' => '',
|
||||
'example' => 'john@appwrite.io',
|
||||
])
|
||||
->addRule('smtpHost', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'SMTP server host name',
|
||||
'default' => '',
|
||||
'example' => 'mail.appwrite.io',
|
||||
])
|
||||
->addRule('smtpPort', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'SMTP server port',
|
||||
'default' => '',
|
||||
'example' => 25,
|
||||
])
|
||||
->addRule('smtpUsername', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'SMTP server username',
|
||||
'default' => '',
|
||||
'example' => 'emailuser',
|
||||
])
|
||||
->addRule('smtpPassword', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'SMTP server password',
|
||||
'default' => '',
|
||||
'example' => 'securepassword',
|
||||
])
|
||||
->addRule('smtpSecure', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'SMTP server secure protocol',
|
||||
'default' => '',
|
||||
'example' => 'tls',
|
||||
])
|
||||
;
|
||||
|
||||
$services = Config::getParam('services', []);
|
||||
@@ -238,6 +281,16 @@ class Project extends Model
|
||||
*/
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
// SMTP
|
||||
$smtp = $document->getAttribute('smtp', []);
|
||||
$document->setAttribute('smtpEnabled', $smtp['enabled'] ?? false);
|
||||
$document->setAttribute('smtpSender', $smtp['sender'] ?? '');
|
||||
$document->setAttribute('smtpHost', $smtp['host'] ?? '');
|
||||
$document->setAttribute('smtpPort', $smtp['port'] ?? '');
|
||||
$document->setAttribute('smtpUsername', $smtp['username'] ?? '');
|
||||
$document->setAttribute('smtpPassword', $smtp['password'] ?? '');
|
||||
$document->setAttribute('smtpSecure', $smtp['secure'] ?? '');
|
||||
|
||||
// Services
|
||||
$values = $document->getAttribute('services', []);
|
||||
$services = Config::getParam('services', []);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
abstract class Template extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Template type',
|
||||
'default' => '',
|
||||
'example' => 'verification',
|
||||
])
|
||||
->addRule('locale', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Template locale',
|
||||
'default' => '',
|
||||
'example' => 'en_us',
|
||||
])
|
||||
->addRule('message', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Template message',
|
||||
'default' => '',
|
||||
'example' => 'Click on the link to verify your account.',
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class TemplateEmail extends Template
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this
|
||||
->addRule('senderName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Name of the sender',
|
||||
'default' => '',
|
||||
'example' => 'My User',
|
||||
])
|
||||
->addRule('senderEmail', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Email of the sender',
|
||||
'default' => '',
|
||||
'example' => 'mail@appwrite.io',
|
||||
])
|
||||
->addRule('replyTo', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Reply to email address',
|
||||
'default' => '',
|
||||
'example' => 'emails@appwrite.io',
|
||||
])
|
||||
->addRule('subject', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Email subject',
|
||||
'default' => '',
|
||||
'example' => 'Please verify your email address',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'EmailTemplate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_EMAIL_TEMPLATE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class TemplateSMS extends Template
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'SmsTemplate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_SMS_TEMPLATE;
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,12 @@ class User extends Model
|
||||
'default' => new \stdClass(),
|
||||
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ trait AccountBase
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $name);
|
||||
$this->assertEquals($response['body']['labels'], []);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -127,6 +129,21 @@ trait AccountBase
|
||||
$sessionId = $response['body']['$id'];
|
||||
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
// apiKey is only available in custom client test
|
||||
$apiKey = $this->getProject()['apiKey'];
|
||||
if (!empty($apiKey)) {
|
||||
$userId = $response['body']['userId'];
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $apiKey,
|
||||
]));
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
@@ -207,6 +224,8 @@ trait AccountBase
|
||||
$this->assertEquals(true, $dateValidator->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $name);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
|
||||
@@ -369,6 +369,20 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
$session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
\usleep(1000 * 30); // wait for 30ms to let the shutdown update accessedAt
|
||||
|
||||
$apiKey = $this->getProject()['apiKey'];
|
||||
$userId = $response['body']['userId'];
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $apiKey,
|
||||
]));
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['accessedAt']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
@@ -235,7 +235,7 @@ trait LocaleBase
|
||||
$response = $this->client->call(Client::METHOD_GET, '/locale/countries', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-locale' => $lang,
|
||||
'x-appwrite-locale' => $lang['code'],
|
||||
]);
|
||||
|
||||
if (!\is_array($response['body']['countries'])) {
|
||||
@@ -243,30 +243,22 @@ trait LocaleBase
|
||||
}
|
||||
|
||||
foreach ($response['body']['countries'] as $i => $code) {
|
||||
$this->assertContains($code['code'], $defaultCountries, $code['code'] . ' country should be removed from ' . $lang);
|
||||
$this->assertContains($code['code'], $defaultCountries, $code['code'] . ' country should be removed from ' . $lang['code']);
|
||||
}
|
||||
|
||||
// foreach (array_keys($defaultCountries) as $i => $code) {
|
||||
// $this->assertArrayHasKey($code, $response['body']['countries'], $code . ' country is missing from ' . $lang . ' (total: ' . count($response['body']['countries']) . ')');
|
||||
// }
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals(194, $response['body']['total']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/locale/continents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-locale' => $lang,
|
||||
'x-appwrite-locale' => $lang['code'],
|
||||
]);
|
||||
|
||||
foreach ($response['body']['continents'] as $i => $code) {
|
||||
$this->assertContains($code['code'], $defaultContinents, $code['code'] . ' continent should be removed from ' . $lang);
|
||||
$this->assertContains($code['code'], $defaultContinents, $code['code'] . ' continent should be removed from ' . $lang['code']);
|
||||
}
|
||||
|
||||
// foreach (array_keys($defaultContinents) as $i => $code) {
|
||||
// $this->assertArrayHasKey($code, $response['body']['continents'], $code . ' continent is missing from ' . $lang . ' (total: ' . count($response['body']['continents']) . ')');
|
||||
// }
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals(7, $response['body']['total']);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ class ProjectsConsoleClientTest extends Scope
|
||||
use ProjectConsole;
|
||||
use SideClient;
|
||||
|
||||
/** @group projectsCRUD */
|
||||
/**
|
||||
* @group smtpAndTemplates
|
||||
* @group projectsCRUD */
|
||||
public function testCreateProject(): array
|
||||
{
|
||||
/**
|
||||
@@ -529,6 +531,156 @@ class ProjectsConsoleClientTest extends Scope
|
||||
return ['projectId' => $projectId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group smtpAndTemplates
|
||||
* @depends testCreateProject
|
||||
*/
|
||||
public function testUpdateProjectSMTP($data): array
|
||||
{
|
||||
$id = $data['projectId'];
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'enabled' => true,
|
||||
'sender' => 'mailer@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => 'user',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertTrue($response['body']['smtpEnabled']);
|
||||
$this->assertEquals('mailer@appwrite.io', $response['body']['smtpSender']);
|
||||
$this->assertEquals('maildev', $response['body']['smtpHost']);
|
||||
$this->assertEquals(1025, $response['body']['smtpPort']);
|
||||
$this->assertEquals('user', $response['body']['smtpUsername']);
|
||||
$this->assertEquals('password', $response['body']['smtpPassword']);
|
||||
$this->assertEquals('', $response['body']['smtpSecure']);
|
||||
|
||||
/** Test Reading Project */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertTrue($response['body']['smtpEnabled']);
|
||||
$this->assertEquals('mailer@appwrite.io', $response['body']['smtpSender']);
|
||||
$this->assertEquals('maildev', $response['body']['smtpHost']);
|
||||
$this->assertEquals(1025, $response['body']['smtpPort']);
|
||||
$this->assertEquals('user', $response['body']['smtpUsername']);
|
||||
$this->assertEquals('password', $response['body']['smtpPassword']);
|
||||
$this->assertEquals('', $response['body']['smtpSecure']);
|
||||
|
||||
/** Reset */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'enabled' => false,
|
||||
'sender' => 'mailer@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => 'user',
|
||||
'password' => 'password',
|
||||
]);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group smtpAndTemplates
|
||||
* @depends testCreateProject */
|
||||
public function testUpdateTemplates($data): array
|
||||
{
|
||||
$id = $data['projectId'];
|
||||
|
||||
/** Get Default Email Template */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/templates/email/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('Account Verification', $response['body']['subject']);
|
||||
$this->assertEquals('Project Test 2 Team', $response['body']['senderName']);
|
||||
$this->assertEquals('team@appwrite.io', $response['body']['senderEmail']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertMatchesRegularExpression('/<!doctype html>/', $response['body']['message']);
|
||||
|
||||
/** Update Email template */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/templates/email/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'subject' => 'Please verify your email',
|
||||
'message' => 'Please verify your email {{url}}',
|
||||
'senderName' => 'Appwrite Custom',
|
||||
'senderEmail' => 'custom@appwrite.io',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('Please verify your email', $response['body']['subject']);
|
||||
$this->assertEquals('Appwrite Custom', $response['body']['senderName']);
|
||||
$this->assertEquals('custom@appwrite.io', $response['body']['senderEmail']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertEquals('Please verify your email {{url}}', $response['body']['message']);
|
||||
|
||||
/** Get Updated Email Template */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/templates/email/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('Please verify your email', $response['body']['subject']);
|
||||
$this->assertEquals('Appwrite Custom', $response['body']['senderName']);
|
||||
$this->assertEquals('custom@appwrite.io', $response['body']['senderEmail']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertEquals('Please verify your email {{url}}', $response['body']['message']);
|
||||
|
||||
/** Get Default SMS Template */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/templates/sms/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertEquals('{{token}}', $response['body']['message']);
|
||||
|
||||
/** Update SMS template */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/templates/sms/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'message' => 'Please verify your email {{token}}',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertEquals('Please verify your email {{token}}', $response['body']['message']);
|
||||
|
||||
/** Get Updated SMS Template */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/templates/sms/verification/en-us', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
$this->assertEquals('Please verify your email {{token}}', $response['body']['message']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** @depends testGetProjectUsage */
|
||||
public function testUpdateProjectAuthDuration($data): array
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ class WebhooksCustomClientTest extends Scope
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-User-Id'], $id);
|
||||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertEquals($webhook['data']['name'], $name);
|
||||
$dateValidator = new DatetimeValidator();
|
||||
@@ -195,7 +195,7 @@ class WebhooksCustomClientTest extends Scope
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-User-Id'], $id);
|
||||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$dateValidator = new DatetimeValidator();
|
||||
@@ -744,7 +744,7 @@ class WebhooksCustomClientTest extends Scope
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-User-Id'], $id);
|
||||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$this->assertNotEmpty($webhook['data']['secret']);
|
||||
@@ -923,7 +923,7 @@ class WebhooksCustomClientTest extends Scope
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? '', $userUid);
|
||||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$this->assertNotEmpty($webhook['data']['teamId']);
|
||||
|
||||
Reference in new issue
Block a user