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:
@@ -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}}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+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