\ No newline at end of file
diff --git a/app/config/locale/templates/pa.email.auth.invitation.tpl b/app/config/locale/templates/pa.email.auth.invitation.tpl
new file mode 100644
index 0000000000..e7643abc17
--- /dev/null
+++ b/app/config/locale/templates/pa.email.auth.invitation.tpl
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/app/config/locale/templates/pa.email.auth.recovery.tpl b/app/config/locale/templates/pa.email.auth.recovery.tpl
new file mode 100644
index 0000000000..fab1d290f5
--- /dev/null
+++ b/app/config/locale/templates/pa.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ Kliknite na ovaj link da verifikujete vašu email adresu.
+
+{{cta}}
+
+ Ukoliko niste zatražili verifikaciju email adrese, ignorišite ovaj email.
+
+
+ Hvala,
+
+ {{project}} tim
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ba.email.auth.invitation.tpl b/app/config/locale/translations/templates/ba.email.auth.invitation.tpl
new file mode 100644
index 0000000000..ea22ec186a
--- /dev/null
+++ b/app/config/locale/translations/templates/ba.email.auth.invitation.tpl
@@ -0,0 +1,14 @@
+
+ Zdravo,
+
+
+ Dobili ste ovaj email jer {{owner}} vas poziva da postanete član {{team}} tima na {{project}}.
+
+{{cta}}
+
+ Ukoliko niste zainteresovani, ignorišite ovu poruku.
+
+ Hvala,
+
+ {{project}} tim
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ba.email.auth.recovery.tpl b/app/config/locale/translations/templates/ba.email.auth.recovery.tpl
new file mode 100644
index 0000000000..368ab87475
--- /dev/null
+++ b/app/config/locale/translations/templates/ba.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ Zdravo {{name}},
+
+
+ Kliknite na ovaj link da resetujete vašu {{project}} lozinku.
+
+{{cta}}
+
+ Ukoliko niste zatražili verifikaciju ove adrese, ignorišite ovaj email.
+
+
+ Hvala,
+
+ {{project}} tim
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/be.email.auth.confirm.tpl b/app/config/locale/translations/templates/be.email.auth.confirm.tpl
new file mode 100644
index 0000000000..64ff468889
--- /dev/null
+++ b/app/config/locale/translations/templates/be.email.auth.confirm.tpl
@@ -0,0 +1,15 @@
+
+ Прывітанне {{name}},
+
+
+ Перайдзіце па гэтай спасылцы, каб пацвердзіць свой адрас электроннай пошты.
+
+{{cta}}
+
+ Калі вы не прасілі спраўдзіць гэты адрас, вы можаце праігнараваць гэтае паведамленне.
+
+
+ Дзякуем,
+
+ Каманда {{project}}
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/be.email.auth.invitation.tpl b/app/config/locale/translations/templates/be.email.auth.invitation.tpl
new file mode 100644
index 0000000000..56319b1f91
--- /dev/null
+++ b/app/config/locale/translations/templates/be.email.auth.invitation.tpl
@@ -0,0 +1,15 @@
+
+ Прывітанне,
+
+
+ Гэта паведамленне было адпраўлена вам, таму што {{owner}} хацеў запрасіць вас стаць членам каманды {{team}} у {{project}}.
+
+{{cta}}
+
+ Калі вам гэта не цікава, вы можаце праігнараваць гэтае паведамленне.
+
+
+ Дзякуем,
+
+ Каманда {{project}}
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/be.email.auth.recovery.tpl b/app/config/locale/translations/templates/be.email.auth.recovery.tpl
new file mode 100644
index 0000000000..4e87165f28
--- /dev/null
+++ b/app/config/locale/translations/templates/be.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ Прывітанне {{name}},
+
+
+ Перайдзіце па гэтай спасылцы, каб скінуць пароль для {{project}}.
+
+{{cta}}
+
+ Калі вы не прасілі скінуць пароль, вы можаце праігнараваць гэта паведамленне.
+
+
+ Дзякуем,
+
+ Каманда {{project}}
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/bg.email.auth.confirm.tpl b/app/config/locale/translations/templates/bg.email.auth.confirm.tpl
new file mode 100644
index 0000000000..dc0c1df182
--- /dev/null
+++ b/app/config/locale/translations/templates/bg.email.auth.confirm.tpl
@@ -0,0 +1,15 @@
+
+ Здравейте {{name}},
+
+
+ Следвайте този линк, за да потвърдите имейл адреса си.
+
+{{cta}}
+
+ Ако не сте поискали да потвърдите този адрес, можете да игнорирате това съобщение.
+
+
+ Поздрави,
+
+ {{project}} екип
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/bg.email.auth.invitation.tpl b/app/config/locale/translations/templates/bg.email.auth.invitation.tpl
new file mode 100644
index 0000000000..d5157f01c6
--- /dev/null
+++ b/app/config/locale/translations/templates/bg.email.auth.invitation.tpl
@@ -0,0 +1,14 @@
+
+ Здравейте,
+
+
+ Този имейл Ви беше изпратен, защото {{owner}} искаше да Ви покани да станете член на {{team}} екип в {{project}}.
+
+{{cta}}
+
+ Ако не се интересувате, можете да игнорирате това съобщение.
+
+ Поздрави,
+
+ {{project}} екип
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/bg.email.auth.recovery.tpl b/app/config/locale/translations/templates/bg.email.auth.recovery.tpl
new file mode 100644
index 0000000000..007f7f05c3
--- /dev/null
+++ b/app/config/locale/translations/templates/bg.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ Здравейте {{name}},
+
+
+ Последвайте този линк за да промените своята {{project}} парола.
+
+{{cta}}
+
+ Ако не сте поискали да потвърдите този адрес, можете да игнорирате това съобщение.
+
+
+ Поздрави,
+
+ {{project}} екип
+
\ No newline at end of file
diff --git a/app/config/locales/templates/bn.email.auth.confirm.tpl b/app/config/locale/translations/templates/bn.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/bn.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/bn.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/bn.email.auth.invitation.tpl b/app/config/locale/translations/templates/bn.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/bn.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/bn.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/bn.email.auth.recovery.tpl b/app/config/locale/translations/templates/bn.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/bn.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/bn.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/cat.email.auth.confirm.tpl b/app/config/locale/translations/templates/cat.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/cat.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/cat.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/cat.email.auth.invitation.tpl b/app/config/locale/translations/templates/cat.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/cat.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/cat.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/cat.email.auth.recovery.tpl b/app/config/locale/translations/templates/cat.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/cat.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/cat.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/cz.email.auth.confirm.tpl b/app/config/locale/translations/templates/cz.email.auth.confirm.tpl
similarity index 71%
rename from app/config/locales/templates/cz.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/cz.email.auth.confirm.tpl
index e72b357a48..9e5fab32cc 100644
--- a/app/config/locales/templates/cz.email.auth.confirm.tpl
+++ b/app/config/locale/translations/templates/cz.email.auth.confirm.tpl
@@ -2,14 +2,14 @@
Ahoj {{name}},
- Kliknutím na tento odkaz ověřte svou e-mailovou adresu.
+ Klepnutím na tento odkaz ověřte svou e-mailovou adresu.
{{cta}}
Pokud jste nepožádali o ověření této adresy, můžete tuto zprávu ignorovat.
- dík,
+ Dík,
{{project}} tým
diff --git a/app/config/locales/templates/cz.email.auth.invitation.tpl b/app/config/locale/translations/templates/cz.email.auth.invitation.tpl
similarity index 77%
rename from app/config/locales/templates/cz.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/cz.email.auth.invitation.tpl
index d5353e71cf..0dbfa6570b 100644
--- a/app/config/locales/templates/cz.email.auth.invitation.tpl
+++ b/app/config/locale/translations/templates/cz.email.auth.invitation.tpl
@@ -2,7 +2,7 @@
Ahoj,
- Tento e-mail vám byl zaslán, protože vás {{owner}} chtěl pozvat, abyste se stali členem týmu v týmu {{team}} v {{project}}.
+ Tento e-mail vám byl zaslán, protože vás {{owner}} chtěl pozvat, abyste se stali členem týmu {{team}} v {{project}}.
Klepnutím na tento odkaz se připojíte k týmu {{team}}:
diff --git a/app/config/locales/templates/cz.email.auth.recovery.tpl b/app/config/locale/translations/templates/cz.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/cz.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/cz.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/de.email.auth.confirm.tpl b/app/config/locale/translations/templates/de.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/de.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/de.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/de.email.auth.invitation.tpl b/app/config/locale/translations/templates/de.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/de.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/de.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/de.email.auth.recovery.tpl b/app/config/locale/translations/templates/de.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/de.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/de.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/en.email.auth.confirm.tpl b/app/config/locale/translations/templates/en.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/en.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/en.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/en.email.auth.invitation.tpl b/app/config/locale/translations/templates/en.email.auth.invitation.tpl
similarity index 70%
rename from app/config/locales/templates/en.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/en.email.auth.invitation.tpl
index 70ea922c0c..f801a5d3c0 100644
--- a/app/config/locales/templates/en.email.auth.invitation.tpl
+++ b/app/config/locale/translations/templates/en.email.auth.invitation.tpl
@@ -2,7 +2,7 @@
Hello,
- This mail was sent to you because {{owner}} wanted to invite you to become a team member at the {{team}} team over at {{project}}.
+ This mail was sent to you because {{owner}} wanted to invite you to become a member of the {{team}} team at {{project}}.
{{cta}}
@@ -11,4 +11,4 @@
Thanks,
{{project}} team
-
\ No newline at end of file
+
diff --git a/app/config/locales/templates/en.email.auth.recovery.tpl b/app/config/locale/translations/templates/en.email.auth.recovery.tpl
similarity index 71%
rename from app/config/locales/templates/en.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/en.email.auth.recovery.tpl
index a4a982f46f..57bdeb18a5 100644
--- a/app/config/locales/templates/en.email.auth.recovery.tpl
+++ b/app/config/locale/translations/templates/en.email.auth.recovery.tpl
@@ -6,10 +6,10 @@
{{cta}}
- If you didn’t ask to verify this address, you can ignore this message.
+ If you didn’t ask to reset your password, you can ignore this message.
Thanks,
{{project}} team
-
\ No newline at end of file
+
diff --git a/app/config/locales/templates/es.email.auth.confirm.tpl b/app/config/locale/translations/templates/es.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/es.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/es.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/es.email.auth.invitation.tpl b/app/config/locale/translations/templates/es.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/es.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/es.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/es.email.auth.recovery.tpl b/app/config/locale/translations/templates/es.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/es.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/es.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/fi.email.auth.confirm.tpl b/app/config/locale/translations/templates/fi.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/fi.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/fi.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/fi.email.auth.invitation.tpl b/app/config/locale/translations/templates/fi.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/fi.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/fi.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/fi.email.auth.recovery.tpl b/app/config/locale/translations/templates/fi.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/fi.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/fi.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/fo.email.auth.confirm.tpl b/app/config/locale/translations/templates/fo.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/fo.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/fo.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/fo.email.auth.invitation.tpl b/app/config/locale/translations/templates/fo.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/fo.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/fo.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/fo.email.auth.recovery.tpl b/app/config/locale/translations/templates/fo.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/fo.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/fo.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/fr.email.auth.confirm.tpl b/app/config/locale/translations/templates/fr.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/fr.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/fr.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/fr.email.auth.invitation.tpl b/app/config/locale/translations/templates/fr.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/fr.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/fr.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/fr.email.auth.recovery.tpl b/app/config/locale/translations/templates/fr.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/fr.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/fr.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/gr.email.auth.confirm.tpl b/app/config/locale/translations/templates/gr.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/gr.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/gr.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/gr.email.auth.invitation.tpl b/app/config/locale/translations/templates/gr.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/gr.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/gr.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/gr.email.auth.recovery.tpl b/app/config/locale/translations/templates/gr.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/gr.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/gr.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/gu.email.auth.confirm.tpl b/app/config/locale/translations/templates/gu.email.auth.confirm.tpl
new file mode 100644
index 0000000000..d2223555d1
--- /dev/null
+++ b/app/config/locale/translations/templates/gu.email.auth.confirm.tpl
@@ -0,0 +1,16 @@
+
+
+નમસ્તે {{name}},
+
+
+ તમારા ઇમેઇલ સરનામાંને ચકાસવા માટે આ લિંક પર ક્લિક કરો.
+
+{{cta}}
+
+ જો તમને આ ઇમેઇલને ચકાસવા માટે પૂછવામાં નહીં આવે તો તમે આ સંદેશને અવગણી શકો છો.
+
+
+ આભાર,
+
+ {{project}} ટીમ
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/gu.email.auth.invitation.tpl b/app/config/locale/translations/templates/gu.email.auth.invitation.tpl
new file mode 100644
index 0000000000..606dc25bb2
--- /dev/null
+++ b/app/config/locale/translations/templates/gu.email.auth.invitation.tpl
@@ -0,0 +1,18 @@
+
+ નમસ્તે,
+
+
+ આ મેલ તમને મોકલ્યો હતો કારણ કે {{owner}} તને {{project}} આ કારણ થી {{team}} અહીં સભ્ય બનવા માટે આમંત્રણ આપવાની ઇચ્છા છે.
+
+
+ ટીમ {{team}} જોડાવા માટે આ લિંક પર ક્લિક કરો :
+
+{{cta}}
+
+ જો તમને રુચિ ન હોય તો તમે આ સંદેશને અવગણી શકો છો.
+
+
+ આભાર,
+
+ {{project}} ટીમ
+
diff --git a/app/config/locale/translations/templates/gu.email.auth.recovery.tpl b/app/config/locale/translations/templates/gu.email.auth.recovery.tpl
new file mode 100644
index 0000000000..cdc4558de6
--- /dev/null
+++ b/app/config/locale/translations/templates/gu.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ નમસ્તે {{name}},
+
+
+ {{project}} પાસવર્ડ ફરીથી સેટ કરવા માટે આ લિંક પર ક્લિક કરો.
+
+{{cta}}
+
+ જો તમને તમારો પાસવર્ડ ફરીથી સેટ કરવાનું કહેવામાં ન આવે, તો તમે આ સંદેશને અવગણી શકો છો.
+
+
+ આભાર,
+
+ {{project}} ટીમ
+
diff --git a/app/config/locales/templates/he.email.auth.confirm.tpl b/app/config/locale/translations/templates/he.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/he.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/he.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/he.email.auth.invitation.tpl b/app/config/locale/translations/templates/he.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/he.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/he.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/he.email.auth.recovery.tpl b/app/config/locale/translations/templates/he.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/he.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/he.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/hi.email.auth.confirm.tpl b/app/config/locale/translations/templates/hi.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/hi.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/hi.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/hi.email.auth.invitation.tpl b/app/config/locale/translations/templates/hi.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/hi.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/hi.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/hi.email.auth.recovery.tpl b/app/config/locale/translations/templates/hi.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/hi.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/hi.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/hu.email.auth.confirm.tpl b/app/config/locale/translations/templates/hu.email.auth.confirm.tpl
new file mode 100644
index 0000000000..6aec157846
--- /dev/null
+++ b/app/config/locale/translations/templates/hu.email.auth.confirm.tpl
@@ -0,0 +1,15 @@
+
+ Kedves {{name}}!
+
+
+ Kérjük, e-mail címe megerősítéséhez kattintson az alábbi hivatkozásra:
+
+{{cta}}
+
+ Ha nem kérte az e-mail cím megerősítését, kérjük, tekintse ezt az üzenetet tárgytalannak.
+
+
+ Köszönettel:
+
+ {{project}} csapat
+
diff --git a/app/config/locale/translations/templates/hu.email.auth.invitation.tpl b/app/config/locale/translations/templates/hu.email.auth.invitation.tpl
new file mode 100644
index 0000000000..0c07be924d
--- /dev/null
+++ b/app/config/locale/translations/templates/hu.email.auth.invitation.tpl
@@ -0,0 +1,15 @@
+
+ Üdvözöljük!
+
+
+ Azért küldtük ezt az e-mailt, mert {{owner}} szeretné meghívni Önt a {{team}} csapatba a {{project}} projektben.
+
+{{cta}}
+
+ Ha nem szeretne csatlakozni, kérjük, tekintse ezt az üzenetet tárgytalannak.
+
+
+ Köszönettel:
+
+ {{project}} csapat
+
diff --git a/app/config/locale/translations/templates/hu.email.auth.recovery.tpl b/app/config/locale/translations/templates/hu.email.auth.recovery.tpl
new file mode 100644
index 0000000000..2919b0ffcf
--- /dev/null
+++ b/app/config/locale/translations/templates/hu.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ Kedves {{name}}!
+
+
+ {{project}} jelszava visszaállításához kérjük, kattintson az alábbi linkre:
+
+{{cta}}
+
+ Ha nem kérte jelszava visszaállítását, kérjük, tekintse ezt az üzenetet tárgytalannak.
+
+
+ Köszönettel:
+
+ {{project}} csapat
+
diff --git a/app/config/locales/templates/hy.email.auth.confirm.tpl b/app/config/locale/translations/templates/hy.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/hy.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/hy.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/hy.email.auth.invitation.tpl b/app/config/locale/translations/templates/hy.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/hy.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/hy.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/hy.email.auth.recovery.tpl b/app/config/locale/translations/templates/hy.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/hy.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/hy.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/id.email.auth.confirm.tpl b/app/config/locale/translations/templates/id.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/id.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/id.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/id.email.auth.invitation.tpl b/app/config/locale/translations/templates/id.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/id.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/id.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/id.email.auth.recovery.tpl b/app/config/locale/translations/templates/id.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/id.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/id.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/is.email.auth.confirm.tpl b/app/config/locale/translations/templates/is.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/is.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/is.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/is.email.auth.invitation.tpl b/app/config/locale/translations/templates/is.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/is.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/is.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/is.email.auth.recovery.tpl b/app/config/locale/translations/templates/is.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/is.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/is.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/it.email.auth.confirm.tpl b/app/config/locale/translations/templates/it.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/it.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/it.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/it.email.auth.invitation.tpl b/app/config/locale/translations/templates/it.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/it.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/it.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/it.email.auth.recovery.tpl b/app/config/locale/translations/templates/it.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/it.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/it.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ja.email.auth.confirm.tpl b/app/config/locale/translations/templates/ja.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ja.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ja.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ja.email.auth.invitation.tpl b/app/config/locale/translations/templates/ja.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ja.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ja.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ja.email.auth.recovery.tpl b/app/config/locale/translations/templates/ja.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ja.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ja.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/jv.email.auth.confirm.tpl b/app/config/locale/translations/templates/jv.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/jv.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/jv.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/jv.email.auth.invitation.tpl b/app/config/locale/translations/templates/jv.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/jv.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/jv.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/jv.email.auth.recovery.tpl b/app/config/locale/translations/templates/jv.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/jv.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/jv.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/ka.email.auth.confirm.tpl b/app/config/locale/translations/templates/ka.email.auth.confirm.tpl
new file mode 100644
index 0000000000..62ac8d924a
--- /dev/null
+++ b/app/config/locale/translations/templates/ka.email.auth.confirm.tpl
@@ -0,0 +1,15 @@
+
+ ಹಲೋ {{name}},
+
+
+ ನಿಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಪರಿಶೀಲಿಸಲು ಈ ಲಿಂಕ್ ಅನ್ನು ಅನುಸರಿಸಿ.
+
+{{cta}}
+
+ ಈ ವಿಳಾಸವನ್ನು ಪರಿಶೀಲಿಸಲು ನೀವು ಕೇಳದಿದ್ದರೆ, ನೀವು ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಬಹುದು.
+
+
+ ಧನ್ಯವಾದಗಳು,
+
+ {{project}} ತಂಡ
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ka.email.auth.invitation.tpl b/app/config/locale/translations/templates/ka.email.auth.invitation.tpl
new file mode 100644
index 0000000000..86507a1d67
--- /dev/null
+++ b/app/config/locale/translations/templates/ka.email.auth.invitation.tpl
@@ -0,0 +1,14 @@
+
+ ಹಲೋ,
+
+
+ {{project}} ಕ್ಕೆ {{team}} ತಂಡದ ಓವರ್ನಲ್ಲಿ ತಂಡದ ಸದಸ್ಯರಾಗಲು {{owner}} ನಿಮ್ಮನ್ನು ಆಹ್ವಾನಿಸಲು ಬಯಸಿದ್ದರಿಂದ ಈ ಮೇಲ್ ಅನ್ನು ನಿಮಗೆ ಕಳುಹಿಸಲಾಗಿದೆ.
+
+{{cta}}
+
+ ನಿಮಗೆ ಆಸಕ್ತಿ ಇಲ್ಲದಿದ್ದರೆ, ನೀವು ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಬಹುದು. P>
+
+ ಧನ್ಯವಾದಗಳು,
+
+ {{project}} ತಂಡ
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ka.email.auth.recovery.tpl b/app/config/locale/translations/templates/ka.email.auth.recovery.tpl
new file mode 100644
index 0000000000..9d90ffc10b
--- /dev/null
+++ b/app/config/locale/translations/templates/ka.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ ಹಲೋ {{name}},
+
+
+ ನಿಮ್ಮ {{project}} ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಲು ಈ ಲಿಂಕ್ ಅನ್ನು ಅನುಸರಿಸಿ.
+
+{{cta}}
+
+ ಈ ವಿಳಾಸವನ್ನು ಪರಿಶೀಲಿಸಲು ನೀವು ಕೇಳದಿದ್ದರೆ, ನೀವು ಈ ಸಂದೇಶವನ್ನು ನಿರ್ಲಕ್ಷಿಸಬಹುದು.
+
+
+ ಧನ್ಯವಾದಗಳು,
+
+ {{project}} ತಂಡ
+
\ No newline at end of file
diff --git a/app/config/locales/templates/km.email.auth.confirm.tpl b/app/config/locale/translations/templates/km.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/km.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/km.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/km.email.auth.invitation.tpl b/app/config/locale/translations/templates/km.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/km.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/km.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/km.email.auth.recovery.tpl b/app/config/locale/translations/templates/km.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/km.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/km.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ko.email.auth.confirm.tpl b/app/config/locale/translations/templates/ko.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ko.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ko.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ko.email.auth.invitation.tpl b/app/config/locale/translations/templates/ko.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ko.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ko.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ko.email.auth.recovery.tpl b/app/config/locale/translations/templates/ko.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ko.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ko.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/lt.email.auth.confirm.tpl b/app/config/locale/translations/templates/lt.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/lt.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/lt.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/lt.email.auth.invitation.tpl b/app/config/locale/translations/templates/lt.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/lt.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/lt.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/lt.email.auth.recovery.tpl b/app/config/locale/translations/templates/lt.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/lt.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/lt.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ml.email.auth.confirm.tpl b/app/config/locale/translations/templates/ml.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ml.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ml.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ml.email.auth.invitation.tpl b/app/config/locale/translations/templates/ml.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ml.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ml.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ml.email.auth.recovery.tpl b/app/config/locale/translations/templates/ml.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ml.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ml.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/mr.email.auth.confirm.tpl b/app/config/locale/translations/templates/mr.email.auth.confirm.tpl
new file mode 100644
index 0000000000..9ca8e0a5b7
--- /dev/null
+++ b/app/config/locale/translations/templates/mr.email.auth.confirm.tpl
@@ -0,0 +1,16 @@
+
+
+नमस्कार {{name}},
+
+
+ आपला ईमेल पत्ता वेरीफाई करण्यासाठी या लिंक वर क्लिक करा.
+
+{{cta}}
+
+ आपण हे ईमेल वेरीफाई करण्यास सांगितले नसल्यास आपण या संदेशाकडे दुर्लक्ष करू शकता.
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
diff --git a/app/config/locale/translations/templates/mr.email.auth.invitation.tpl b/app/config/locale/translations/templates/mr.email.auth.invitation.tpl
new file mode 100644
index 0000000000..e9507ec409
--- /dev/null
+++ b/app/config/locale/translations/templates/mr.email.auth.invitation.tpl
@@ -0,0 +1,18 @@
+
+ नमस्कार,
+
+
+ हा मेल आपल्याला पाठविला गेला कारण {{owner}} आपल्याला {{project}} या कारणास्तव {{team}} येथे सदस्य होण्यासाठी आमंत्रित करू इच्छित होते.
+
+
+ टीम {{team}}मध्ये सामील होण्यासाठी या लिंक वर क्लिक करा :
+
+{{cta}}
+
+ आपल्याला स्वारस्य नसल्यास आपण या संदेशाकडे दुर्लक्ष करू शकता.
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
diff --git a/app/config/locale/translations/templates/mr.email.auth.recovery.tpl b/app/config/locale/translations/templates/mr.email.auth.recovery.tpl
new file mode 100644
index 0000000000..ea93107ea2
--- /dev/null
+++ b/app/config/locale/translations/templates/mr.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ नमस्कार {{name}},
+
+
+ {{project}} पासवर्ड रीसेट करण्यासाठी या लिंक वर क्लिक करा.
+
+{{cta}}
+
+ आपण आपला पासवर्ड रीसेट करण्यास सांगितले नसेल तर, आपण हा संदेश दुर्लक्षित करू शकता.
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
diff --git a/app/config/locales/templates/my.email.auth.confirm.tpl b/app/config/locale/translations/templates/my.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/my.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/my.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/my.email.auth.invitation.tpl b/app/config/locale/translations/templates/my.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/my.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/my.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/my.email.auth.recovery.tpl b/app/config/locale/translations/templates/my.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/my.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/my.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/nl.email.auth.confirm.tpl b/app/config/locale/translations/templates/nl.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/nl.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/nl.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/nl.email.auth.invitation.tpl b/app/config/locale/translations/templates/nl.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/nl.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/nl.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/nl.email.auth.recovery.tpl b/app/config/locale/translations/templates/nl.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/nl.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/nl.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/no.email.auth.confirm.tpl b/app/config/locale/translations/templates/no.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/no.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/no.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/no.email.auth.invitation.tpl b/app/config/locale/translations/templates/no.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/no.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/no.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/no.email.auth.recovery.tpl b/app/config/locale/translations/templates/no.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/no.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/no.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/np.email.auth.confirm.tpl b/app/config/locale/translations/templates/np.email.auth.confirm.tpl
new file mode 100644
index 0000000000..c782ae98e9
--- /dev/null
+++ b/app/config/locale/translations/templates/np.email.auth.confirm.tpl
@@ -0,0 +1,16 @@
+
+ नमस्कार {{name}},
+
+
+ तपाईंको ईमेल ठेगाना प्रमाणित गर्न यो लिंक अनुसरण गर्नुहोस्।
+
+{{cta}}
+
+ यदि तपाईंले यो ठेगाना प्रमाणित गर्न सोधेन भने, तपाईं यो सन्देशलाई वेवास्ता गर्न सक्नुहुनेछ।
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
+
diff --git a/app/config/locale/translations/templates/np.email.auth.invitation.tpl b/app/config/locale/translations/templates/np.email.auth.invitation.tpl
new file mode 100644
index 0000000000..65d555185a
--- /dev/null
+++ b/app/config/locale/translations/templates/np.email.auth.invitation.tpl
@@ -0,0 +1,15 @@
+
+ नमस्कार {{name}},
+
+
+ यो मेल तपाईंलाई पठाइएको थियो किनभने {{owner}}ले तपाईंलाई {{team}} टीममा रहेको {{project}} प्रोजेक्टमा टीमको सदस्य बन्न आमन्त्रित गर्न चाहन्छ।
+
+{{cta}}
+
+ यदि तपाईं इच्छुक हुनुहुन्न भने, तपाईं यो सन्देशलाई वेवास्ता गर्न सक्नुहुनेछ।
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
diff --git a/app/config/locale/translations/templates/np.email.auth.recovery.tpl b/app/config/locale/translations/templates/np.email.auth.recovery.tpl
new file mode 100644
index 0000000000..a2f9bb97d6
--- /dev/null
+++ b/app/config/locale/translations/templates/np.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ नमस्कार {{name}},
+
+
+ तपाईंको {{project}}को पासवर्ड रिसेट गर्नका लागि यो लिंक अनुसरण गर्नुहोस्।
+
+{{cta}}
+
+ यदि तपाईंले यो ठेगाना प्रमाणित गर्न सोधेन भने, तपाईं यो सन्देशलाई वेवास्ता गर्न सक्नुहुनेछ।
+
+
+ धन्यवाद,
+
+ {{project}} टीम
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/od.email.auth.confirm.tpl b/app/config/locale/translations/templates/od.email.auth.confirm.tpl
new file mode 100644
index 0000000000..9453a2a35c
--- /dev/null
+++ b/app/config/locale/translations/templates/od.email.auth.confirm.tpl
@@ -0,0 +1,16 @@
+
diff --git a/app/config/locales/templates/ph.email.auth.confirm.tpl b/app/config/locale/translations/templates/ph.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ph.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ph.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ph.email.auth.invitation.tpl b/app/config/locale/translations/templates/ph.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ph.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ph.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ph.email.auth.recovery.tpl b/app/config/locale/translations/templates/ph.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ph.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ph.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/pl.email.auth.confirm.tpl b/app/config/locale/translations/templates/pl.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/pl.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/pl.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/pl.email.auth.invitation.tpl b/app/config/locale/translations/templates/pl.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/pl.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/pl.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/pl.email.auth.recovery.tpl b/app/config/locale/translations/templates/pl.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/pl.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/pl.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/pt-br.email.auth.confirm.tpl b/app/config/locale/translations/templates/pt-br.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/pt-br.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/pt-br.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/pt-br.email.auth.invitation.tpl b/app/config/locale/translations/templates/pt-br.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/pt-br.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/pt-br.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/pt-br.email.auth.recovery.tpl b/app/config/locale/translations/templates/pt-br.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/pt-br.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/pt-br.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/pt-pt.email.auth.confirm.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/pt-pt.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/pt-pt.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/pt-pt.email.auth.invitation.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/pt-pt.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/pt-pt.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/pt-pt.email.auth.recovery.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/pt-pt.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/pt-pt.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ro.email.auth.confirm.tpl b/app/config/locale/translations/templates/ro.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ro.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ro.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ro.email.auth.invitation.tpl b/app/config/locale/translations/templates/ro.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ro.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ro.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ro.email.auth.recovery.tpl b/app/config/locale/translations/templates/ro.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ro.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ro.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ru.email.auth.confirm.tpl b/app/config/locale/translations/templates/ru.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ru.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ru.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ru.email.auth.invitation.tpl b/app/config/locale/translations/templates/ru.email.auth.invitation.tpl
similarity index 75%
rename from app/config/locales/templates/ru.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ru.email.auth.invitation.tpl
index 7dc637a3de..8d90140c64 100644
--- a/app/config/locales/templates/ru.email.auth.invitation.tpl
+++ b/app/config/locale/translations/templates/ru.email.auth.invitation.tpl
@@ -2,7 +2,7 @@
Здравствуйте,
- Это письмо отправлено вам, потому что {{owner}} приглашает стать членом команды {{team}} в проекте {{project}}.
+ Это письмо отправлено вам, потому что {{owner}} приглашает вас стать членом команды {{team}} в проекте {{project}}.
Перейдите по ссылке, чтобы присоединиться к команде {{team}} :
diff --git a/app/config/locales/templates/ru.email.auth.recovery.tpl b/app/config/locale/translations/templates/ru.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ru.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ru.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/si.email.auth.confirm.tpl b/app/config/locale/translations/templates/si.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/si.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/si.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/si.email.auth.invitation.tpl b/app/config/locale/translations/templates/si.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/si.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/si.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/si.email.auth.recovery.tpl b/app/config/locale/translations/templates/si.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/si.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/si.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/sl.email.auth.confirm.tpl b/app/config/locale/translations/templates/sl.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/sl.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/sl.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/sl.email.auth.invitation.tpl b/app/config/locale/translations/templates/sl.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/sl.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/sl.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/sl.email.auth.recovery.tpl b/app/config/locale/translations/templates/sl.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/sl.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/sl.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/sv.email.auth.confirm.tpl b/app/config/locale/translations/templates/sv.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/sv.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/sv.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/sv.email.auth.invitation.tpl b/app/config/locale/translations/templates/sv.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/sv.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/sv.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/sv.email.auth.recovery.tpl b/app/config/locale/translations/templates/sv.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/sv.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/sv.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ta.email.auth.confirm.tpl b/app/config/locale/translations/templates/ta.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ta.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ta.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ta.email.auth.invitation.tpl b/app/config/locale/translations/templates/ta.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ta.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ta.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ta.email.auth.recovery.tpl b/app/config/locale/translations/templates/ta.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ta.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ta.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/th.email.auth.confirm.tpl b/app/config/locale/translations/templates/th.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/th.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/th.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/th.email.auth.invitation.tpl b/app/config/locale/translations/templates/th.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/th.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/th.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/th.email.auth.recovery.tpl b/app/config/locale/translations/templates/th.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/th.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/th.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/tr.email.auth.confirm.tpl b/app/config/locale/translations/templates/tr.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/tr.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/tr.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/tr.email.auth.invitation.tpl b/app/config/locale/translations/templates/tr.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/tr.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/tr.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/tr.email.auth.recovery.tpl b/app/config/locale/translations/templates/tr.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/tr.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/tr.email.auth.recovery.tpl
diff --git a/app/config/locales/templates/ua.email.auth.confirm.tpl b/app/config/locale/translations/templates/ua.email.auth.confirm.tpl
similarity index 100%
rename from app/config/locales/templates/ua.email.auth.confirm.tpl
rename to app/config/locale/translations/templates/ua.email.auth.confirm.tpl
diff --git a/app/config/locales/templates/ua.email.auth.invitation.tpl b/app/config/locale/translations/templates/ua.email.auth.invitation.tpl
similarity index 100%
rename from app/config/locales/templates/ua.email.auth.invitation.tpl
rename to app/config/locale/translations/templates/ua.email.auth.invitation.tpl
diff --git a/app/config/locales/templates/ua.email.auth.recovery.tpl b/app/config/locale/translations/templates/ua.email.auth.recovery.tpl
similarity index 100%
rename from app/config/locales/templates/ua.email.auth.recovery.tpl
rename to app/config/locale/translations/templates/ua.email.auth.recovery.tpl
diff --git a/app/config/locale/translations/templates/ur.email.auth.confirm.tpl b/app/config/locale/translations/templates/ur.email.auth.confirm.tpl
new file mode 100644
index 0000000000..c468415d42
--- /dev/null
+++ b/app/config/locale/translations/templates/ur.email.auth.confirm.tpl
@@ -0,0 +1,15 @@
+
+ ہیلو {{name}},
+
+
+ اپنے ای میل پتے کی تصدیق کے ل this اس لنک کی پیروی کریں۔
+
+{{cta}}
+
+ اگر آپ نے اس پتے کی تصدیق کرنے کے لئے نہیں کہا تو آپ اس پیغام کو نظرانداز کرسکتے ہیں۔
+
+
+ شکریہ,
+
+ {{project}} ٹیم
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ur.email.auth.invitation.tpl b/app/config/locale/translations/templates/ur.email.auth.invitation.tpl
new file mode 100644
index 0000000000..c1982cb97f
--- /dev/null
+++ b/app/config/locale/translations/templates/ur.email.auth.invitation.tpl
@@ -0,0 +1,14 @@
+
+ ہیلو,
+
+
+ یہ میل آپ کو اس لئے بھیجا گیا تھا کیونکہ {{owner}} میں آپ کو ٹیم کے ممبر بننے کے لئے مدعو کرنا چاہتا تھا {{team}} میں ٹیم ختم {{project}}.
+
+{{cta}}
+
+ اگر آپ دلچسپی نہیں رکھتے ہیں تو ، آپ اس پیغام کو نظرانداز کرسکتے ہیں۔
+
+ شکریہ,
+
+ {{project}} ٹیم
+
\ No newline at end of file
diff --git a/app/config/locale/translations/templates/ur.email.auth.recovery.tpl b/app/config/locale/translations/templates/ur.email.auth.recovery.tpl
new file mode 100644
index 0000000000..4c884ec9ce
--- /dev/null
+++ b/app/config/locale/translations/templates/ur.email.auth.recovery.tpl
@@ -0,0 +1,15 @@
+
+ ہیلو {{name}},
+
+
+ اپنا دوبارہ ترتیب دینے کیلئے اس لنک پر عمل کریں {{project}} پاس ورڈ.
+
+{{cta}}
+
+ اگر آپ نے اس پتے کی تصدیق کرنے کے لئے نہیں کہا تو آپ اس پیغام کو نظرانداز کرسکتے ہیں۔
+
- Kattints erre a linkre, hogy visszaállítsuk a {{project}} jelszavad.
-
-{{cta}}
-
- Ha nem kérvényezted, hogy visszaállítsuk a jelszavad ignoráld ezt a levelet.
-
-
- Köszönettel,
-
- {{project}} csapat
-
diff --git a/app/config/platforms.php b/app/config/platforms.php
index 35e03c7a44..eea2a273e9 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -30,7 +30,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
- 'version' => '0.3.0-dev.1',
+ 'version' => '0.3.0-dev.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'enabled' => true,
'beta' => true,
diff --git a/app/config/roles.php b/app/config/roles.php
index 4d2420ec58..0a4ec4369c 100644
--- a/app/config/roles.php
+++ b/app/config/roles.php
@@ -37,6 +37,8 @@ $admins = [
'users.write',
'collections.read',
'collections.write',
+ 'functions.read',
+ 'functions.write',
'platforms.read',
'platforms.write',
'keys.read',
diff --git a/app/config/scopes.php b/app/config/scopes.php
index 59ad2a0859..88c67d70dd 100644
--- a/app/config/scopes.php
+++ b/app/config/scopes.php
@@ -11,6 +11,9 @@ return [ // List of publicly visible scopes
'documents.write',
'files.read',
'files.write',
+ 'functions.read',
+ 'functions.write',
+ 'health.read',
// 'platforms.read',
// 'platforms.write',
// 'keys.read',
diff --git a/app/config/services.php b/app/config/services.php
index cc5df8c8e2..e500f9a241 100644
--- a/app/config/services.php
+++ b/app/config/services.php
@@ -3,89 +3,96 @@
return [
'/' => [
'name' => 'Homepage',
- 'controller' => 'controllers/web/home.php',
+ 'controller' => 'web/home.php',
'sdk' => false,
'tests' => false,
],
'console/' => [
'name' => 'Console',
- 'controller' => 'controllers/web/console.php',
+ 'controller' => 'web/console.php',
'sdk' => false,
'tests' => false,
],
'v1/account' => [
'name' => 'Account',
'description' => '/docs/services/account.md',
- 'controller' => 'controllers/api/account.php',
+ 'controller' => 'api/account.php',
'sdk' => true,
'tests' => false,
],
'v1/avatars' => [
'name' => 'Avatars',
'description' => '/docs/services/avatars.md',
- 'controller' => 'controllers/api/avatars.php',
+ 'controller' => 'api/avatars.php',
'sdk' => true,
'tests' => false,
],
'v1/database' => [
'name' => 'Database',
'description' => '/docs/services/database.md',
- 'controller' => 'controllers/api/database.php',
+ 'controller' => 'api/database.php',
'sdk' => true,
'tests' => false,
],
'v1/locale' => [
'name' => 'Locale',
'description' => '/docs/services/locale.md',
- 'controller' => 'controllers/api/locale.php',
+ 'controller' => 'api/locale.php',
'sdk' => true,
'tests' => false,
],
'v1/health' => [
'name' => 'Health',
'description' => '/docs/services/health.md',
- 'controller' => 'controllers/api/health.php',
+ 'controller' => 'api/health.php',
'sdk' => true,
'tests' => false,
],
'v1/projects' => [
'name' => 'Projects',
- 'controller' => 'controllers/api/projects.php',
+ 'controller' => 'api/projects.php',
'sdk' => true,
'tests' => false,
],
'v1/storage' => [
'name' => 'Storage',
'description' => '/docs/services/storage.md',
- 'controller' => 'controllers/api/storage.php',
+ 'controller' => 'api/storage.php',
'sdk' => true,
'tests' => false,
],
'v1/teams' => [
'name' => 'Teams',
'description' => '/docs/services/teams.md',
- 'controller' => 'controllers/api/teams.php',
+ 'controller' => 'api/teams.php',
'sdk' => true,
'tests' => false,
],
'v1/users' => [
'name' => 'Users',
'description' => '/docs/services/users.md',
- 'controller' => 'controllers/api/users.php',
+ 'controller' => 'api/users.php',
+ 'sdk' => true,
+ 'tests' => false,
+ ],
+ 'v1/functions' => [
+ 'name' => 'Users',
+ 'description' => '/docs/services/functions.md',
+ 'controller' => 'api/functions.php',
'sdk' => true,
'tests' => false,
],
'v1/mock' => [
'name' => 'Mock',
'description' => '',
- 'controller' => 'controllers/mock.php',
+ 'controller' => 'mock.php',
'sdk' => false,
'tests' => true,
],
'v1/graphql' => [
'name' => 'GraphQL',
'description' => 'GraphQL Endpoint',
- 'controller' => 'controllers/api/graphql.php',
+ 'controller' => 'api/graphql.php',
'sdk' => false,
'tests' => false,
],
diff --git a/app/config/specs/0.6.2.client.json b/app/config/specs/0.6.2.client.json
index 6524743bca..2a778b4596 100644
--- a/app/config/specs/0.6.2.client.json
+++ b/app/config/specs/0.6.2.client.json
@@ -1,2 +1,2 @@
-{"swagger":"2.0","info":{"version":"0.6.2","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@localhost.test"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","extensions":{"demo":"5df5acd0d48c2"}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","extensions":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"get","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","extensions":{"weight":38,"cookies":false,"type":"","demo":"docs\/examples\/account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account","operationId":"create","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#createVerification) route to start verifying the user email address. To allow your new user to login to his new account, you need to create a new [account session](\/docs\/client\/account#createSession).","extensions":{"weight":32,"cookies":false,"type":"","demo":"docs\/examples\/account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]},"delete":{"summary":"Delete Account","operationId":"delete","consumes":["application\/json"],"tags":["account"],"description":"Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately.","extensions":{"weight":46,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/email":{"patch":{"summary":"Update Account Email","operationId":"updateEmail","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.","extensions":{"weight":44,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/logs":{"get":{"summary":"Get Account Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","extensions":{"weight":41,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/name":{"patch":{"summary":"Update Account Name","operationId":"updateName","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account name.","extensions":{"weight":42,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"User name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]}},"\/account\/password":{"patch":{"summary":"Update Account Password","operationId":"updatePassword","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user password. For validation, user is required to pass the password twice.","extensions":{"weight":43,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-password.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-password.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"password","description":"New user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"oldPassword","description":"Old user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/prefs":{"get":{"summary":"Get Account Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user preferences as a key-value object.","extensions":{"weight":39,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"patch":{"summary":"Update Account Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account preferences. You can pass only the specific settings you wish to update.","extensions":{"weight":45,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/account\/recovery":{"post":{"summary":"Create Password Recovery","operationId":"createRecovery","consumes":["application\/json"],"tags":["account"],"description":"Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT \/account\/recovery](\/docs\/client\/account#updateRecovery) endpoint to complete the process.","extensions":{"weight":49,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"url","description":"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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Password Recovery","operationId":"updateRecovery","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](\/docs\/client\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.","extensions":{"weight":50,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User account UID address.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid reset token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"},{"name":"password","description":"New password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"passwordAgain","description":"New password again. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/sessions":{"get":{"summary":"Get Account Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of active sessions across different devices.","extensions":{"weight":40,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account Session","operationId":"createSession","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user.","extensions":{"weight":33,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]},"delete":{"summary":"Delete All Account Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["account"],"description":"Delete all sessions from the user account and remove any sessions cookies from the end client.","extensions":{"weight":48,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/sessions\/oauth2\/{provider}":{"get":{"summary":"Create Account Session with OAuth2","operationId":"createOAuth2Session","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login to his account using the OAuth2 provider of his choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.","extensions":{"weight":34,"cookies":false,"type":"webAuth","demo":"docs\/examples\/account\/create-o-auth2session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-oauth2.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"provider","description":"OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, twitch, vk, yahoo, yandex.","required":true,"type":"string","x-example":"amazon","in":"path"},{"name":"success","description":"URL to redirect back to your app after a successful login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"\/auth\/oauth2\/success","in":"query"},{"name":"failure","description":"URL to redirect back to your app after a failed login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"\/auth\/oauth2\/failure","in":"query"},{"name":"scopes","description":"A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"}]}},"\/account\/sessions\/{sessionId}":{"delete":{"summary":"Delete Account Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to log out the currently logged in user from all his account sessions across all his different devices. When using the option id argument, only the session unique ID provider will be deleted.","extensions":{"weight":47,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"sessionId","description":"Session unique ID. Use the string 'current' to delete the current device session.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/account\/verification":{"post":{"summary":"Create Email Verification","operationId":"createVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](\/docs\/client\/account#updateAccountVerification). \n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n","extensions":{"weight":51,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"URL to redirect the user back to your app from the verification 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Email Verification","operationId":"updateVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.","extensions":{"weight":52,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid verification token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}},"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"getBrowser","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","extensions":{"weight":54,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"getCreditCard","consumes":["application\/json"],"tags":["avatars"],"description":"Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.","extensions":{"weight":53,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"getFavicon","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.","extensions":{"weight":57,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"getFlag","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","extensions":{"weight":55,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"getImage","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","extensions":{"weight":56,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"getInitials","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","extensions":{"weight":59,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"getQR","consumes":["application\/json"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","extensions":{"weight":58,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"listDocuments","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":66,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"offset","description":"Offset value. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":50,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"$id","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"createDocument","consumes":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database?sdk=nodejs#createCollection) API or directly from your database console.","extensions":{"weight":65,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"parentDocument","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"[PARENT_DOCUMENT]","default":"","in":"formData"},{"name":"parentProperty","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","default":"","in":"formData"},{"name":"parentPropertyType","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"assign","default":"assign","in":"formData"}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"getDocument","consumes":["application\/json"],"tags":["database"],"description":"Get document by its unique ID. This endpoint response returns a JSON object with the document data.","extensions":{"weight":67,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"updateDocument","consumes":["application\/json"],"tags":["database"],"description":"","extensions":{"weight":68,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Document","operationId":"deleteDocument","consumes":["application\/json"],"tags":["database"],"description":"Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted.","extensions":{"weight":69,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"get","consumes":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","extensions":{"weight":70,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"getContinents","consumes":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","extensions":{"weight":74,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"getCountries","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","extensions":{"weight":71,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"getCountriesEU","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","extensions":{"weight":72,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"getCountriesPhones","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","extensions":{"weight":73,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"getCurrencies","consumes":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","extensions":{"weight":75,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"getLanguages","consumes":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","extensions":{"weight":76,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"listFiles","consumes":["application\/json"],"tags":["storage"],"description":"Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":124,"cookies":false,"type":"","demo":"docs\/examples\/storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"createFile","consumes":["multipart\/form-data"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","extensions":{"weight":123,"cookies":false,"type":"upload","demo":"docs\/examples\/storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"file","description":"Binary File.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"getFile","consumes":["application\/json"],"tags":["storage"],"description":"Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.","extensions":{"weight":125,"cookies":false,"type":"","demo":"docs\/examples\/storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"updateFile","consumes":["application\/json"],"tags":["storage"],"description":"Update file by its unique ID. Only users with write permissions have access to update this resource.","extensions":{"weight":129,"cookies":false,"type":"","demo":"docs\/examples\/storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete File","operationId":"deleteFile","consumes":["application\/json"],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":130,"cookies":false,"type":"","demo":"docs\/examples\/storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"getFileDownload","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.","extensions":{"weight":127,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"getFilePreview","consumes":["application\/json"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","extensions":{"weight":126,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"getFileView","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","extensions":{"weight":128,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"as","description":"Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.","required":false,"type":"string","x-example":"pdf","default":"","in":"query"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"list","consumes":["application\/json"],"tags":["teams"],"description":"Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":132,"cookies":false,"type":"","demo":"docs\/examples\/teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"create","consumes":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","extensions":{"weight":131,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Team name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":["owner"],"in":"formData"}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"get","consumes":["application\/json"],"tags":["teams"],"description":"Get team by its unique ID. All team members have read access for this resource.","extensions":{"weight":133,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"update","consumes":["application\/json"],"tags":["teams"],"description":"Update team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":134,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"name","description":"Team name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]},"delete":{"summary":"Delete Team","operationId":"delete","consumes":["application\/json"],"tags":["teams"],"description":"Delete team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":135,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"getMemberships","consumes":["application\/json"],"tags":["teams"],"description":"Get team members by the team unique ID. All team members have read access for this list of resources.","extensions":{"weight":137,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"createMembership","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","extensions":{"weight":136,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"email","description":"New team member email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"name","description":"New team member name.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"URL to redirect the user back to your app from the invitation 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}":{"delete":{"summary":"Delete Team Membership","operationId":"deleteMembership","consumes":["application\/json"],"tags":["teams"],"description":"This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it.","extensions":{"weight":139,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}\/status":{"patch":{"summary":"Update Team Membership Status","operationId":"updateMembershipStatus","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to allow a user to accept an invitation to join a team after he is being redirected back to your app from the invitation email he was sent.","extensions":{"weight":138,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update-membership-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"},{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Secret key.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}}},"definitions":{"Error":{"required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/localhost\/docs"}}
\ No newline at end of file
+{"swagger":"2.0","info":{"version":"0.6.2","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@localhost.test"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","extensions":{"demo":"5df5acd0d48c2"}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","extensions":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"get","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","extensions":{"weight":38,"cookies":false,"type":"","demo":"docs\/examples\/account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account","operationId":"create","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#createVerification) route to start verifying the user email address. To allow your new user to login to his new account, you need to create a new [account session](\/docs\/client\/account#createSession).","extensions":{"weight":32,"cookies":false,"type":"","demo":"docs\/examples\/account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]},"delete":{"summary":"Delete Account","operationId":"delete","consumes":["application\/json"],"tags":["account"],"description":"Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately.","extensions":{"weight":46,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/email":{"patch":{"summary":"Update Account Email","operationId":"updateEmail","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.","extensions":{"weight":44,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/logs":{"get":{"summary":"Get Account Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","extensions":{"weight":41,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/name":{"patch":{"summary":"Update Account Name","operationId":"updateName","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account name.","extensions":{"weight":42,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"User name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]}},"\/account\/password":{"patch":{"summary":"Update Account Password","operationId":"updatePassword","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user password. For validation, user is required to pass the password twice.","extensions":{"weight":43,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-password.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-password.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"password","description":"New user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"oldPassword","description":"Old user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/prefs":{"get":{"summary":"Get Account Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user preferences as a key-value object.","extensions":{"weight":39,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"patch":{"summary":"Update Account Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account preferences. You can pass only the specific settings you wish to update.","extensions":{"weight":45,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/account\/recovery":{"post":{"summary":"Create Password Recovery","operationId":"createRecovery","consumes":["application\/json"],"tags":["account"],"description":"Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT \/account\/recovery](\/docs\/client\/account#updateRecovery) endpoint to complete the process.","extensions":{"weight":49,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"url","description":"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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Password Recovery","operationId":"updateRecovery","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](\/docs\/client\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.","extensions":{"weight":50,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User account UID address.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid reset token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"},{"name":"password","description":"New password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"passwordAgain","description":"New password again. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/sessions":{"get":{"summary":"Get Account Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of active sessions across different devices.","extensions":{"weight":40,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account Session","operationId":"createSession","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user.","extensions":{"weight":33,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]},"delete":{"summary":"Delete All Account Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["account"],"description":"Delete all sessions from the user account and remove any sessions cookies from the end client.","extensions":{"weight":48,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/sessions\/oauth2\/{provider}":{"get":{"summary":"Create Account Session with OAuth2","operationId":"createOAuth2Session","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login to his account using the OAuth2 provider of his choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.","extensions":{"weight":34,"cookies":false,"type":"webAuth","demo":"docs\/examples\/account\/create-o-auth2session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-oauth2.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"provider","description":"OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, twitch, vk, yahoo, yandex.","required":true,"type":"string","x-example":"amazon","in":"path"},{"name":"success","description":"URL to redirect back to your app after a successful login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/success","in":"query"},{"name":"failure","description":"URL to redirect back to your app after a failed login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/failure","in":"query"},{"name":"scopes","description":"A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"}]}},"\/account\/sessions\/{sessionId}":{"delete":{"summary":"Delete Account Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to log out the currently logged in user from all his account sessions across all his different devices. When using the option id argument, only the session unique ID provider will be deleted.","extensions":{"weight":47,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"sessionId","description":"Session unique ID. Use the string 'current' to delete the current device session.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/account\/verification":{"post":{"summary":"Create Email Verification","operationId":"createVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](\/docs\/client\/account#updateAccountVerification). \n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n","extensions":{"weight":51,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"URL to redirect the user back to your app from the verification 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Email Verification","operationId":"updateVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.","extensions":{"weight":52,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid verification token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}},"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"getBrowser","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","extensions":{"weight":54,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"getCreditCard","consumes":["application\/json"],"tags":["avatars"],"description":"Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.","extensions":{"weight":53,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"getFavicon","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.","extensions":{"weight":57,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"getFlag","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","extensions":{"weight":55,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"getImage","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","extensions":{"weight":56,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"getInitials","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","extensions":{"weight":59,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"getQR","consumes":["application\/json"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","extensions":{"weight":58,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"listDocuments","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":66,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"offset","description":"Offset value. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":50,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"$id","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"createDocument","consumes":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database?sdk=nodejs#createCollection) API or directly from your database console.","extensions":{"weight":65,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"parentDocument","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"[PARENT_DOCUMENT]","default":"","in":"formData"},{"name":"parentProperty","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","default":"","in":"formData"},{"name":"parentPropertyType","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"assign","default":"assign","in":"formData"}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"getDocument","consumes":["application\/json"],"tags":["database"],"description":"Get document by its unique ID. This endpoint response returns a JSON object with the document data.","extensions":{"weight":67,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"updateDocument","consumes":["application\/json"],"tags":["database"],"description":"","extensions":{"weight":68,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Document","operationId":"deleteDocument","consumes":["application\/json"],"tags":["database"],"description":"Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted.","extensions":{"weight":69,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"get","consumes":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","extensions":{"weight":70,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"getContinents","consumes":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","extensions":{"weight":74,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"getCountries","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","extensions":{"weight":71,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"getCountriesEU","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","extensions":{"weight":72,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"getCountriesPhones","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","extensions":{"weight":73,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"getCurrencies","consumes":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","extensions":{"weight":75,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"getLanguages","consumes":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","extensions":{"weight":76,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"listFiles","consumes":["application\/json"],"tags":["storage"],"description":"Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":124,"cookies":false,"type":"","demo":"docs\/examples\/storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"createFile","consumes":["multipart\/form-data"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","extensions":{"weight":123,"cookies":false,"type":"upload","demo":"docs\/examples\/storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"file","description":"Binary File.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"getFile","consumes":["application\/json"],"tags":["storage"],"description":"Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.","extensions":{"weight":125,"cookies":false,"type":"","demo":"docs\/examples\/storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"updateFile","consumes":["application\/json"],"tags":["storage"],"description":"Update file by its unique ID. Only users with write permissions have access to update this resource.","extensions":{"weight":129,"cookies":false,"type":"","demo":"docs\/examples\/storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete File","operationId":"deleteFile","consumes":["application\/json"],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":130,"cookies":false,"type":"","demo":"docs\/examples\/storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"getFileDownload","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.","extensions":{"weight":127,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"getFilePreview","consumes":["application\/json"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","extensions":{"weight":126,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"getFileView","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","extensions":{"weight":128,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"as","description":"Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.","required":false,"type":"string","x-example":"pdf","default":"","in":"query"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"list","consumes":["application\/json"],"tags":["teams"],"description":"Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":132,"cookies":false,"type":"","demo":"docs\/examples\/teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"create","consumes":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","extensions":{"weight":131,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Team name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":["owner"],"in":"formData"}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"get","consumes":["application\/json"],"tags":["teams"],"description":"Get team by its unique ID. All team members have read access for this resource.","extensions":{"weight":133,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"update","consumes":["application\/json"],"tags":["teams"],"description":"Update team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":134,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"name","description":"Team name.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]},"delete":{"summary":"Delete Team","operationId":"delete","consumes":["application\/json"],"tags":["teams"],"description":"Delete team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":135,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"getMemberships","consumes":["application\/json"],"tags":["teams"],"description":"Get team members by the team unique ID. All team members have read access for this list of resources.","extensions":{"weight":137,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"createMembership","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","extensions":{"weight":136,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"email","description":"New team member email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"name","description":"New team member name.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"URL to redirect the user back to your app from the invitation 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}":{"delete":{"summary":"Delete Team Membership","operationId":"deleteMembership","consumes":["application\/json"],"tags":["teams"],"description":"This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it.","extensions":{"weight":139,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}\/status":{"patch":{"summary":"Update Team Membership Status","operationId":"updateMembershipStatus","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to allow a user to accept an invitation to join a team after he is being redirected back to your app from the invitation email he was sent.","extensions":{"weight":138,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update-membership-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"},{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Secret key.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}}},"definitions":{"Error":{"required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/localhost\/docs"}}
\ No newline at end of file
diff --git a/app/config/specs/0.7.0.client.json b/app/config/specs/0.7.0.client.json
new file mode 100644
index 0000000000..1453566c2b
--- /dev/null
+++ b/app/config/specs/0.7.0.client.json
@@ -0,0 +1,2 @@
+
+{"swagger":"2.0","info":{"version":"0.7.0","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@localhost.test"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","extensions":{"demo":"5df5acd0d48c2"}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","extensions":{"demo":"en"}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"get","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","extensions":{"weight":41,"cookies":false,"type":"","demo":"docs\/examples\/account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account","operationId":"create","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#createVerification) route to start verifying the user email address. To allow your new user to login to his new account, you need to create a new [account session](\/docs\/client\/account#createSession).","extensions":{"weight":35,"cookies":false,"type":"","demo":"docs\/examples\/account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]},"delete":{"summary":"Delete Account","operationId":"delete","consumes":["application\/json"],"tags":["account"],"description":"Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately.","extensions":{"weight":49,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/email":{"patch":{"summary":"Update Account Email","operationId":"updateEmail","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.","extensions":{"weight":47,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/logs":{"get":{"summary":"Get Account Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","extensions":{"weight":44,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/name":{"patch":{"summary":"Update Account Name","operationId":"updateName","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account name.","extensions":{"weight":45,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"User name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]}},"\/account\/password":{"patch":{"summary":"Update Account Password","operationId":"updatePassword","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user password. For validation, user is required to pass the password twice.","extensions":{"weight":46,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-password.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-password.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"password","description":"New user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"oldPassword","description":"Old user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/prefs":{"get":{"summary":"Get Account Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user preferences as a key-value object.","extensions":{"weight":42,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"patch":{"summary":"Update Account Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account preferences. You can pass only the specific settings you wish to update.","extensions":{"weight":48,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/account\/recovery":{"post":{"summary":"Create Password Recovery","operationId":"createRecovery","consumes":["application\/json"],"tags":["account"],"description":"Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT \/account\/recovery](\/docs\/client\/account#updateRecovery) endpoint to complete the process.","extensions":{"weight":52,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"url","description":"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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Password Recovery","operationId":"updateRecovery","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](\/docs\/client\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.","extensions":{"weight":53,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User account UID address.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid reset token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"},{"name":"password","description":"New password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"passwordAgain","description":"New password again. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/sessions":{"get":{"summary":"Get Account Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of active sessions across different devices.","extensions":{"weight":43,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]},"post":{"summary":"Create Account Session","operationId":"createSession","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user.","extensions":{"weight":36,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]},"delete":{"summary":"Delete All Account Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["account"],"description":"Delete all sessions from the user account and remove any sessions cookies from the end client.","extensions":{"weight":51,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}]}},"\/account\/sessions\/oauth2\/{provider}":{"get":{"summary":"Create Account Session with OAuth2","operationId":"createOAuth2Session","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login to his account using the OAuth2 provider of his choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.","extensions":{"weight":37,"cookies":false,"type":"webAuth","demo":"docs\/examples\/account\/create-o-auth2session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-oauth2.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"provider","description":"OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, twitch, vk, yahoo, yandex.","required":true,"type":"string","x-example":"amazon","in":"path"},{"name":"success","description":"URL to redirect back to your app after a successful login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/success","in":"query"},{"name":"failure","description":"URL to redirect back to your app after a failed login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/failure","in":"query"},{"name":"scopes","description":"A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"}]}},"\/account\/sessions\/{sessionId}":{"delete":{"summary":"Delete Account Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to log out the currently logged in user from all his account sessions across all his different devices. When using the option id argument, only the session unique ID provider will be deleted.","extensions":{"weight":50,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"sessionId","description":"Session unique ID. Use the string 'current' to delete the current device session.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/account\/verification":{"post":{"summary":"Create Email Verification","operationId":"createVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](\/docs\/client\/account#updateAccountVerification). \n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n","extensions":{"weight":54,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"account","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"URL to redirect the user back to your app from the verification 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Email Verification","operationId":"updateVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.","extensions":{"weight":55,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid verification token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}},"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"getBrowser","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","extensions":{"weight":57,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"getCreditCard","consumes":["application\/json"],"tags":["avatars"],"description":"Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.","extensions":{"weight":56,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"getFavicon","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.","extensions":{"weight":60,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"getFlag","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","extensions":{"weight":58,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"getImage","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","extensions":{"weight":59,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"getInitials","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","extensions":{"weight":62,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"getQR","consumes":["application\/json"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","extensions":{"weight":61,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"listDocuments","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":69,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Offset value. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"$id","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"createDocument","consumes":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database?sdk=nodejs#createCollection) API or directly from your database console.","extensions":{"weight":68,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"parentDocument","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"[PARENT_DOCUMENT]","default":"","in":"formData"},{"name":"parentProperty","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","default":"","in":"formData"},{"name":"parentPropertyType","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"assign","default":"assign","in":"formData"}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"getDocument","consumes":["application\/json"],"tags":["database"],"description":"Get document by its unique ID. This endpoint response returns a JSON object with the document data.","extensions":{"weight":70,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"updateDocument","consumes":["application\/json"],"tags":["database"],"description":"","extensions":{"weight":71,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Document","operationId":"deleteDocument","consumes":["application\/json"],"tags":["database"],"description":"Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted.","extensions":{"weight":72,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"get","consumes":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","extensions":{"weight":73,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"getContinents","consumes":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","extensions":{"weight":77,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"getCountries","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","extensions":{"weight":74,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"getCountriesEU","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","extensions":{"weight":75,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"getCountriesPhones","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","extensions":{"weight":76,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"getCurrencies","consumes":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","extensions":{"weight":78,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"getLanguages","consumes":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","extensions":{"weight":79,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[]}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"listFiles","consumes":["application\/json"],"tags":["storage"],"description":"Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":127,"cookies":false,"type":"","demo":"docs\/examples\/storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"createFile","consumes":["multipart\/form-data"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","extensions":{"weight":126,"cookies":false,"type":"upload","demo":"docs\/examples\/storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"file","description":"Binary file.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"getFile","consumes":["application\/json"],"tags":["storage"],"description":"Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.","extensions":{"weight":128,"cookies":false,"type":"","demo":"docs\/examples\/storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"updateFile","consumes":["application\/json"],"tags":["storage"],"description":"Update file by its unique ID. Only users with write permissions have access to update this resource.","extensions":{"weight":132,"cookies":false,"type":"","demo":"docs\/examples\/storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete File","operationId":"deleteFile","consumes":["application\/json"],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":133,"cookies":false,"type":"","demo":"docs\/examples\/storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"getFileDownload","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.","extensions":{"weight":130,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"getFilePreview","consumes":["application\/json"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","extensions":{"weight":129,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","default":"","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"getFileView","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","extensions":{"weight":131,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"as","description":"Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.","required":false,"type":"string","x-example":"pdf","default":"","in":"query"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"list","consumes":["application\/json"],"tags":["teams"],"description":"Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":135,"cookies":false,"type":"","demo":"docs\/examples\/teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"create","consumes":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","extensions":{"weight":134,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":["owner"],"in":"formData"}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"get","consumes":["application\/json"],"tags":["teams"],"description":"Get team by its unique ID. All team members have read access for this resource.","extensions":{"weight":136,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"update","consumes":["application\/json"],"tags":["teams"],"description":"Update team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":137,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]},"delete":{"summary":"Delete Team","operationId":"delete","consumes":["application\/json"],"tags":["teams"],"description":"Delete team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":138,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"getMemberships","consumes":["application\/json"],"tags":["teams"],"description":"Get team members by the team unique ID. All team members have read access for this list of resources.","extensions":{"weight":140,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"createMembership","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","extensions":{"weight":139,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"email","description":"New team member email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"name","description":"New team member name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"URL to redirect the user back to your app from the invitation 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}":{"delete":{"summary":"Delete Team Membership","operationId":"deleteMembership","consumes":["application\/json"],"tags":["teams"],"description":"This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it.","extensions":{"weight":142,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}\/status":{"patch":{"summary":"Update Team Membership Status","operationId":"updateMembershipStatus","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to allow a user to accept an invitation to join a team after he is being redirected back to your app from the invitation email he was sent.","extensions":{"weight":141,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update-membership-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"},{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Secret key.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}}},"definitions":{"Error":{"required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/localhost\/docs"}}
\ No newline at end of file
diff --git a/app/config/specs/0.7.0.console.json b/app/config/specs/0.7.0.console.json
new file mode 100644
index 0000000000..67769fa542
--- /dev/null
+++ b/app/config/specs/0.7.0.console.json
@@ -0,0 +1,2 @@
+
+{"swagger":"2.0","info":{"version":"0.7.0","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@localhost.test"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","extensions":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","extensions":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","extensions":{"demo":"en"}},"Mode":{"type":"apiKey","name":"X-Appwrite-Mode","description":"","in":"header","extensions":{"demo":""}}},"paths":{"\/account":{"get":{"summary":"Get Account","operationId":"get","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user data as JSON object.","extensions":{"weight":41,"cookies":false,"type":"","demo":"docs\/examples\/account\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]},"post":{"summary":"Create Account","operationId":"create","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [\/account\/verfication](\/docs\/client\/account#createVerification) route to start verifying the user email address. To allow your new user to login to his new account, you need to create a new [account session](\/docs\/client\/account#createSession).","extensions":{"weight":35,"cookies":false,"type":"","demo":"docs\/examples\/account\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]},"delete":{"summary":"Delete Account","operationId":"delete","consumes":["application\/json"],"tags":["account"],"description":"Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately.","extensions":{"weight":49,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]}},"\/account\/email":{"patch":{"summary":"Update Account Email","operationId":"updateEmail","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request.","extensions":{"weight":47,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-email.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-email.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/logs":{"get":{"summary":"Get Account Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.","extensions":{"weight":44,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]}},"\/account\/name":{"patch":{"summary":"Update Account Name","operationId":"updateName","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account name.","extensions":{"weight":45,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-name.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-name.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"User name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]}},"\/account\/password":{"patch":{"summary":"Update Account Password","operationId":"updatePassword","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user password. For validation, user is required to pass the password twice.","extensions":{"weight":46,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-password.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-password.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"password","description":"New user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"oldPassword","description":"Old user password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/prefs":{"get":{"summary":"Get Account Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user preferences as a key-value object.","extensions":{"weight":42,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]},"patch":{"summary":"Update Account Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["account"],"description":"Update currently logged in user account preferences. You can pass only the specific settings you wish to update.","extensions":{"weight":48,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/account\/recovery":{"post":{"summary":"Create Password Recovery","operationId":"createRecovery","consumes":["application\/json"],"tags":["account"],"description":"Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT \/account\/recovery](\/docs\/client\/account#updateRecovery) endpoint to complete the process.","extensions":{"weight":52,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"url","description":"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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Password Recovery","operationId":"updateRecovery","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](\/docs\/client\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.","extensions":{"weight":53,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-recovery.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-recovery.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User account UID address.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid reset token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"},{"name":"password","description":"New password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"passwordAgain","description":"New password again. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]}},"\/account\/sessions":{"get":{"summary":"Get Account Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["account"],"description":"Get currently logged in user list of active sessions across different devices.","extensions":{"weight":43,"cookies":false,"type":"","demo":"docs\/examples\/account\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/get-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]},"post":{"summary":"Create Account Session","operationId":"createSession","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user.","extensions":{"weight":36,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"}]},"delete":{"summary":"Delete All Account Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["account"],"description":"Delete all sessions from the user account and remove any sessions cookies from the end client.","extensions":{"weight":51,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}]}},"\/account\/sessions\/oauth2\/{provider}":{"get":{"summary":"Create Account Session with OAuth2","operationId":"createOAuth2Session","consumes":["application\/json"],"tags":["account"],"description":"Allow the user to login to his account using the OAuth2 provider of his choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.","extensions":{"weight":37,"cookies":false,"type":"webAuth","demo":"docs\/examples\/account\/create-o-auth2session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-session-oauth2.md","rate-limit":50,"rate-time":3600,"rate-key":"ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"provider","description":"OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, twitch, vk, yahoo, yandex.","required":true,"type":"string","x-example":"amazon","in":"path"},{"name":"success","description":"URL to redirect back to your app after a successful login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/success","in":"query"},{"name":"failure","description":"URL to redirect back to your app after a failed login attempt. 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.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"https:\/\/appwrite.io\/auth\/oauth2\/failure","in":"query"},{"name":"scopes","description":"A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"}]}},"\/account\/sessions\/{sessionId}":{"delete":{"summary":"Delete Account Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to log out the currently logged in user from all his account sessions across all his different devices. When using the option id argument, only the session unique ID provider will be deleted.","extensions":{"weight":50,"cookies":false,"type":"","demo":"docs\/examples\/account\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/delete-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"sessionId","description":"Session unique ID. Use the string 'current' to delete the current device session.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/account\/verification":{"post":{"summary":"Create Email Verification","operationId":"createVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](\/docs\/client\/account#updateAccountVerification). \n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n","extensions":{"weight":54,"cookies":false,"type":"","demo":"docs\/examples\/account\/create-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},email:{param-email}","scope":"account","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"url","description":"URL to redirect the user back to your app from the verification 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]},"put":{"summary":"Complete Email Verification","operationId":"updateVerification","consumes":["application\/json"],"tags":["account"],"description":"Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code.","extensions":{"weight":55,"cookies":false,"type":"","demo":"docs\/examples\/account\/update-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/update-verification.md","rate-limit":10,"rate-time":3600,"rate-key":"url:{url},userId:{param-userId}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Valid verification token.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}},"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"getBrowser","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","extensions":{"weight":57,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"getCreditCard","consumes":["application\/json"],"tags":["avatars"],"description":"Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.","extensions":{"weight":56,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"getFavicon","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.","extensions":{"weight":60,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"getFlag","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","extensions":{"weight":58,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"getImage","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","extensions":{"weight":59,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"getInitials","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","extensions":{"weight":62,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"getQR","consumes":["application\/json"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","extensions":{"weight":61,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections":{"get":{"summary":"List Collections","operationId":"listCollections","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project collections. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":64,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-collections.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-collections.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Collection","operationId":"createCollection","consumes":["application\/json"],"tags":["database"],"description":"Create a new Collection.","extensions":{"weight":63,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Collection name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"rules","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/database\/collections\/{collectionId}":{"get":{"summary":"Get Collection","operationId":"getCollection","consumes":["application\/json"],"tags":["database"],"description":"Get collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.","extensions":{"weight":65,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]},"put":{"summary":"Update Collection","operationId":"updateCollection","consumes":["application\/json"],"tags":["database"],"description":"Update collection by its unique ID.","extensions":{"weight":66,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"name","description":"Collection name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"rules","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"}]},"delete":{"summary":"Delete Collection","operationId":"deleteCollection","consumes":["application\/json"],"tags":["database"],"description":"Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":67,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"listDocuments","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":69,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Offset value. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"$id","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"createDocument","consumes":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database?sdk=nodejs#createCollection) API or directly from your database console.","extensions":{"weight":68,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"parentDocument","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"[PARENT_DOCUMENT]","default":"","in":"formData"},{"name":"parentProperty","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","default":"","in":"formData"},{"name":"parentPropertyType","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"assign","default":"assign","in":"formData"}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"getDocument","consumes":["application\/json"],"tags":["database"],"description":"Get document by its unique ID. This endpoint response returns a JSON object with the document data.","extensions":{"weight":70,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"updateDocument","consumes":["application\/json"],"tags":["database"],"description":"","extensions":{"weight":71,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Document","operationId":"deleteDocument","consumes":["application\/json"],"tags":["database"],"description":"Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted.","extensions":{"weight":72,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/functions":{"get":{"summary":"List Functions","operationId":"list","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":155,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Function","operationId":"create","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":154,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Function name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"env","description":"Execution enviornment.","required":true,"type":"string","x-example":"node-14","in":"formData"},{"name":"vars","description":"Key-value JSON object.","required":false,"type":"object","x-example":"{}","default":[],"in":"formData"},{"name":"events","description":"Events list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"},{"name":"schedule","description":"Schedule CRON syntax.","required":false,"type":"string","default":"","in":"formData"},{"name":"timeout","description":"Function maximum execution time in seconds.","required":false,"type":"integer","format":"int32","x-example":1,"default":15,"in":"formData"}]}},"\/functions\/{functionId}":{"get":{"summary":"Get Function","operationId":"get","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":156,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]},"put":{"summary":"Update Function","operationId":"update","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":157,"cookies":false,"type":"","demo":"docs\/examples\/functions\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"name","description":"Function name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"vars","description":"Key-value JSON object.","required":false,"type":"object","x-example":"{}","default":[],"in":"formData"},{"name":"events","description":"Events list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"},{"name":"schedule","description":"Schedule CRON syntax.","required":false,"type":"string","default":"","in":"formData"},{"name":"timeout","description":"Function maximum execution time in seconds.","required":false,"type":"integer","format":"int32","x-example":1,"default":15,"in":"formData"}]},"delete":{"summary":"Delete Function","operationId":"delete","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":159,"cookies":false,"type":"","demo":"docs\/examples\/functions\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/executions":{"get":{"summary":"List Executions","operationId":"listExecutions","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":165,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list-executions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-executions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Execution","operationId":"createExecution","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":164,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-execution.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"async","description":"Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"formData"}]}},"\/functions\/{functionId}\/executions\/{executionId}":{"get":{"summary":"Get Execution","operationId":"getExecution","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":166,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-execution.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"executionId","description":"Execution unique ID.","required":true,"type":"string","x-example":"[EXECUTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/tag":{"patch":{"summary":"Update Function Tag","operationId":"updateTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":158,"cookies":false,"type":"","demo":"docs\/examples\/functions\/update-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tag","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG]","in":"formData"}]}},"\/functions\/{functionId}\/tags":{"get":{"summary":"List Tags","operationId":"listTags","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":161,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list-tags.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-tags.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Tag","operationId":"createTag","consumes":["multipart\/form-data"],"tags":["functions"],"description":"","extensions":{"weight":160,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"command","description":"Code execution command.","required":true,"type":"string","x-example":"[COMMAND]","in":"formData"},{"name":"code","description":"Gzip file containing your code.","required":true,"type":"file","in":"formData"}]}},"\/functions\/{functionId}\/tags\/{tagId}":{"get":{"summary":"Get Tag","operationId":"getTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":162,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]},"delete":{"summary":"Delete Tag","operationId":"deleteTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":163,"cookies":false,"type":"","demo":"docs\/examples\/functions\/delete-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]}},"\/health":{"get":{"summary":"Get HTTP","operationId":"get","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite HTTP server is up and responsive.","extensions":{"weight":80,"cookies":false,"type":"","demo":"docs\/examples\/health\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/anti-virus":{"get":{"summary":"Get Anti virus","operationId":"getAntiVirus","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite Anti Virus server is up and connection is successful.","extensions":{"weight":92,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-anti-virus.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-anti-virus.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/cache":{"get":{"summary":"Get Cache","operationId":"getCache","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite in-memory cache server is up and connection is successful.","extensions":{"weight":83,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-cache.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-cache.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/db":{"get":{"summary":"Get DB","operationId":"getDB","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite database server is up and connection is successful.","extensions":{"weight":82,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-d-b.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-db.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/certificates":{"get":{"summary":"Get Certificate Queue","operationId":"getQueueCertificates","consumes":["application\/json"],"tags":["health"],"description":"Get the number of certificates that are waiting to be issued against [Letsencrypt](https:\/\/letsencrypt.org\/) in the Appwrite internal queue server.","extensions":{"weight":89,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-certificates.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-certificates.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/functions":{"get":{"summary":"Get Functions Queue","operationId":"getQueueFunctions","consumes":["application\/json"],"tags":["health"],"description":"","extensions":{"weight":90,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-functions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/logs":{"get":{"summary":"Get Logs Queue","operationId":"getQueueLogs","consumes":["application\/json"],"tags":["health"],"description":"Get the number of logs that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":87,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/tasks":{"get":{"summary":"Get Tasks Queue","operationId":"getQueueTasks","consumes":["application\/json"],"tags":["health"],"description":"Get the number of tasks that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":86,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-tasks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-tasks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/usage":{"get":{"summary":"Get Usage Queue","operationId":"getQueueUsage","consumes":["application\/json"],"tags":["health"],"description":"Get the number of usage stats that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":88,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/webhooks":{"get":{"summary":"Get Webhooks Queue","operationId":"getQueueWebhooks","consumes":["application\/json"],"tags":["health"],"description":"Get the number of webhooks that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":85,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-webhooks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-webhooks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/storage\/local":{"get":{"summary":"Get Local Storage","operationId":"getStorageLocal","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite local storage device is up and connection is successful.","extensions":{"weight":91,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-storage-local.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-local.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/time":{"get":{"summary":"Get Time","operationId":"getTime","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite server time is synced with Google remote NTP server. We use this technology to smoothly handle leap seconds with no disruptive events. The [Network Time Protocol](https:\/\/en.wikipedia.org\/wiki\/Network_Time_Protocol) (NTP) is used by hundreds of millions of computers and devices to synchronize their clocks over the Internet. If your computer sets its own clock, it likely uses NTP.","extensions":{"weight":84,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-time.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-time.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"get","consumes":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","extensions":{"weight":73,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"getContinents","consumes":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","extensions":{"weight":77,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"getCountries","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","extensions":{"weight":74,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"getCountriesEU","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","extensions":{"weight":75,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"getCountriesPhones","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","extensions":{"weight":76,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"getCurrencies","consumes":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","extensions":{"weight":78,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"getLanguages","consumes":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","extensions":{"weight":79,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/projects":{"get":{"summary":"List Projects","operationId":"list","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":95,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Project","operationId":"create","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":94,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Project name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"formData"},{"name":"description","description":"Project description. Max length: 256 chars.","required":false,"type":"string","x-example":"[DESCRIPTION]","default":"","in":"formData"},{"name":"logo","description":"Project logo.","required":false,"type":"string","x-example":"[LOGO]","default":"","in":"formData"},{"name":"url","description":"Project URL.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"","in":"formData"},{"name":"legalName","description":"Project legal Name. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_NAME]","default":"","in":"formData"},{"name":"legalCountry","description":"Project legal Country. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_COUNTRY]","default":"","in":"formData"},{"name":"legalState","description":"Project legal State. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_STATE]","default":"","in":"formData"},{"name":"legalCity","description":"Project legal City. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_CITY]","default":"","in":"formData"},{"name":"legalAddress","description":"Project legal Address. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_ADDRESS]","default":"","in":"formData"},{"name":"legalTaxId","description":"Project legal Tax ID. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_TAX_ID]","default":"","in":"formData"}]}},"\/projects\/{projectId}":{"get":{"summary":"Get Project","operationId":"get","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":96,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"patch":{"summary":"Update Project","operationId":"update","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":98,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"name","description":"Project name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"description","description":"Project description. Max length: 256 chars.","required":false,"type":"string","x-example":"[DESCRIPTION]","default":"","in":"formData"},{"name":"logo","description":"Project logo.","required":false,"type":"string","x-example":"[LOGO]","default":"","in":"formData"},{"name":"url","description":"Project URL.","required":false,"type":"string","format":"url","x-example":"https:\/\/example.com","default":"","in":"formData"},{"name":"legalName","description":"Project legal name. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_NAME]","default":"","in":"formData"},{"name":"legalCountry","description":"Project legal country. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_COUNTRY]","default":"","in":"formData"},{"name":"legalState","description":"Project legal state. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_STATE]","default":"","in":"formData"},{"name":"legalCity","description":"Project legal city. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_CITY]","default":"","in":"formData"},{"name":"legalAddress","description":"Project legal address. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_ADDRESS]","default":"","in":"formData"},{"name":"legalTaxId","description":"Project legal tax ID. Max length: 256 chars.","required":false,"type":"string","x-example":"[LEGAL_TAX_ID]","default":"","in":"formData"}]},"delete":{"summary":"Delete Project","operationId":"delete","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":100,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"password","description":"Your user password for confirmation. Must be between 6 to 32 chars.","required":true,"type":"string","x-example":"[PASSWORD]","in":"formData"}]}},"\/projects\/{projectId}\/domains":{"get":{"summary":"List Domains","operationId":"listDomains","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":122,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list-domains.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"post":{"summary":"Create Domain","operationId":"createDomain","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":121,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"domain","description":"Domain name.","required":true,"type":"string","in":"formData"}]}},"\/projects\/{projectId}\/domains\/{domainId}":{"get":{"summary":"Get Domain","operationId":"getDomain","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":123,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","x-example":"[DOMAIN_ID]","in":"path"}]},"delete":{"summary":"Delete Domain","operationId":"deleteDomain","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":125,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete-domain.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","x-example":"[DOMAIN_ID]","in":"path"}]}},"\/projects\/{projectId}\/domains\/{domainId}\/verification":{"patch":{"summary":"Update Domain Verification Status","operationId":"updateDomainVerification","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":124,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-domain-verification.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"domainId","description":"Domain unique ID.","required":true,"type":"string","x-example":"[DOMAIN_ID]","in":"path"}]}},"\/projects\/{projectId}\/keys":{"get":{"summary":"List Keys","operationId":"listKeys","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":107,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list-keys.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"post":{"summary":"Create Key","operationId":"createKey","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":106,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"name","description":"Key name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"scopes","description":"Key scopes list.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/projects\/{projectId}\/keys\/{keyId}":{"get":{"summary":"Get Key","operationId":"getKey","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":108,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","x-example":"[KEY_ID]","in":"path"}]},"put":{"summary":"Update Key","operationId":"updateKey","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":109,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","x-example":"[KEY_ID]","in":"path"},{"name":"name","description":"Key name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"scopes","description":"Key scopes list","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Key","operationId":"deleteKey","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":110,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete-key.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"keyId","description":"Key unique ID.","required":true,"type":"string","x-example":"[KEY_ID]","in":"path"}]}},"\/projects\/{projectId}\/oauth2":{"patch":{"summary":"Update Project OAuth2","operationId":"updateOAuth2","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":99,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-o-auth2.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"provider","description":"Provider Name","required":true,"type":"string","x-example":"amazon","in":"formData"},{"name":"appId","description":"Provider app ID. Max length: 256 chars.","required":false,"type":"string","x-example":"[APP_ID]","default":"","in":"formData"},{"name":"secret","description":"Provider secret key. Max length: 512 chars.","required":false,"type":"string","x-example":"[SECRET]","default":"","in":"formData"}]}},"\/projects\/{projectId}\/platforms":{"get":{"summary":"List Platforms","operationId":"listPlatforms","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":117,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list-platforms.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"post":{"summary":"Create Platform","operationId":"createPlatform","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":116,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"type","description":"Platform type.","required":true,"type":"string","x-example":"web","in":"formData"},{"name":"name","description":"Platform name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"key","description":"Package name for android or bundle ID for iOS. Max length: 256 chars.","required":false,"type":"string","x-example":"[KEY]","default":"","in":"formData"},{"name":"store","description":"App store or Google Play store ID. Max length: 256 chars.","required":false,"type":"string","x-example":"[STORE]","default":"","in":"formData"},{"name":"hostname","description":"Platform client hostname. Max length: 256 chars.","required":false,"type":"string","x-example":"[HOSTNAME]","default":"","in":"formData"}]}},"\/projects\/{projectId}\/platforms\/{platformId}":{"get":{"summary":"Get Platform","operationId":"getPlatform","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":118,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","x-example":"[PLATFORM_ID]","in":"path"}]},"put":{"summary":"Update Platform","operationId":"updatePlatform","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":119,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","x-example":"[PLATFORM_ID]","in":"path"},{"name":"name","description":"Platform name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"key","description":"Package name for android or bundle ID for iOS. Max length: 256 chars.","required":false,"type":"string","x-example":"[KEY]","default":"","in":"formData"},{"name":"store","description":"App store or Google Play store ID. Max length: 256 chars.","required":false,"type":"string","x-example":"[STORE]","default":"","in":"formData"},{"name":"hostname","description":"Platform client URL. Max length: 256 chars.","required":false,"type":"string","x-example":"[HOSTNAME]","default":"","in":"formData"}]},"delete":{"summary":"Delete Platform","operationId":"deletePlatform","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":120,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete-platform.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"platformId","description":"Platform unique ID.","required":true,"type":"string","x-example":"[PLATFORM_ID]","in":"path"}]}},"\/projects\/{projectId}\/tasks":{"get":{"summary":"List Tasks","operationId":"listTasks","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":112,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list-tasks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"post":{"summary":"Create Task","operationId":"createTask","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":111,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"name","description":"Task name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"status","description":"Task status.","required":true,"type":"string","x-example":"play","in":"formData"},{"name":"schedule","description":"Task schedule CRON syntax.","required":true,"type":"string","in":"formData"},{"name":"security","description":"Certificate verification, false for disabled or true for enabled.","required":true,"type":"boolean","x-example":false,"in":"formData"},{"name":"httpMethod","description":"Task HTTP method.","required":true,"type":"string","x-example":"GET","in":"formData"},{"name":"httpUrl","description":"Task HTTP URL","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"},{"name":"httpHeaders","description":"Task HTTP headers list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"httpUser","description":"Task HTTP user. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_USER]","default":"","in":"formData"},{"name":"httpPass","description":"Task HTTP password. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_PASS]","default":"","in":"formData"}]}},"\/projects\/{projectId}\/tasks\/{taskId}":{"get":{"summary":"Get Task","operationId":"getTask","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":113,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","x-example":"[TASK_ID]","in":"path"}]},"put":{"summary":"Update Task","operationId":"updateTask","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":114,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","x-example":"[TASK_ID]","in":"path"},{"name":"name","description":"Task name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"status","description":"Task status.","required":true,"type":"string","x-example":"play","in":"formData"},{"name":"schedule","description":"Task schedule CRON syntax.","required":true,"type":"string","in":"formData"},{"name":"security","description":"Certificate verification, false for disabled or true for enabled.","required":true,"type":"boolean","x-example":false,"in":"formData"},{"name":"httpMethod","description":"Task HTTP method.","required":true,"type":"string","x-example":"GET","in":"formData"},{"name":"httpUrl","description":"Task HTTP URL.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"},{"name":"httpHeaders","description":"Task HTTP headers list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"httpUser","description":"Task HTTP user. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_USER]","default":"","in":"formData"},{"name":"httpPass","description":"Task HTTP password. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_PASS]","default":"","in":"formData"}]},"delete":{"summary":"Delete Task","operationId":"deleteTask","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":115,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete-task.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"taskId","description":"Task unique ID.","required":true,"type":"string","x-example":"[TASK_ID]","in":"path"}]}},"\/projects\/{projectId}\/usage":{"get":{"summary":"Get Project","operationId":"getUsage","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":97,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"range","description":"Date range.","required":false,"type":"string","x-example":"daily","default":"last30","in":"query"}]}},"\/projects\/{projectId}\/webhooks":{"get":{"summary":"List Webhooks","operationId":"listWebhooks","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":102,"cookies":false,"type":"","demo":"docs\/examples\/projects\/list-webhooks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"}]},"post":{"summary":"Create Webhook","operationId":"createWebhook","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":101,"cookies":false,"type":"","demo":"docs\/examples\/projects\/create-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"name","description":"Webhook name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"events","description":"Events list.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"Webhook URL.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"},{"name":"security","description":"Certificate verification, false for disabled or true for enabled.","required":true,"type":"boolean","x-example":false,"in":"formData"},{"name":"httpUser","description":"Webhook HTTP user. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_USER]","default":"","in":"formData"},{"name":"httpPass","description":"Webhook HTTP password. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_PASS]","default":"","in":"formData"}]}},"\/projects\/{projectId}\/webhooks\/{webhookId}":{"get":{"summary":"Get Webhook","operationId":"getWebhook","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":103,"cookies":false,"type":"","demo":"docs\/examples\/projects\/get-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.read","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","x-example":"[WEBHOOK_ID]","in":"path"}]},"put":{"summary":"Update Webhook","operationId":"updateWebhook","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":104,"cookies":false,"type":"","demo":"docs\/examples\/projects\/update-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","x-example":"[WEBHOOK_ID]","in":"path"},{"name":"name","description":"Webhook name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"events","description":"Events list.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"Webhook URL.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"},{"name":"security","description":"Certificate verification, false for disabled or true for enabled.","required":true,"type":"boolean","x-example":false,"in":"formData"},{"name":"httpUser","description":"Webhook HTTP user. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_USER]","default":"","in":"formData"},{"name":"httpPass","description":"Webhook HTTP password. Max length: 256 chars.","required":false,"type":"string","x-example":"[HTTP_PASS]","default":"","in":"formData"}]},"delete":{"summary":"Delete Webhook","operationId":"deleteWebhook","consumes":["application\/json"],"tags":["projects"],"description":"","extensions":{"weight":105,"cookies":false,"type":"","demo":"docs\/examples\/projects\/delete-webhook.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"projects.write","platforms":[]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"projectId","description":"Project unique ID.","required":true,"type":"string","x-example":"[PROJECT_ID]","in":"path"},{"name":"webhookId","description":"Webhook unique ID.","required":true,"type":"string","x-example":"[WEBHOOK_ID]","in":"path"}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"listFiles","consumes":["application\/json"],"tags":["storage"],"description":"Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":127,"cookies":false,"type":"","demo":"docs\/examples\/storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"createFile","consumes":["multipart\/form-data"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","extensions":{"weight":126,"cookies":false,"type":"upload","demo":"docs\/examples\/storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"file","description":"Binary file.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"getFile","consumes":["application\/json"],"tags":["storage"],"description":"Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.","extensions":{"weight":128,"cookies":false,"type":"","demo":"docs\/examples\/storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"updateFile","consumes":["application\/json"],"tags":["storage"],"description":"Update file by its unique ID. Only users with write permissions have access to update this resource.","extensions":{"weight":132,"cookies":false,"type":"","demo":"docs\/examples\/storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete File","operationId":"deleteFile","consumes":["application\/json"],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":133,"cookies":false,"type":"","demo":"docs\/examples\/storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"getFileDownload","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.","extensions":{"weight":130,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"getFilePreview","consumes":["application\/json"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","extensions":{"weight":129,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","default":"","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"getFileView","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","extensions":{"weight":131,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"as","description":"Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.","required":false,"type":"string","x-example":"pdf","default":"","in":"query"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"list","consumes":["application\/json"],"tags":["teams"],"description":"Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":135,"cookies":false,"type":"","demo":"docs\/examples\/teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"create","consumes":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","extensions":{"weight":134,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":["owner"],"in":"formData"}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"get","consumes":["application\/json"],"tags":["teams"],"description":"Get team by its unique ID. All team members have read access for this resource.","extensions":{"weight":136,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"update","consumes":["application\/json"],"tags":["teams"],"description":"Update team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":137,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]},"delete":{"summary":"Delete Team","operationId":"delete","consumes":["application\/json"],"tags":["teams"],"description":"Delete team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":138,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"getMemberships","consumes":["application\/json"],"tags":["teams"],"description":"Get team members by the team unique ID. All team members have read access for this list of resources.","extensions":{"weight":140,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"createMembership","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","extensions":{"weight":139,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"email","description":"New team member email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"name","description":"New team member name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"URL to redirect the user back to your app from the invitation 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}":{"delete":{"summary":"Delete Team Membership","operationId":"deleteMembership","consumes":["application\/json"],"tags":["teams"],"description":"This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it.","extensions":{"weight":142,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}\/status":{"patch":{"summary":"Update Team Membership Status","operationId":"updateMembershipStatus","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to allow a user to accept an invitation to join a team after he is being redirected back to your app from the invitation email he was sent.","extensions":{"weight":141,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update-membership-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team-membership-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"public","platforms":["client"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"},{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"formData"},{"name":"secret","description":"Secret key.","required":true,"type":"string","x-example":"[SECRET]","in":"formData"}]}},"\/users":{"get":{"summary":"List Users","operationId":"list","consumes":["application\/json"],"tags":["users"],"description":"Get a list of all the project users. You can use the query params to filter your results.","extensions":{"weight":144,"cookies":false,"type":"","demo":"docs\/examples\/users\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/list-users.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create User","operationId":"create","consumes":["application\/json"],"tags":["users"],"description":"Create a new user.","extensions":{"weight":143,"cookies":false,"type":"","demo":"docs\/examples\/users\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/create-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]}},"\/users\/{userId}":{"get":{"summary":"Get User","operationId":"get","consumes":["application\/json"],"tags":["users"],"description":"Get user by its unique ID.","extensions":{"weight":145,"cookies":false,"type":"","demo":"docs\/examples\/users\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"delete":{"summary":"Delete User","operationId":"deleteUser","consumes":["application\/json"],"tags":["users"],"description":"Delete a user by its unique ID.","extensions":{"weight":153,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-user.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/logs":{"get":{"summary":"Get User Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["users"],"description":"Get user activity logs list by its unique ID.","extensions":{"weight":148,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/prefs":{"get":{"summary":"Get User Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["users"],"description":"Get user preferences by its unique ID.","extensions":{"weight":146,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"patch":{"summary":"Update User Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["users"],"description":"Update user preferences by its unique ID. You can pass only the specific settings you wish to update.","extensions":{"weight":150,"cookies":false,"type":"","demo":"docs\/examples\/users\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/users\/{userId}\/sessions":{"get":{"summary":"Get User Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["users"],"description":"Get user sessions list by its unique ID.","extensions":{"weight":147,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"delete":{"summary":"Delete User Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["users"],"description":"Delete all user sessions by its unique ID.","extensions":{"weight":152,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/sessions\/{sessionId}":{"delete":{"summary":"Delete User Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["users"],"description":"Delete user sessions by its unique ID.","extensions":{"weight":151,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"sessionId","description":"User unique session ID.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/users\/{userId}\/status":{"patch":{"summary":"Update User Status","operationId":"updateStatus","consumes":["application\/json"],"tags":["users"],"description":"Update user status by its unique ID.","extensions":{"weight":149,"cookies":false,"type":"","demo":"docs\/examples\/users\/update-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"status","description":"User Status code. To activate the user pass 1, to block the user pass 2 and for disabling the user pass 0","required":true,"type":"string","x-example":1,"in":"formData"}]}}},"definitions":{"Error":{"required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/localhost\/docs"}}
\ No newline at end of file
diff --git a/app/config/specs/0.7.0.server.json b/app/config/specs/0.7.0.server.json
new file mode 100644
index 0000000000..4b7c8546f8
--- /dev/null
+++ b/app/config/specs/0.7.0.server.json
@@ -0,0 +1,2 @@
+
+{"swagger":"2.0","info":{"version":"0.7.0","title":"Appwrite","description":"Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)","termsOfService":"https:\/\/appwrite.io\/policy\/terms","contact":{"name":"Appwrite Team","url":"https:\/\/appwrite.io\/support","email":"team@localhost.test"},"license":{"name":"BSD-3-Clause","url":"https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE"}},"host":"appwrite.io","basePath":"\/v1","schemes":["https"],"consumes":["application\/json","multipart\/form-data"],"produces":["application\/json"],"securityDefinitions":{"Project":{"type":"apiKey","name":"X-Appwrite-Project","description":"Your project ID","in":"header","extensions":{"demo":"5df5acd0d48c2"}},"Key":{"type":"apiKey","name":"X-Appwrite-Key","description":"Your secret API key","in":"header","extensions":{"demo":"919c2d18fb5d4...a2ae413da83346ad2"}},"Locale":{"type":"apiKey","name":"X-Appwrite-Locale","description":"","in":"header","extensions":{"demo":"en"}}},"paths":{"\/avatars\/browsers\/{code}":{"get":{"summary":"Get Browser Icon","operationId":"getBrowser","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user \/account\/sessions endpoint. Use width, height and quality arguments to change the output settings.","extensions":{"weight":57,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-browser.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-browser.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Browser Code.","required":true,"type":"string","x-example":"aa","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/credit-cards\/{code}":{"get":{"summary":"Get Credit Card Icon","operationId":"getCreditCard","consumes":["application\/json"],"tags":["avatars"],"description":"Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.","extensions":{"weight":56,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-credit-card.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-credit-card.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa.","required":true,"type":"string","x-example":"amex","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/favicon":{"get":{"summary":"Get Favicon","operationId":"getFavicon","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.","extensions":{"weight":60,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-favicon.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-favicon.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"url","description":"Website URL which you want to fetch the favicon from.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"}]}},"\/avatars\/flags\/{code}":{"get":{"summary":"Get Country Flag","operationId":"getFlag","consumes":["application\/json"],"tags":["avatars"],"description":"You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings.","extensions":{"weight":58,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-flag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-flag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"code","description":"Country Code. ISO Alpha-2 country code format.","required":true,"type":"string","x-example":"af","in":"path"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"quality","description":"Image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"}]}},"\/avatars\/image":{"get":{"summary":"Get Image from URL","operationId":"getImage","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.","extensions":{"weight":59,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-image.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-image.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"url","description":"Image URL which you want to crop.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"query"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 2000.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"}]}},"\/avatars\/initials":{"get":{"summary":"Get User Initials","operationId":"getInitials","consumes":["application\/json"],"tags":["avatars"],"description":"Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.","extensions":{"weight":62,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-initials.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-initials.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Full Name. When empty, current user name or email will be used. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"query"},{"name":"width","description":"Image width. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"height","description":"Image height. Pass an integer between 0 to 2000. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":500,"in":"query"},{"name":"color","description":"Changes text color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"},{"name":"background","description":"Changes background color. By default a random color will be picked and stay will persistent to the given name.","required":false,"type":"string","default":"","in":"query"}]}},"\/avatars\/qr":{"get":{"summary":"Get QR Code","operationId":"getQR","consumes":["application\/json"],"tags":["avatars"],"description":"Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.","extensions":{"weight":61,"cookies":false,"type":"location","demo":"docs\/examples\/avatars\/get-q-r.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/avatars\/get-qr.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"avatars.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"text","description":"Plain text to be converted to QR code image.","required":true,"type":"string","x-example":"[TEXT]","in":"query"},{"name":"size","description":"QR code size. Pass an integer between 0 to 1000. Defaults to 400.","required":false,"type":"integer","format":"int32","x-example":0,"default":400,"in":"query"},{"name":"margin","description":"Margin from edge. Pass an integer between 0 to 10. Defaults to 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"query"},{"name":"download","description":"Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.","required":false,"type":"boolean","x-example":false,"default":false,"in":"query"}]}},"\/database\/collections":{"get":{"summary":"List Collections","operationId":"listCollections","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project collections. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":64,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-collections.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-collections.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Collection","operationId":"createCollection","consumes":["application\/json"],"tags":["database"],"description":"Create a new Collection.","extensions":{"weight":63,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Collection name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"rules","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/database\/collections\/{collectionId}":{"get":{"summary":"Get Collection","operationId":"getCollection","consumes":["application\/json"],"tags":["database"],"description":"Get collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.","extensions":{"weight":65,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]},"put":{"summary":"Update Collection","operationId":"updateCollection","consumes":["application\/json"],"tags":["database"],"description":"Update collection by its unique ID.","extensions":{"weight":66,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"name","description":"Collection name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"rules","description":"Array of [rule objects](\/docs\/rules). Each rule define a collection field name, data type and validation.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"}]},"delete":{"summary":"Delete Collection","operationId":"deleteCollection","consumes":["application\/json"],"tags":["database"],"description":"Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":67,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-collection.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-collection.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"collections.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID.","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"}]}},"\/database\/collections\/{collectionId}\/documents":{"get":{"summary":"List Documents","operationId":"listDocuments","consumes":["application\/json"],"tags":["database"],"description":"Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":69,"cookies":false,"type":"","demo":"docs\/examples\/database\/list-documents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/list-documents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"filters","description":"Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"query"},{"name":"limit","description":"Maximum number of documents to return in response. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Offset value. Use this value to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderField","description":"Document field that results will be sorted by.","required":false,"type":"string","x-example":"[ORDER_FIELD]","default":"$id","in":"query"},{"name":"orderType","description":"Order direction. Possible values are DESC for descending order, or ASC for ascending order.","required":false,"type":"string","x-example":"DESC","default":"ASC","in":"query"},{"name":"orderCast","description":"Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.","required":false,"type":"string","x-example":"int","default":"string","in":"query"},{"name":"search","description":"Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"}]},"post":{"summary":"Create Document","operationId":"createDocument","consumes":["application\/json"],"tags":["database"],"description":"Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](\/docs\/server\/database?sdk=nodejs#createCollection) API or directly from your database console.","extensions":{"weight":68,"cookies":false,"type":"","demo":"docs\/examples\/database\/create-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/create-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"parentDocument","description":"Parent document unique ID. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"[PARENT_DOCUMENT]","default":"","in":"formData"},{"name":"parentProperty","description":"Parent document property name. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","default":"","in":"formData"},{"name":"parentPropertyType","description":"Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.","required":false,"type":"string","x-example":"assign","default":"assign","in":"formData"}]}},"\/database\/collections\/{collectionId}\/documents\/{documentId}":{"get":{"summary":"Get Document","operationId":"getDocument","consumes":["application\/json"],"tags":["database"],"description":"Get document by its unique ID. This endpoint response returns a JSON object with the document data.","extensions":{"weight":70,"cookies":false,"type":"","demo":"docs\/examples\/database\/get-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/get-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]},"patch":{"summary":"Update Document","operationId":"updateDocument","consumes":["application\/json"],"tags":["database"],"description":"","extensions":{"weight":71,"cookies":false,"type":"","demo":"docs\/examples\/database\/update-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/update-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"},{"name":"data","description":"Document data as JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete Document","operationId":"deleteDocument","consumes":["application\/json"],"tags":["database"],"description":"Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted.","extensions":{"weight":72,"cookies":false,"type":"","demo":"docs\/examples\/database\/delete-document.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/database\/delete-document.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"documents.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"collectionId","description":"Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](\/docs\/server\/database#createCollection).","required":true,"type":"string","x-example":"[COLLECTION_ID]","in":"path"},{"name":"documentId","description":"Document unique ID.","required":true,"type":"string","x-example":"[DOCUMENT_ID]","in":"path"}]}},"\/functions":{"get":{"summary":"List Functions","operationId":"list","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":155,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Function","operationId":"create","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":154,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Function name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"env","description":"Execution enviornment.","required":true,"type":"string","x-example":"node-14","in":"formData"},{"name":"vars","description":"Key-value JSON object.","required":false,"type":"object","x-example":"{}","default":[],"in":"formData"},{"name":"events","description":"Events list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"},{"name":"schedule","description":"Schedule CRON syntax.","required":false,"type":"string","default":"","in":"formData"},{"name":"timeout","description":"Function maximum execution time in seconds.","required":false,"type":"integer","format":"int32","x-example":1,"default":15,"in":"formData"}]}},"\/functions\/{functionId}":{"get":{"summary":"Get Function","operationId":"get","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":156,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]},"put":{"summary":"Update Function","operationId":"update","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":157,"cookies":false,"type":"","demo":"docs\/examples\/functions\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"name","description":"Function name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"vars","description":"Key-value JSON object.","required":false,"type":"object","x-example":"{}","default":[],"in":"formData"},{"name":"events","description":"Events list.","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":[],"in":"formData"},{"name":"schedule","description":"Schedule CRON syntax.","required":false,"type":"string","default":"","in":"formData"},{"name":"timeout","description":"Function maximum execution time in seconds.","required":false,"type":"integer","format":"int32","x-example":1,"default":15,"in":"formData"}]},"delete":{"summary":"Delete Function","operationId":"delete","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":159,"cookies":false,"type":"","demo":"docs\/examples\/functions\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-function.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/executions":{"get":{"summary":"List Executions","operationId":"listExecutions","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":165,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list-executions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-executions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Execution","operationId":"createExecution","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":164,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-execution.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"async","description":"Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.","required":false,"type":"integer","format":"int32","x-example":0,"default":1,"in":"formData"}]}},"\/functions\/{functionId}\/executions\/{executionId}":{"get":{"summary":"Get Execution","operationId":"getExecution","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":166,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get-execution.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-execution.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"executionId","description":"Execution unique ID.","required":true,"type":"string","x-example":"[EXECUTION_ID]","in":"path"}]}},"\/functions\/{functionId}\/tag":{"patch":{"summary":"Update Function Tag","operationId":"updateTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":158,"cookies":false,"type":"","demo":"docs\/examples\/functions\/update-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/update-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tag","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG]","in":"formData"}]}},"\/functions\/{functionId}\/tags":{"get":{"summary":"List Tags","operationId":"listTags","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":161,"cookies":false,"type":"","demo":"docs\/examples\/functions\/list-tags.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/list-tags.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Tag","operationId":"createTag","consumes":["multipart\/form-data"],"tags":["functions"],"description":"","extensions":{"weight":160,"cookies":false,"type":"","demo":"docs\/examples\/functions\/create-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/create-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"command","description":"Code execution command.","required":true,"type":"string","x-example":"[COMMAND]","in":"formData"},{"name":"code","description":"Gzip file containing your code.","required":true,"type":"file","in":"formData"}]}},"\/functions\/{functionId}\/tags\/{tagId}":{"get":{"summary":"Get Tag","operationId":"getTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":162,"cookies":false,"type":"","demo":"docs\/examples\/functions\/get-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/get-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]},"delete":{"summary":"Delete Tag","operationId":"deleteTag","consumes":["application\/json"],"tags":["functions"],"description":"","extensions":{"weight":163,"cookies":false,"type":"","demo":"docs\/examples\/functions\/delete-tag.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/functions\/delete-tag.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"functions.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"functionId","description":"Function unique ID.","required":true,"type":"string","x-example":"[FUNCTION_ID]","in":"path"},{"name":"tagId","description":"Tag unique ID.","required":true,"type":"string","x-example":"[TAG_ID]","in":"path"}]}},"\/health":{"get":{"summary":"Get HTTP","operationId":"get","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite HTTP server is up and responsive.","extensions":{"weight":80,"cookies":false,"type":"","demo":"docs\/examples\/health\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/anti-virus":{"get":{"summary":"Get Anti virus","operationId":"getAntiVirus","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite Anti Virus server is up and connection is successful.","extensions":{"weight":92,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-anti-virus.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-anti-virus.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/cache":{"get":{"summary":"Get Cache","operationId":"getCache","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite in-memory cache server is up and connection is successful.","extensions":{"weight":83,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-cache.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-cache.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/db":{"get":{"summary":"Get DB","operationId":"getDB","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite database server is up and connection is successful.","extensions":{"weight":82,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-d-b.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-db.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/certificates":{"get":{"summary":"Get Certificate Queue","operationId":"getQueueCertificates","consumes":["application\/json"],"tags":["health"],"description":"Get the number of certificates that are waiting to be issued against [Letsencrypt](https:\/\/letsencrypt.org\/) in the Appwrite internal queue server.","extensions":{"weight":89,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-certificates.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-certificates.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/functions":{"get":{"summary":"Get Functions Queue","operationId":"getQueueFunctions","consumes":["application\/json"],"tags":["health"],"description":"","extensions":{"weight":90,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-functions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-functions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/logs":{"get":{"summary":"Get Logs Queue","operationId":"getQueueLogs","consumes":["application\/json"],"tags":["health"],"description":"Get the number of logs that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":87,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/tasks":{"get":{"summary":"Get Tasks Queue","operationId":"getQueueTasks","consumes":["application\/json"],"tags":["health"],"description":"Get the number of tasks that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":86,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-tasks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-tasks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/usage":{"get":{"summary":"Get Usage Queue","operationId":"getQueueUsage","consumes":["application\/json"],"tags":["health"],"description":"Get the number of usage stats that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":88,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-usage.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-usage.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/queue\/webhooks":{"get":{"summary":"Get Webhooks Queue","operationId":"getQueueWebhooks","consumes":["application\/json"],"tags":["health"],"description":"Get the number of webhooks that are waiting to be processed in the Appwrite internal queue server.","extensions":{"weight":85,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-queue-webhooks.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-queue-webhooks.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/storage\/local":{"get":{"summary":"Get Local Storage","operationId":"getStorageLocal","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite local storage device is up and connection is successful.","extensions":{"weight":91,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-storage-local.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-storage-local.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/health\/time":{"get":{"summary":"Get Time","operationId":"getTime","consumes":["application\/json"],"tags":["health"],"description":"Check the Appwrite server time is synced with Google remote NTP server. We use this technology to smoothly handle leap seconds with no disruptive events. The [Network Time Protocol](https:\/\/en.wikipedia.org\/wiki\/Network_Time_Protocol) (NTP) is used by hundreds of millions of computers and devices to synchronize their clocks over the Internet. If your computer sets its own clock, it likely uses NTP.","extensions":{"weight":84,"cookies":false,"type":"","demo":"docs\/examples\/health\/get-time.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/health\/get-time.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"health.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale":{"get":{"summary":"Get User Locale","operationId":"get","consumes":["application\/json"],"tags":["locale"],"description":"Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))","extensions":{"weight":73,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-locale.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/continents":{"get":{"summary":"List Continents","operationId":"getContinents","consumes":["application\/json"],"tags":["locale"],"description":"List of all continents. You can use the locale header to get the data in a supported language.","extensions":{"weight":77,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-continents.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-continents.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries":{"get":{"summary":"List Countries","operationId":"getCountries","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries. You can use the locale header to get the data in a supported language.","extensions":{"weight":74,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries\/eu":{"get":{"summary":"List EU Countries","operationId":"getCountriesEU","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language.","extensions":{"weight":75,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-e-u.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-eu.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/countries\/phones":{"get":{"summary":"List Countries Phone Codes","operationId":"getCountriesPhones","consumes":["application\/json"],"tags":["locale"],"description":"List of all countries phone codes. You can use the locale header to get the data in a supported language.","extensions":{"weight":76,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-countries-phones.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-countries-phones.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/currencies":{"get":{"summary":"List Currencies","operationId":"getCurrencies","consumes":["application\/json"],"tags":["locale"],"description":"List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language.","extensions":{"weight":78,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-currencies.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-currencies.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/locale\/languages":{"get":{"summary":"List Languages","operationId":"getLanguages","consumes":["application\/json"],"tags":["locale"],"description":"List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language.","extensions":{"weight":79,"cookies":false,"type":"","demo":"docs\/examples\/locale\/get-languages.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/locale\/get-languages.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"locale.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}]}},"\/storage\/files":{"get":{"summary":"List Files","operationId":"listFiles","consumes":["application\/json"],"tags":["storage"],"description":"Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":127,"cookies":false,"type":"","demo":"docs\/examples\/storage\/list-files.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/list-files.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create File","operationId":"createFile","consumes":["multipart\/form-data"],"tags":["storage"],"description":"Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments.","extensions":{"weight":126,"cookies":false,"type":"upload","demo":"docs\/examples\/storage\/create-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/create-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"file","description":"Binary file.","required":true,"type":"file","in":"formData"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]}},"\/storage\/files\/{fileId}":{"get":{"summary":"Get File","operationId":"getFile","consumes":["application\/json"],"tags":["storage"],"description":"Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.","extensions":{"weight":128,"cookies":false,"type":"","demo":"docs\/examples\/storage\/get-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]},"put":{"summary":"Update File","operationId":"updateFile","consumes":["application\/json"],"tags":["storage"],"description":"Update file by its unique ID. Only users with write permissions have access to update this resource.","extensions":{"weight":132,"cookies":false,"type":"","demo":"docs\/examples\/storage\/update-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/update-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"read","description":"An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"write","description":"An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](\/docs\/permissions) and get a full list of available permissions.","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"}]},"delete":{"summary":"Delete File","operationId":"deleteFile","consumes":["application\/json"],"tags":["storage"],"description":"Delete a file by its unique ID. Only users with write permissions have access to delete this resource.","extensions":{"weight":133,"cookies":false,"type":"","demo":"docs\/examples\/storage\/delete-file.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/delete-file.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/download":{"get":{"summary":"Get File for Download","operationId":"getFileDownload","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.","extensions":{"weight":130,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-download.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-download.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"}]}},"\/storage\/files\/{fileId}\/preview":{"get":{"summary":"Get File Preview","operationId":"getFilePreview","consumes":["application\/json"],"tags":["storage"],"description":"Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image.","extensions":{"weight":129,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-preview.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-preview.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"width","description":"Resize preview image width, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"height","description":"Resize preview image height, Pass an integer between 0 to 4000.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"quality","description":"Preview image quality. Pass an integer between 0 to 100. Defaults to 100.","required":false,"type":"integer","format":"int32","x-example":0,"default":100,"in":"query"},{"name":"background","description":"Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.","required":false,"type":"string","default":"","in":"query"},{"name":"output","description":"Output format type (jpeg, jpg, png, gif and webp).","required":false,"type":"string","x-example":"jpg","default":"","in":"query"}]}},"\/storage\/files\/{fileId}\/view":{"get":{"summary":"Get File for View","operationId":"getFileView","consumes":["application\/json"],"tags":["storage"],"description":"Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.","extensions":{"weight":131,"cookies":false,"type":"location","demo":"docs\/examples\/storage\/get-file-view.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/storage\/get-file-view.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"files.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"fileId","description":"File unique ID.","required":true,"type":"string","x-example":"[FILE_ID]","in":"path"},{"name":"as","description":"Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.","required":false,"type":"string","x-example":"pdf","default":"","in":"query"}]}},"\/teams":{"get":{"summary":"List Teams","operationId":"list","consumes":["application\/json"],"tags":["teams"],"description":"Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](\/docs\/admin).","extensions":{"weight":135,"cookies":false,"type":"","demo":"docs\/examples\/teams\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/list-teams.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team","operationId":"create","consumes":["application\/json"],"tags":["teams"],"description":"Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project.","extensions":{"weight":134,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":false,"type":"array","collectionFormat":"multi","items":{"type":"string"},"default":["owner"],"in":"formData"}]}},"\/teams\/{teamId}":{"get":{"summary":"Get Team","operationId":"get","consumes":["application\/json"],"tags":["teams"],"description":"Get team by its unique ID. All team members have read access for this resource.","extensions":{"weight":136,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]},"put":{"summary":"Update Team","operationId":"update","consumes":["application\/json"],"tags":["teams"],"description":"Update team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":137,"cookies":false,"type":"","demo":"docs\/examples\/teams\/update.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/update-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"name","description":"Team name. Max length: 128 chars.","required":true,"type":"string","x-example":"[NAME]","in":"formData"}]},"delete":{"summary":"Delete Team","operationId":"delete","consumes":["application\/json"],"tags":["teams"],"description":"Delete team by its unique ID. Only team owners have write access for this resource.","extensions":{"weight":138,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"}]}},"\/teams\/{teamId}\/memberships":{"get":{"summary":"Get Team Memberships","operationId":"getMemberships","consumes":["application\/json"],"tags":["teams"],"description":"Get team members by the team unique ID. All team members have read access for this list of resources.","extensions":{"weight":140,"cookies":false,"type":"","demo":"docs\/examples\/teams\/get-memberships.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/get-team-members.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.read","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create Team Membership","operationId":"createMembership","consumes":["application\/json"],"tags":["teams"],"description":"Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.\n\nUse the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](\/docs\/client\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.\n\nPlease note that in order to avoid a [Redirect Attacks](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface.","extensions":{"weight":139,"cookies":false,"type":"","demo":"docs\/examples\/teams\/create-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/create-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"email","description":"New team member email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"name","description":"New team member name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"},{"name":"roles","description":"Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](\/docs\/permissions).","required":true,"type":"array","collectionFormat":"multi","items":{"type":"string"},"in":"formData"},{"name":"url","description":"URL to redirect the user back to your app from the invitation 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.","required":true,"type":"string","format":"url","x-example":"https:\/\/example.com","in":"formData"}]}},"\/teams\/{teamId}\/memberships\/{inviteId}":{"delete":{"summary":"Delete Team Membership","operationId":"deleteMembership","consumes":["application\/json"],"tags":["teams"],"description":"This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it.","extensions":{"weight":142,"cookies":false,"type":"","demo":"docs\/examples\/teams\/delete-membership.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/teams\/delete-team-membership.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"teams.write","platforms":["client","server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"teamId","description":"Team unique ID.","required":true,"type":"string","x-example":"[TEAM_ID]","in":"path"},{"name":"inviteId","description":"Invite unique ID.","required":true,"type":"string","x-example":"[INVITE_ID]","in":"path"}]}},"\/users":{"get":{"summary":"List Users","operationId":"list","consumes":["application\/json"],"tags":["users"],"description":"Get a list of all the project users. You can use the query params to filter your results.","extensions":{"weight":144,"cookies":false,"type":"","demo":"docs\/examples\/users\/list.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/list-users.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"search","description":"Search term to filter your list results. Max length: 256 chars.","required":false,"type":"string","x-example":"[SEARCH]","default":"","in":"query"},{"name":"limit","description":"Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.","required":false,"type":"integer","format":"int32","x-example":0,"default":25,"in":"query"},{"name":"offset","description":"Results offset. The default value is 0. Use this param to manage pagination.","required":false,"type":"integer","format":"int32","x-example":0,"default":0,"in":"query"},{"name":"orderType","description":"Order result by ASC or DESC order.","required":false,"type":"string","x-example":"ASC","default":"ASC","in":"query"}]},"post":{"summary":"Create User","operationId":"create","consumes":["application\/json"],"tags":["users"],"description":"Create a new user.","extensions":{"weight":143,"cookies":false,"type":"","demo":"docs\/examples\/users\/create.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/create-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"email","description":"User email.","required":true,"type":"string","format":"email","x-example":"email@example.com","in":"formData"},{"name":"password","description":"User password. Must be between 6 to 32 chars.","required":true,"type":"string","format":"format","x-example":"password","in":"formData"},{"name":"name","description":"User name. Max length: 128 chars.","required":false,"type":"string","x-example":"[NAME]","default":"","in":"formData"}]}},"\/users\/{userId}":{"get":{"summary":"Get User","operationId":"get","consumes":["application\/json"],"tags":["users"],"description":"Get user by its unique ID.","extensions":{"weight":145,"cookies":false,"type":"","demo":"docs\/examples\/users\/get.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"delete":{"summary":"Delete User","operationId":"deleteUser","consumes":["application\/json"],"tags":["users"],"description":"Delete a user by its unique ID.","extensions":{"weight":153,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-user.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/logs":{"get":{"summary":"Get User Logs","operationId":"getLogs","consumes":["application\/json"],"tags":["users"],"description":"Get user activity logs list by its unique ID.","extensions":{"weight":148,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-logs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-logs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/prefs":{"get":{"summary":"Get User Preferences","operationId":"getPrefs","consumes":["application\/json"],"tags":["users"],"description":"Get user preferences by its unique ID.","extensions":{"weight":146,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"patch":{"summary":"Update User Preferences","operationId":"updatePrefs","consumes":["application\/json"],"tags":["users"],"description":"Update user preferences by its unique ID. You can pass only the specific settings you wish to update.","extensions":{"weight":150,"cookies":false,"type":"","demo":"docs\/examples\/users\/update-prefs.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-prefs.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"prefs","description":"Prefs key-value JSON object.","required":true,"type":"object","x-example":"{}","in":"formData"}]}},"\/users\/{userId}\/sessions":{"get":{"summary":"Get User Sessions","operationId":"getSessions","consumes":["application\/json"],"tags":["users"],"description":"Get user sessions list by its unique ID.","extensions":{"weight":147,"cookies":false,"type":"","demo":"docs\/examples\/users\/get-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/get-user-sessions.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.read","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]},"delete":{"summary":"Delete User Sessions","operationId":"deleteSessions","consumes":["application\/json"],"tags":["users"],"description":"Delete all user sessions by its unique ID.","extensions":{"weight":152,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-sessions.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-sessions.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"}]}},"\/users\/{userId}\/sessions\/{sessionId}":{"delete":{"summary":"Delete User Session","operationId":"deleteSession","consumes":["application\/json"],"tags":["users"],"description":"Delete user sessions by its unique ID.","extensions":{"weight":151,"cookies":false,"type":"","demo":"docs\/examples\/users\/delete-session.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/delete-user-session.md","rate-limit":100,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"sessionId","description":"User unique session ID.","required":true,"type":"string","x-example":"[SESSION_ID]","in":"path"}]}},"\/users\/{userId}\/status":{"patch":{"summary":"Update User Status","operationId":"updateStatus","consumes":["application\/json"],"tags":["users"],"description":"Update user status by its unique ID.","extensions":{"weight":149,"cookies":false,"type":"","demo":"docs\/examples\/users\/update-status.md","edit":"https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/users\/update-user-status.md","rate-limit":0,"rate-time":3600,"rate-key":"url:{url},ip:{ip}","scope":"users.write","platforms":["server"]},"security":[{"Project":[],"Key":[]}],"parameters":[{"name":"userId","description":"User unique ID.","required":true,"type":"string","x-example":"[USER_ID]","in":"path"},{"name":"status","description":"User Status code. To activate the user pass 1, to block the user pass 2 and for disabling the user pass 0","required":true,"type":"string","x-example":1,"in":"formData"}]}}},"definitions":{"Error":{"required":["code","message"],"properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}},"externalDocs":{"description":"Full API docs, specs and tutorials","url":"https:\/\/localhost\/docs"}}
\ No newline at end of file
diff --git a/app/config/storage/inputs.php b/app/config/storage/inputs.php
new file mode 100644
index 0000000000..c580316c53
--- /dev/null
+++ b/app/config/storage/inputs.php
@@ -0,0 +1,8 @@
+ 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'png' => 'image/png',
+];
\ No newline at end of file
diff --git a/app/config/storage/logos.php b/app/config/storage/logos.php
new file mode 100644
index 0000000000..3170762dca
--- /dev/null
+++ b/app/config/storage/logos.php
@@ -0,0 +1,43 @@
+ __DIR__.'/logos/none.png',
+
+ // Video Files
+ 'video/mp4' => __DIR__.'/logos/video.png',
+ 'video/x-flv' => __DIR__.'/logos/video.png',
+ 'application/x-mpegURL' => __DIR__.'/logos/video.png',
+ 'video/MP2T' => __DIR__.'/logos/video.png',
+ 'video/3gpp' => __DIR__.'/logos/video.png',
+ 'video/quicktime' => __DIR__.'/logos/video.png',
+ 'video/x-msvideo' => __DIR__.'/logos/video.png',
+ 'video/x-ms-wmv' => __DIR__.'/logos/video.png',
+
+ // // Microsoft Word
+ 'application/msword' => __DIR__.'/logos/word.png',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png',
+ 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png',
+
+ // // Microsoft Excel
+ 'application/vnd.ms-excel' => __DIR__.'/logos/excel.png',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png',
+ 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png',
+ 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png',
+ 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png',
+
+ // // Microsoft Power Point
+ 'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png',
+ 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png',
+
+ // Adobe PDF
+ 'application/pdf' => __DIR__.'/logos/pdf.png',
+];
\ No newline at end of file
diff --git a/app/config/files/excel.png b/app/config/storage/logos/excel.png
similarity index 100%
rename from app/config/files/excel.png
rename to app/config/storage/logos/excel.png
diff --git a/app/config/files/none.png b/app/config/storage/logos/none.png
similarity index 100%
rename from app/config/files/none.png
rename to app/config/storage/logos/none.png
diff --git a/app/config/files/pdf.png b/app/config/storage/logos/pdf.png
similarity index 100%
rename from app/config/files/pdf.png
rename to app/config/storage/logos/pdf.png
diff --git a/app/config/files/ppt.png b/app/config/storage/logos/ppt.png
similarity index 100%
rename from app/config/files/ppt.png
rename to app/config/storage/logos/ppt.png
diff --git a/app/config/files/video.png b/app/config/storage/logos/video.png
similarity index 100%
rename from app/config/files/video.png
rename to app/config/storage/logos/video.png
diff --git a/app/config/files/word.png b/app/config/storage/logos/word.png
similarity index 100%
rename from app/config/files/word.png
rename to app/config/storage/logos/word.png
diff --git a/app/config/storage/mimes.php b/app/config/storage/mimes.php
new file mode 100644
index 0000000000..242a990b8d
--- /dev/null
+++ b/app/config/storage/mimes.php
@@ -0,0 +1,50 @@
+ 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'png' => 'image/png',
+ 'webp' => 'image/webp',
+];
\ No newline at end of file
diff --git a/app/config/variables.php b/app/config/variables.php
new file mode 100644
index 0000000000..c1b9982764
--- /dev/null
+++ b/app/config/variables.php
@@ -0,0 +1,136 @@
+ '_APP_ENV',
+ 'default' => 'production',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_OPTIONS_ABUSE',
+ 'default' => 'enabled',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_OPTIONS_FORCE_HTTPS',
+ 'default' => 'enabled',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_OPENSSL_KEY_V1',
+ 'default' => 'your-secret-key',
+ 'required' => true,
+ 'question' => 'Choose a secret API key, make sure to make a backup of your key in a secure location',
+ ],
+ [
+ 'name' => '_APP_DOMAIN',
+ 'default' => 'localhost',
+ 'required' => true,
+ 'question' => 'Enter your Appwrite hostname',
+ ],
+ [
+ 'name' => '_APP_DOMAIN_TARGET',
+ 'default' => 'localhost',
+ 'required' => true,
+ 'question' => "Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.",
+ ],
+ [
+ 'name' => '_APP_REDIS_HOST',
+ 'default' => 'redis',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_REDIS_PORT',
+ 'default' => '6379',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_HOST',
+ 'default' => 'mariadb',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_PORT',
+ 'default' => '3306',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_SCHEMA',
+ 'default' => 'appwrite',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_USER',
+ 'default' => 'user',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_DB_PASS',
+ 'default' => 'password',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_INFLUXDB_HOST',
+ 'default' => 'influxdb',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_INFLUXDB_PORT',
+ 'default' => '8086',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STATSD_HOST',
+ 'default' => 'telegraf',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STATSD_PORT',
+ 'default' => '8125',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_HOST',
+ 'default' => 'smtp',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_SMTP_PORT',
+ 'default' => '25',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_STORAGE_LIMIT',
+ 'default' => '100000000',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_TIMEOUT',
+ 'default' => '900',
+ 'required' => false,
+ 'question' => '',
+ ],
+ [
+ 'name' => '_APP_FUNCTIONS_CONTAINERS',
+ 'default' => '10',
+ 'required' => false,
+ 'question' => '',
+ ],
+];
\ No newline at end of file
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 8ba5784412..f80c09c6b9 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -1,10 +1,7 @@
init(function() use (&$oauth2Keys) {
+App::init(function() use (&$oauth2Keys) {
foreach (Config::getParam('providers') as $key => $provider) {
if (!$provider['enabled']) {
continue;
@@ -43,9 +39,9 @@ $utopia->init(function() use (&$oauth2Keys) {
$oauth2Keys[] = 'oauth2'.\ucfirst($key);
$oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
-}, 'account');
+}, [], 'account');
-$utopia->post('/v1/account')
+App::post('/v1/account')
->desc('Create Account')
->groups(['api', 'account'])
->label('webhook', 'account.create')
@@ -55,97 +51,102 @@ $utopia->post('/v1/account')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/account/create.md')
->label('abuse-limit', 10)
- ->param('email', '', function () { return new Email(); }, 'User email.')
- ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
- ->param('name', '', function () { return new Text(100); }, 'User name.', true)
- ->action(
- function ($email, $password, $name) use ($request, $response, $audit, $projectDB, $project, $webhook, $oauth2Keys) {
- if ('console' === $project->getId()) {
- $whitlistEmails = $project->getAttribute('authWhitelistEmails');
- $whitlistIPs = $project->getAttribute('authWhitelistIPs');
- $whitlistDomains = $project->getAttribute('authWhitelistDomains');
+ ->param('email', '', new Email(), 'User email.')
+ ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
+ ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
+ ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) use ($oauth2Keys) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) {
- throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401);
- }
+ if ('console' === $project->getId()) {
+ $whitlistEmails = $project->getAttribute('authWhitelistEmails');
+ $whitlistIPs = $project->getAttribute('authWhitelistIPs');
+ $whitlistDomains = $project->getAttribute('authWhitelistDomains');
- if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) {
- throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401);
- }
-
- if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) {
- throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401);
- }
+ if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) {
+ throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401);
}
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
-
- if (!empty($profile)) {
- throw new Exception('Account already exists', 409);
+ if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) {
+ throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401);
}
- Authorization::disable();
-
- try {
- $user = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_USERS,
- '$permissions' => [
- 'read' => ['*'],
- 'write' => ['user:{self}'],
- ],
- 'email' => $email,
- 'emailVerification' => false,
- 'status' => Auth::USER_STATUS_UNACTIVATED,
- 'password' => Auth::passwordHash($password),
- 'password-update' => \time(),
- 'registration' => \time(),
- 'reset' => false,
- 'name' => $name,
- ], ['email' => $email]);
- } catch (Duplicate $th) {
- throw new Exception('Account already exists', 409);
+ if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) {
+ throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401);
}
-
- Authorization::enable();
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $webhook
- ->setParam('payload', [
- 'name' => $name,
- 'email' => $email,
- ])
- ;
-
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.create')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json(\array_merge($user->getArrayCopy(\array_merge(
- [
- '$id',
- 'email',
- 'registration',
- 'name',
- ],
- $oauth2Keys
- )), ['roles' => Authorization::getRoles()]));
}
- );
-$utopia->post('/v1/account/sessions')
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
+
+ if (!empty($profile)) {
+ throw new Exception('Account already exists', 409);
+ }
+
+ Authorization::disable();
+
+ try {
+ $user = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_USERS,
+ '$permissions' => [
+ 'read' => ['*'],
+ 'write' => ['user:{self}'],
+ ],
+ 'email' => $email,
+ 'emailVerification' => false,
+ 'status' => Auth::USER_STATUS_UNACTIVATED,
+ 'password' => Auth::passwordHash($password),
+ 'password-update' => \time(),
+ 'registration' => \time(),
+ 'reset' => false,
+ 'name' => $name,
+ ], ['email' => $email]);
+ } catch (Duplicate $th) {
+ throw new Exception('Account already exists', 409);
+ }
+
+ Authorization::enable();
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $webhooks
+ ->setParam('payload', [
+ 'name' => $name,
+ 'email' => $email,
+ ])
+ ;
+
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.create')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'email',
+ 'registration',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => Authorization::getRoles()]));
+ }, ['request', 'response', 'project', 'projectDB', 'webhooks', 'audits']);
+
+App::post('/v1/account/sessions')
->desc('Create Account Session')
->groups(['api', 'account'])
->label('webhook', 'account.sessions.create')
@@ -156,86 +157,92 @@ $utopia->post('/v1/account/sessions')
->label('sdk.description', '/docs/references/account/create-session.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
- ->param('email', '', function () { return new Email(); }, 'User email.')
- ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
- ->action(
- function ($email, $password) use ($response, $request, $projectDB, $audit, $webhook) {
- $protocol = Config::getParam('protocol');
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
+ ->param('email', '', new Email(), 'User email.')
+ ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
+ ->action(function ($email, $password, $request, $response, $projectDB, $webhooks, $audits) {
+ /** @var Appwrite\Swoole\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (false == $profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
- $audit
- //->setParam('userId', $profile->getId())
- ->setParam('event', 'account.sesssions.failed')
- ->setParam('resource', 'users/'.($profile ? $profile->getId() : ''))
- ;
+ $protocol = $request->getProtocol();
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
- throw new Exception('Invalid credentials', 401); // Wrong password or username
- }
-
- $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
- $secret = Auth::tokenGenerator();
- $session = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
- '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
- 'type' => Auth::TOKEN_TYPE_LOGIN,
- 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
- 'expire' => $expiry,
- 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
- 'ip' => $request->getIP(),
- ]);
-
- Authorization::setRole('user:'.$profile->getId());
-
- $session = $projectDB->createDocument($session->getArrayCopy());
-
- if (false === $session) {
- throw new Exception('Failed saving session to DB', 500);
- }
-
- $profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND);
-
- $profile = $projectDB->updateDocument($profile->getArrayCopy());
-
- if (false === $profile) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $webhook
- ->setParam('payload', [
- 'name' => $profile->getAttribute('name', ''),
- 'email' => $profile->getAttribute('email', ''),
- ])
+ if (false == $profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
+ $audits
+ //->setParam('userId', $profile->getId())
+ ->setParam('event', 'account.sesssions.failed')
+ ->setParam('resource', 'users/'.($profile ? $profile->getId() : ''))
;
- $audit
- ->setParam('userId', $profile->getId())
- ->setParam('event', 'account.sessions.create')
- ->setParam('resource', 'users/'.$profile->getId())
- ;
+ throw new Exception('Invalid credentials', 401); // Wrong password or username
+ }
- if (!Config::getParam('domainVerification')) {
- $response
- ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
- ;
- }
-
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $secret = Auth::tokenGenerator();
+ $session = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
+ '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
+ 'type' => Auth::TOKEN_TYPE_LOGIN,
+ 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
+ 'expire' => $expiry,
+ 'userAgent' => $request->getUserAgent('UNKNOWN'),
+ 'ip' => $request->getIP(),
+ ]);
+
+ Authorization::setRole('user:'.$profile->getId());
+
+ $session = $projectDB->createDocument($session->getArrayCopy());
+
+ if (false === $session) {
+ throw new Exception('Failed saving session to DB', 500);
+ }
+
+ $profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND);
+
+ $profile = $projectDB->updateDocument($profile->getArrayCopy());
+
+ if (false === $profile) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $webhooks
+ ->setParam('payload', [
+ 'name' => $profile->getAttribute('name', ''),
+ 'email' => $profile->getAttribute('email', ''),
+ ])
+ ;
+
+ $audits
+ ->setParam('userId', $profile->getId())
+ ->setParam('event', 'account.sessions.create')
+ ->setParam('resource', 'users/'.$profile->getId())
+ ;
+
+ if (!Config::getParam('domainVerification')) {
$response
- ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($session->getArrayCopy(['$id', 'type', 'expire']))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)]))
;
}
- );
+
+ $response
+ ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ;
+
+ $response->dynamic($session, Response::MODEL_SESSION);
+ ;
+ }, ['request', 'response', 'projectDB', 'webhooks', 'audits']);
-$utopia->get('/v1/account/sessions/oauth2/:provider')
+App::get('/v1/account/sessions/oauth2/:provider')
->desc('Create Account Session with OAuth2')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
@@ -249,91 +256,95 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
->label('sdk.methodType', 'webAuth')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
- ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
- ->param('success', $oauthDefaultSuccess, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt. 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)
- ->param('failure', $oauthDefaultFailure, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt. 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)
- ->param('scopes', [], function () { return new ArrayList(new Text(128)); }, 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.', true)
- ->action(
- function ($provider, $success, $failure, $scopes) use ($response, $request, $project) {
- $protocol = Config::getParam('protocol');
- $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
- $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
- $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
+ ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
+ ->param('success', $oauthDefaultSuccess, function ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt. 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'])
+ ->param('failure', $oauthDefaultFailure, function ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt. 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'])
+ ->param('scopes', [], new ArrayList(new Text(128)), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.', true)
+ ->action(function ($provider, $success, $failure, $scopes, $request, $response, $project) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
- $appSecret = \json_decode($appSecret, true);
+ $protocol = $request->getProtocol();
+ $callback = $protocol.'://'.$request->getHostname().'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
+ $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
+ $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
- if (!empty($appSecret) && isset($appSecret['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
- $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
- }
+ $appSecret = \json_decode($appSecret, true);
- if (empty($appId) || empty($appSecret)) {
- throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your '.APP_NAME.' console to continue.', 412);
- }
-
- $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
-
- if (!\class_exists($classname)) {
- throw new Exception('Provider is not supported', 501);
- }
-
- $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes);
-
- $response
- ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
- ->addHeader('Pragma', 'no-cache')
- ->redirect($oauth2->getLoginURL());
+ if (!empty($appSecret) && isset($appSecret['version'])) {
+ $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']);
+ $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
- );
-$utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
+ if (empty($appId) || empty($appSecret)) {
+ throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your '.APP_NAME.' console to continue.', 412);
+ }
+
+ $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
+
+ if (!\class_exists($classname)) {
+ throw new Exception('Provider is not supported', 501);
+ }
+
+ $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes);
+
+ $response
+ ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ ->addHeader('Pragma', 'no-cache')
+ ->redirect($oauth2->getLoginURL());
+ }, ['request', 'response', 'project']);
+
+App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('OAuth2 Callback')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
->label('scope', 'public')
->label('docs', false)
- ->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
- ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
- ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true)
- ->action(
- function ($projectId, $provider, $code, $state) use ($response) {
- $domain = Config::getParam('domain');
- $protocol = Config::getParam('protocol');
-
- $response
- ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
- ->addHeader('Pragma', 'no-cache')
- ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
- .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
- }
- );
+ ->param('projectId', '', new Text(1024), 'Project unique ID.')
+ ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
+ ->param('code', '', new Text(1024), 'OAuth2 code.')
+ ->param('state', '', new Text(2048), 'Login state params.', true)
+ ->action(function ($projectId, $provider, $code, $state, $request, $response) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
-$utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
+ $domain = $request->getHostname();
+ $protocol = $request->getProtocol();
+
+ $response
+ ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ ->addHeader('Pragma', 'no-cache')
+ ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
+ .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
+ }, ['request', 'response']);
+
+App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('OAuth2 Callback')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
->label('scope', 'public')
->label('origin', '*')
->label('docs', false)
- ->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
- ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
- ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true)
- ->action(
- function ($projectId, $provider, $code, $state) use ($response) {
- $domain = Config::getParam('domain');
- $protocol = Config::getParam('protocol');
-
- $response
- ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
- ->addHeader('Pragma', 'no-cache')
- ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
- .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
- }
- );
+ ->param('projectId', '', new Text(1024), 'Project unique ID.')
+ ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
+ ->param('code', '', new Text(1024), 'OAuth2 code.')
+ ->param('state', '', new Text(2048), 'Login state params.', true)
+ ->action(function ($projectId, $provider, $code, $state, $request, $response) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
+ $domain = $request->getHostname();
+ $protocol = $request->getProtocol();
+
+ $response
+ ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ ->addHeader('Pragma', 'no-cache')
+ ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
+ .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
+ }, ['request', 'response']);
+
+App::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 Redirect')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
@@ -342,192 +353,197 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->label('docs', false)
- ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.')
- ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
- ->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true)
- ->action(
- function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit, $oauthDefaultSuccess) {
- $protocol = Config::getParam('protocol');
- $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
- $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
- $validateURL = new URL();
+ ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
+ ->param('code', '', new Text(1024), 'OAuth2 code.')
+ ->param('state', '', new Text(2048), 'OAuth2 state params.', true)
+ ->action(function ($provider, $code, $state, $request, $response, $project, $user, $projectDB, $audits) use ($oauthDefaultSuccess) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+
+ $protocol = $request->getProtocol();
+ $callback = $protocol.'://'.$request->getHostname().'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
+ $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
+ $validateURL = new URL();
- $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
- $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
+ $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', '');
+ $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}');
- $appSecret = \json_decode($appSecret, true);
+ $appSecret = \json_decode($appSecret, true);
- if (!empty($appSecret) && isset($appSecret['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
- $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
+ if (!empty($appSecret) && isset($appSecret['version'])) {
+ $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']);
+ $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
+ }
+
+ $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
+
+ if (!\class_exists($classname)) {
+ throw new Exception('Provider is not supported', 501);
+ }
+
+ $oauth2 = new $classname($appId, $appSecret, $callback);
+
+ if (!empty($state)) {
+ try {
+ $state = \array_merge($defaultState, $oauth2->parseState($state));
+ } catch (\Exception $exception) {
+ throw new Exception('Failed to parse login state params as passed from OAuth2 provider');
+ }
+ } else {
+ $state = $defaultState;
+ }
+
+ if (!$validateURL->isValid($state['success'])) {
+ throw new Exception('Invalid redirect URL for success login', 400);
+ }
+
+ if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
+ throw new Exception('Invalid redirect URL for failure login', 400);
+ }
+
+ $state['failure'] = null;
+ $accessToken = $oauth2->getAccessToken($code);
+
+ if (empty($accessToken)) {
+ if (!empty($state['failure'])) {
+ $response->redirect($state['failure'], 301, 0);
}
- $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider);
+ throw new Exception('Failed to obtain access token');
+ }
- if (!\class_exists($classname)) {
- throw new Exception('Provider is not supported', 501);
+ $oauth2ID = $oauth2->getUserID($accessToken);
+
+ if (empty($oauth2ID)) {
+ if (!empty($state['failure'])) {
+ $response->redirect($state['failure'], 301, 0);
}
- $oauth2 = new $classname($appId, $appSecret, $callback);
+ throw new Exception('Missing ID from OAuth2 provider', 400);
+ }
- if (!empty($state)) {
- try {
- $state = \array_merge($defaultState, $oauth2->parseState($state));
- } catch (\Exception $exception) {
- throw new Exception('Failed to parse login state params as passed from OAuth2 provider');
- }
- } else {
- $state = $defaultState;
- }
+ $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
- if (!$validateURL->isValid($state['success'])) {
- throw new Exception('Invalid redirect URL for success login', 400);
- }
+ if ($current) {
+ $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401);
+ }
- if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
- throw new Exception('Invalid redirect URL for failure login', 400);
- }
-
- $state['failure'] = null;
- $accessToken = $oauth2->getAccessToken($code);
+ $user = (empty($user->getId())) ? $projectDB->getCollectionFirst([ // Get user by provider id
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'oauth2'.\ucfirst($provider).'='.$oauth2ID,
+ ],
+ ]) : $user;
- if (empty($accessToken)) {
- if (!empty($state['failure'])) {
- $response->redirect($state['failure'], 301, 0);
- }
+ if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
+ $name = $oauth2->getUserName($accessToken);
+ $email = $oauth2->getUserEmail($accessToken);
- throw new Exception('Failed to obtain access token');
- }
-
- $oauth2ID = $oauth2->getUserID($accessToken);
-
- if (empty($oauth2ID)) {
- if (!empty($state['failure'])) {
- $response->redirect($state['failure'], 301, 0);
- }
-
- throw new Exception('Missing ID from OAuth2 provider', 400);
- }
-
- $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
-
- if ($current) {
- $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401);
- }
-
- $user = (empty($user->getId())) ? $projectDB->getCollectionFirst([ // Get user by provider id
+ $user = $projectDB->getCollectionFirst([ // Get user by provider email address
'limit' => 1,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'oauth2'.\ucfirst($provider).'='.$oauth2ID,
+ 'email='.$email,
],
- ]) : $user;
-
- if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
- $name = $oauth2->getUserName($accessToken);
- $email = $oauth2->getUserEmail($accessToken);
-
- $user = $projectDB->getCollectionFirst([ // Get user by provider email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
-
- if (!$user || empty($user->getId())) { // Last option -> create user alone, generate random password
- Authorization::disable();
-
- try {
- $user = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_USERS,
- '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']],
- 'email' => $email,
- 'emailVerification' => true,
- 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider
- 'password' => Auth::passwordHash(Auth::passwordGenerator()),
- 'password-update' => \time(),
- 'registration' => \time(),
- 'reset' => false,
- 'name' => $name,
- ], ['email' => $email]);
- } catch (Duplicate $th) {
- throw new Exception('Account already exists', 409);
- }
-
- Authorization::enable();
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
- }
- }
-
- // Create session token, verify user account and update OAuth2 ID and Access Token
-
- $secret = Auth::tokenGenerator();
- $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
- $session = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
- '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]],
- 'type' => Auth::TOKEN_TYPE_LOGIN,
- 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
- 'expire' => $expiry,
- 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
- 'ip' => $request->getIP(),
]);
- $user
- ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID)
- ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken)
- ->setAttribute('status', Auth::USER_STATUS_ACTIVATED)
- ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND)
- ;
+ if (!$user || empty($user->getId())) { // Last option -> create user alone, generate random password
+ Authorization::disable();
- Authorization::setRole('user:'.$user->getId());
+ try {
+ $user = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_USERS,
+ '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']],
+ 'email' => $email,
+ 'emailVerification' => true,
+ 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider
+ 'password' => Auth::passwordHash(Auth::passwordGenerator()),
+ 'password-update' => \time(),
+ 'registration' => \time(),
+ 'reset' => false,
+ 'name' => $name,
+ ], ['email' => $email]);
+ } catch (Duplicate $th) {
+ throw new Exception('Account already exists', 409);
+ }
- $user = $projectDB->updateDocument($user->getArrayCopy());
+ Authorization::enable();
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
}
+ }
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.sessions.create')
- ->setParam('resource', 'users/'.$user->getId())
- ->setParam('data', ['provider' => $provider])
- ;
+ // Create session token, verify user account and update OAuth2 ID and Access Token
- if (!Config::getParam('domainVerification')) {
- $response
- ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
- ;
- }
-
- // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing
- if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) {
- $state['success'] = URLParser::parse($state['success']);
- $query = URLParser::parseQuery($state['success']['query']);
- $query['project'] = $project->getId();
- $query['domain'] = COOKIE_DOMAIN;
- $query['key'] = Auth::$cookieName;
- $query['secret'] = Auth::encodeSession($user->getId(), $secret);
- $state['success']['query'] = URLParser::unparseQuery($query);
- $state['success'] = URLParser::unparse($state['success']);
- }
+ $secret = Auth::tokenGenerator();
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $session = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
+ '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]],
+ 'type' => Auth::TOKEN_TYPE_LOGIN,
+ 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
+ 'expire' => $expiry,
+ 'userAgent' => $request->getUserAgent('UNKNOWN'),
+ 'ip' => $request->getIP(),
+ ]);
+ $user
+ ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID)
+ ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken)
+ ->setAttribute('status', Auth::USER_STATUS_ACTIVATED)
+ ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND)
+ ;
+
+ Authorization::setRole('user:'.$user->getId());
+
+ $user = $projectDB->updateDocument($user->getArrayCopy());
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.sessions.create')
+ ->setParam('resource', 'users/'.$user->getId())
+ ->setParam('data', ['provider' => $provider])
+ ;
+
+ if (!Config::getParam('domainVerification')) {
$response
- ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
- ->addHeader('Pragma', 'no-cache')
- ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ->redirect($state['success'])
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
- );
+
+ // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing
+ if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) {
+ $state['success'] = URLParser::parse($state['success']);
+ $query = URLParser::parseQuery($state['success']['query']);
+ $query['project'] = $project->getId();
+ $query['domain'] = Config::getParam('cookieDomain');
+ $query['key'] = Auth::$cookieName;
+ $query['secret'] = Auth::encodeSession($user->getId(), $secret);
+ $state['success']['query'] = URLParser::unparseQuery($query);
+ $state['success'] = URLParser::unparse($state['success']);
+ }
-$utopia->get('/v1/account')
+ $response
+ ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
+ ->addHeader('Pragma', 'no-cache')
+ ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->redirect($state['success'])
+ ;
+ }, ['request', 'response', 'project', 'user', 'projectDB', 'audits']);
+
+App::get('/v1/account')
->desc('Get Account')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -536,22 +552,23 @@ $utopia->get('/v1/account')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/account/get.md')
->label('sdk.response', ['200' => 'user'])
- ->action(
- function () use ($response, &$user, $oauth2Keys) {
- $response->json(\array_merge($user->getArrayCopy(\array_merge(
- [
- '$id',
- 'email',
- 'emailVerification',
- 'registration',
- 'name',
- ],
- $oauth2Keys
- )), ['roles' => Authorization::getRoles()]));
- }
- );
+ ->action(function ($response, $user) use ($oauth2Keys) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
-$utopia->get('/v1/account/prefs')
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'email',
+ 'emailVerification',
+ 'registration',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => Authorization::getRoles()]));
+ }, ['response', 'user']);
+
+App::get('/v1/account/prefs')
->desc('Get Account Preferences')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -559,22 +576,23 @@ $utopia->get('/v1/account/prefs')
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/account/get-prefs.md')
- ->action(
- function () use ($response, $user) {
- $prefs = $user->getAttribute('prefs', '{}');
+ ->action(function ($response, $user) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
- try {
- $prefs = \json_decode($prefs, true);
- $prefs = ($prefs) ? $prefs : [];
- } catch (\Exception $error) {
- throw new Exception('Failed to parse prefs', 500);
- }
+ $prefs = $user->getAttribute('prefs', '{}');
- $response->json($prefs);
+ try {
+ $prefs = \json_decode($prefs, true);
+ $prefs = ($prefs) ? $prefs : [];
+ } catch (\Exception $error) {
+ throw new Exception('Failed to parse prefs', 500);
}
- );
-$utopia->get('/v1/account/sessions')
+ $response->json($prefs);
+ }, ['response', 'user']);
+
+App::get('/v1/account/sessions')
->desc('Get Account Sessions')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -582,58 +600,60 @@ $utopia->get('/v1/account/sessions')
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/account/get-sessions.md')
- ->action(
- function () use ($response, $user) {
- $tokens = $user->getAttribute('tokens', []);
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $sessions = [];
- $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
- $index = 0;
- $countries = Locale::getText('countries');
+ ->action(function ($response, $user, $locale, $geodb) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var GeoIp2\Database\Reader $geodb */
- foreach ($tokens as $token) { /* @var $token Document */
- if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
- continue;
- }
+ $tokens = $user->getAttribute('tokens', []);
+ $sessions = [];
+ $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
+ $index = 0;
+ $countries = $locale->getText('countries');
- $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
-
- $dd = new DeviceDetector($userAgent);
-
- // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
- // $dd->skipBotDetection();
-
- $dd->parse();
-
- $sessions[$index] = [
- '$id' => $token->getId(),
- 'OS' => $dd->getOs(),
- 'client' => $dd->getClient(),
- 'device' => $dd->getDevice(),
- 'brand' => $dd->getBrand(),
- 'model' => $dd->getModel(),
- 'ip' => $token->getAttribute('ip', ''),
- 'geo' => [],
- 'current' => ($current == $token->getId()) ? true : false,
- ];
-
- try {
- $record = $reader->country($token->getAttribute('ip', ''));
- $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
- $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- } catch (\Exception $e) {
- $sessions[$index]['geo']['isoCode'] = '--';
- $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
- }
-
- ++$index;
+ foreach ($tokens as $token) { /* @var $token Document */
+ if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
+ continue;
}
- $response->json($sessions);
- }
- );
+ $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
-$utopia->get('/v1/account/logs')
+ $dd = new DeviceDetector($userAgent);
+
+ // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
+ // $dd->skipBotDetection();
+
+ $dd->parse();
+
+ $sessions[$index] = [
+ '$id' => $token->getId(),
+ 'OS' => $dd->getOs(),
+ 'client' => $dd->getClient(),
+ 'device' => $dd->getDevice(),
+ 'brand' => $dd->getBrand(),
+ 'model' => $dd->getModel(),
+ 'ip' => $token->getAttribute('ip', ''),
+ 'geo' => [],
+ 'current' => ($current == $token->getId()) ? true : false,
+ ];
+
+ try {
+ $record = $geodb->country($token->getAttribute('ip', ''));
+ $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
+ $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown');
+ } catch (\Exception $e) {
+ $sessions[$index]['geo']['isoCode'] = '--';
+ $sessions[$index]['geo']['country'] = $locale->getText('locale.country.unknown');
+ }
+
+ ++$index;
+ }
+
+ $response->json($sessions);
+ }, ['response', 'user', 'locale', 'geodb']);
+
+App::get('/v1/account/logs')
->desc('Get Account Logs')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -641,72 +661,75 @@ $utopia->get('/v1/account/logs')
->label('sdk.namespace', 'account')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/account/get-logs.md')
- ->action(
- function () use ($response, $register, $project, $user) {
- $adapter = new AuditAdapter($register->get('db'));
- $adapter->setNamespace('app_'.$project->getId());
+ ->action(function ($response, $register, $project, $user, $locale, $geodb) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var GeoIp2\Database\Reader $geodb */
- $audit = new Audit($adapter);
- $countries = Locale::getText('countries');
+ $adapter = new AuditAdapter($register->get('db'));
+ $adapter->setNamespace('app_'.$project->getId());
- $logs = $audit->getLogsByUserAndActions($user->getId(), [
- 'account.create',
- 'account.delete',
- 'account.update.name',
- 'account.update.email',
- 'account.update.password',
- 'account.update.prefs',
- 'account.sessions.create',
- 'account.sessions.delete',
- 'account.recovery.create',
- 'account.recovery.update',
- 'account.verification.create',
- 'account.verification.update',
- 'teams.membership.create',
- 'teams.membership.update',
- 'teams.membership.delete',
- ]);
+ $audit = new Audit($adapter);
+ $countries = $locale->getText('countries');
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $output = [];
+ $logs = $audit->getLogsByUserAndActions($user->getId(), [
+ 'account.create',
+ 'account.delete',
+ 'account.update.name',
+ 'account.update.email',
+ 'account.update.password',
+ 'account.update.prefs',
+ 'account.sessions.create',
+ 'account.sessions.delete',
+ 'account.recovery.create',
+ 'account.recovery.update',
+ 'account.verification.create',
+ 'account.verification.update',
+ 'teams.membership.create',
+ 'teams.membership.update',
+ 'teams.membership.delete',
+ ]);
- foreach ($logs as $i => &$log) {
- $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
+ $output = [];
- $dd = new DeviceDetector($log['userAgent']);
+ foreach ($logs as $i => &$log) {
+ $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
- $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
+ $dd = new DeviceDetector($log['userAgent']);
- $dd->parse();
+ $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
- $output[$i] = [
- 'event' => $log['event'],
- 'ip' => $log['ip'],
- 'time' => \strtotime($log['time']),
- 'OS' => $dd->getOs(),
- 'client' => $dd->getClient(),
- 'device' => $dd->getDevice(),
- 'brand' => $dd->getBrand(),
- 'model' => $dd->getModel(),
- 'geo' => [],
- ];
+ $dd->parse();
- try {
- $record = $reader->country($log['ip']);
- $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
- $output[$i]['geo']['country'] = $record->country->name;
- $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- } catch (\Exception $e) {
- $output[$i]['geo']['isoCode'] = '--';
- $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
- }
+ $output[$i] = [
+ 'event' => $log['event'],
+ 'ip' => $log['ip'],
+ 'time' => \strtotime($log['time']),
+ 'OS' => $dd->getOs(),
+ 'client' => $dd->getClient(),
+ 'device' => $dd->getDevice(),
+ 'brand' => $dd->getBrand(),
+ 'model' => $dd->getModel(),
+ 'geo' => [],
+ ];
+
+ try {
+ $record = $geodb->country($log['ip']);
+ $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
+ $output[$i]['geo']['country'] = $record->country->name;
+ $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown');
+ } catch (\Exception $e) {
+ $output[$i]['geo']['isoCode'] = '--';
+ $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown');
}
-
- $response->json($output);
}
- );
-$utopia->patch('/v1/account/name')
+ $response->json($output);
+ }, ['response', 'register', 'project', 'user', 'locale', 'geodb']);
+
+App::patch('/v1/account/name')
->desc('Update Account Name')
->groups(['api', 'account'])
->label('webhook', 'account.update.name')
@@ -715,36 +738,39 @@ $utopia->patch('/v1/account/name')
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateName')
->label('sdk.description', '/docs/references/account/update-name.md')
- ->param('name', '', function () { return new Text(100); }, 'User name.')
- ->action(
- function ($name) use ($response, $user, $projectDB, $audit, $oauth2Keys) {
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'name' => $name,
- ]));
+ ->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
+ ->action(function ($name, $response, $user, $projectDB, $audits) use ($oauth2Keys) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'name' => $name,
+ ]));
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.update.name')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $response->json(\array_merge($user->getArrayCopy(\array_merge(
- [
- '$id',
- 'email',
- 'registration',
- 'name',
- ],
- $oauth2Keys
- )), ['roles' => Authorization::getRoles()]));
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
}
- );
-$utopia->patch('/v1/account/password')
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.update.name')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'email',
+ 'registration',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => Authorization::getRoles()]));
+ }, ['response', 'user', 'projectDB', 'audits']);
+
+App::patch('/v1/account/password')
->desc('Update Account Password')
->groups(['api', 'account'])
->label('webhook', 'account.update.password')
@@ -753,41 +779,44 @@ $utopia->patch('/v1/account/password')
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePassword')
->label('sdk.description', '/docs/references/account/update-password.md')
- ->param('password', '', function () { return new Password(); }, 'New user password. Must be between 6 to 32 chars.')
- ->param('oldPassword', '', function () { return new Password(); }, 'Old user password. Must be between 6 to 32 chars.')
- ->action(
- function ($password, $oldPassword) use ($response, $user, $projectDB, $audit, $oauth2Keys) {
- if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
- throw new Exception('Invalid credentials', 401);
- }
+ ->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.')
+ ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.')
+ ->action(function ($password, $oldPassword, $response, $user, $projectDB, $audits) use ($oauth2Keys) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'password' => Auth::passwordHash($password),
- ]));
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.update.password')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $response->json(\array_merge($user->getArrayCopy(\array_merge(
- [
- '$id',
- 'email',
- 'registration',
- 'name',
- ],
- $oauth2Keys
- )), ['roles' => Authorization::getRoles()]));
+ if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
+ throw new Exception('Invalid credentials', 401);
}
- );
-$utopia->patch('/v1/account/email')
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'password' => Auth::passwordHash($password),
+ ]));
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.update.password')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'email',
+ 'registration',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => Authorization::getRoles()]));
+ }, ['response', 'user', 'projectDB', 'audits']);
+
+App::patch('/v1/account/email')
->desc('Update Account Email')
->groups(['api', 'account'])
->label('webhook', 'account.update.email')
@@ -796,56 +825,59 @@ $utopia->patch('/v1/account/email')
->label('sdk.namespace', 'account')
->label('sdk.method', 'updateEmail')
->label('sdk.description', '/docs/references/account/update-email.md')
- ->param('email', '', function () { return new Email(); }, 'User email.')
- ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
- ->action(
- function ($email, $password) use ($response, $user, $projectDB, $audit, $oauth2Keys) {
- if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
- throw new Exception('Invalid credentials', 401);
- }
+ ->param('email', '', new Email(), 'User email.')
+ ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
+ ->action(function ($email, $password, $response, $user, $projectDB, $audits) use ($oauth2Keys) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
-
- if (!empty($profile)) {
- throw new Exception('User already registered', 400);
- }
-
- // TODO after this user needs to confirm mail again
-
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'email' => $email,
- 'emailVerification' => false,
- ]));
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.update.email')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $response->json(\array_merge($user->getArrayCopy(\array_merge(
- [
- '$id',
- 'email',
- 'registration',
- 'name',
- ],
- $oauth2Keys
- )), ['roles' => Authorization::getRoles()]));
+ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
+ throw new Exception('Invalid credentials', 401);
}
- );
-$utopia->patch('/v1/account/prefs')
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
+
+ if (!empty($profile)) {
+ throw new Exception('User already registered', 400);
+ }
+
+ // TODO after this user needs to confirm mail again
+
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'email' => $email,
+ 'emailVerification' => false,
+ ]));
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.update.email')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'email',
+ 'registration',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => Authorization::getRoles()]));
+ }, ['response', 'user', 'projectDB', 'audits']);
+
+App::patch('/v1/account/prefs')
->desc('Update Account Preferences')
->groups(['api', 'account'])
->label('webhook', 'account.update.prefs')
@@ -853,40 +885,43 @@ $utopia->patch('/v1/account/prefs')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'updatePrefs')
- ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.')
+ ->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->label('sdk.description', '/docs/references/account/update-prefs.md')
- ->action(
- function ($prefs) use ($response, $user, $projectDB, $audit) {
- $old = \json_decode($user->getAttribute('prefs', '{}'), true);
- $old = ($old) ? $old : [];
+ ->action(function ($prefs, $response, $user, $projectDB, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'prefs' => \json_encode(\array_merge($old, $prefs)),
- ]));
+ $old = \json_decode($user->getAttribute('prefs', '{}'), true);
+ $old = ($old) ? $old : [];
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'prefs' => \json_encode(\array_merge($old, $prefs)),
+ ]));
- $audit
- ->setParam('event', 'account.update.prefs')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $prefs = $user->getAttribute('prefs', '{}');
-
- try {
- $prefs = \json_decode($prefs, true);
- $prefs = ($prefs) ? $prefs : [];
- } catch (\Exception $error) {
- throw new Exception('Failed to parse prefs', 500);
- }
-
- $response->json($prefs);
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
}
- );
-$utopia->delete('/v1/account')
+ $audits
+ ->setParam('event', 'account.update.prefs')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $prefs = $user->getAttribute('prefs', '{}');
+
+ try {
+ $prefs = \json_decode($prefs, true);
+ $prefs = ($prefs) ? $prefs : [];
+ } catch (\Exception $error) {
+ throw new Exception('Failed to parse prefs', 500);
+ }
+
+ $response->json($prefs);
+ }, ['response', 'user', 'projectDB', 'audits']);
+
+App::delete('/v1/account')
->desc('Delete Account')
->groups(['api', 'account'])
->label('webhook', 'account.delete')
@@ -895,54 +930,59 @@ $utopia->delete('/v1/account')
->label('sdk.namespace', 'account')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/account/delete.md')
- ->action(
- function () use ($response, $user, $projectDB, $audit, $webhook) {
- $protocol = Config::getParam('protocol');
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'status' => Auth::USER_STATUS_BLOCKED,
- ]));
+ ->action(function ($request, $response, $user, $projectDB, $audits, $webhooks) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $webhooks */
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
+ $protocol = $request->getProtocol();
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'status' => Auth::USER_STATUS_BLOCKED,
+ ]));
- //TODO delete all tokens or only current session?
- //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later
- /*
- * Data to delete
- * * Tokens
- * * Memberships
- */
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.delete')
- ->setParam('resource', 'users/'.$user->getId())
- ->setParam('data', $user->getArrayCopy())
- ;
+ //TODO delete all tokens or only current session?
+ //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later
+ /*
+ * Data to delete
+ * * Tokens
+ * * Memberships
+ */
- $webhook
- ->setParam('payload', [
- 'name' => $user->getAttribute('name', ''),
- 'email' => $user->getAttribute('email', ''),
- ])
- ;
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.delete')
+ ->setParam('resource', 'users/'.$user->getId())
+ ->setParam('data', $user->getArrayCopy())
+ ;
- if (!Config::getParam('domainVerification')) {
- $response
- ->addHeader('X-Fallback-Cookies', \json_encode([]))
- ;
- }
+ $webhooks
+ ->setParam('payload', [
+ 'name' => $user->getAttribute('name', ''),
+ 'email' => $user->getAttribute('email', ''),
+ ])
+ ;
+ if (!Config::getParam('domainVerification')) {
$response
- ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ->noContent()
+ ->addHeader('X-Fallback-Cookies', \json_encode([]))
;
}
- );
-$utopia->delete('/v1/account/sessions/:sessionId')
+ $response
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->noContent()
+ ;
+ }, ['request', 'response', 'user', 'projectDB', 'audits', 'webhooks']);
+
+App::delete('/v1/account/sessions/:sessionId')
->desc('Delete Account Session')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -952,83 +992,35 @@ $utopia->delete('/v1/account/sessions/:sessionId')
->label('sdk.method', 'deleteSession')
->label('sdk.description', '/docs/references/account/delete-session.md')
->label('abuse-limit', 100)
- ->param('sessionId', null, function () { return new UID(); }, 'Session unique ID. Use the string \'current\' to delete the current device session.')
- ->action(
- function ($sessionId) use ($response, $user, $projectDB, $webhook, $audit) {
- $protocol = Config::getParam('protocol');
- $sessionId = ($sessionId === 'current')
- ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)
- : $sessionId;
-
- $tokens = $user->getAttribute('tokens', []);
+ ->param('sessionId', null, new UID(), 'Session unique ID. Use the string \'current\' to delete the current device session.')
+ ->action(function ($sessionId, $request, $response, $user, $projectDB, $audits, $webhooks) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $webhooks */
- foreach ($tokens as $token) { /* @var $token Document */
- if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) {
- if (!$projectDB->deleteDocument($token->getId())) {
- throw new Exception('Failed to remove token from DB', 500);
- }
+ $protocol = $request->getProtocol();
+ $sessionId = ($sessionId === 'current')
+ ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)
+ : $sessionId;
+
+ $tokens = $user->getAttribute('tokens', []);
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.sessions.delete')
- ->setParam('resource', '/user/'.$user->getId())
- ;
-
- $webhook
- ->setParam('payload', [
- 'name' => $user->getAttribute('name', ''),
- 'email' => $user->getAttribute('email', ''),
- ])
- ;
-
- if (!Config::getParam('domainVerification')) {
- $response
- ->addHeader('X-Fallback-Cookies', \json_encode([]))
- ;
- }
-
- if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
- $response
- ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ;
- }
-
- return $response->noContent();
- }
- }
-
- throw new Exception('Session not found', 404);
- }
- );
-
-$utopia->delete('/v1/account/sessions')
- ->desc('Delete All Account Sessions')
- ->groups(['api', 'account'])
- ->label('scope', 'account')
- ->label('webhook', 'account.sessions.delete')
- ->label('sdk.platform', [APP_PLATFORM_CLIENT])
- ->label('sdk.namespace', 'account')
- ->label('sdk.method', 'deleteSessions')
- ->label('sdk.description', '/docs/references/account/delete-sessions.md')
- ->label('abuse-limit', 100)
- ->action(
- function () use ($response, $user, $projectDB, $audit, $webhook) {
- $protocol = Config::getParam('protocol');
- $tokens = $user->getAttribute('tokens', []);
-
- foreach ($tokens as $token) { /* @var $token Document */
+ foreach ($tokens as $token) { /* @var $token Document */
+ if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) {
if (!$projectDB->deleteDocument($token->getId())) {
throw new Exception('Failed to remove token from DB', 500);
}
- $audit
+ $audits
->setParam('userId', $user->getId())
->setParam('event', 'account.sessions.delete')
->setParam('resource', '/user/'.$user->getId())
;
- $webhook
+ $webhooks
->setParam('payload', [
'name' => $user->getAttribute('name', ''),
'email' => $user->getAttribute('email', ''),
@@ -1043,17 +1035,75 @@ $utopia->delete('/v1/account/sessions')
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$response
- ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
}
+
+ return $response->noContent();
+ }
+ }
+
+ throw new Exception('Session not found', 404);
+ }, ['request', 'response', 'user', 'projectDB', 'audits', 'webhooks']);
+
+App::delete('/v1/account/sessions')
+ ->desc('Delete All Account Sessions')
+ ->groups(['api', 'account'])
+ ->label('scope', 'account')
+ ->label('webhook', 'account.sessions.delete')
+ ->label('sdk.platform', [APP_PLATFORM_CLIENT])
+ ->label('sdk.namespace', 'account')
+ ->label('sdk.method', 'deleteSessions')
+ ->label('sdk.description', '/docs/references/account/delete-sessions.md')
+ ->label('abuse-limit', 100)
+ ->action(function ($request, $response, $user, $projectDB, $audits, $webhooks) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $webhooks */
+
+ $protocol = $request->getProtocol();
+ $tokens = $user->getAttribute('tokens', []);
+
+ foreach ($tokens as $token) { /* @var $token Document */
+ if (!$projectDB->deleteDocument($token->getId())) {
+ throw new Exception('Failed to remove token from DB', 500);
}
- $response->noContent();
- }
- );
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.sessions.delete')
+ ->setParam('resource', '/user/'.$user->getId())
+ ;
-$utopia->post('/v1/account/recovery')
+ $webhooks
+ ->setParam('payload', [
+ 'name' => $user->getAttribute('name', ''),
+ 'email' => $user->getAttribute('email', ''),
+ ])
+ ;
+
+ if (!Config::getParam('domainVerification')) {
+ $response
+ ->addHeader('X-Fallback-Cookies', \json_encode([]))
+ ;
+ }
+
+ if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
+ $response
+ ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ;
+ }
+ }
+
+ $response->noContent();
+ }, ['request', 'response', 'user', 'projectDB', 'audits', 'webhooks']);
+
+App::post('/v1/account/recovery')
->desc('Create Password Recovery')
->groups(['api', 'account'])
->label('scope', 'public')
@@ -1063,96 +1113,103 @@ $utopia->post('/v1/account/recovery')
->label('sdk.description', '/docs/references/account/create-recovery.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
- ->param('email', '', function () { return new Email(); }, 'User email.')
- ->param('url', '', function () use ($clients) { return 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.')
- ->action(
- function ($email, $url) use ($request, $response, $projectDB, $mail, $audit, $project) {
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
+ ->param('email', '', new Email(), 'User email.')
+ ->param('url', '', function ($clients) { return 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'])
+ ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mails, $audits) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var Appwrite\Event\Event $mails */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($profile)) {
- throw new Exception('User not found', 404); // TODO maybe hide this
- }
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
- $secret = Auth::tokenGenerator();
- $recovery = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
- '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
- 'type' => Auth::TOKEN_TYPE_RECOVERY,
- 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
- 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY,
- 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
- 'ip' => $request->getIP(),
- ]);
-
- Authorization::setRole('user:'.$profile->getId());
-
- $recovery = $projectDB->createDocument($recovery->getArrayCopy());
-
- if (false === $recovery) {
- throw new Exception('Failed saving recovery to DB', 500);
- }
-
- $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND);
-
- $profile = $projectDB->updateDocument($profile->getArrayCopy());
-
- if (false === $profile) {
- throw new Exception('Failed to save user to DB', 500);
- }
-
- $url = Template::parseURL($url);
- $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]);
- $url = Template::unParseURL($url);
-
- $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
- $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.recovery.body'));
- $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
-
- $body
- ->setParam('{{content}}', $content->render())
- ->setParam('{{cta}}', $cta->render())
- ->setParam('{{title}}', Locale::getText('account.emails.recovery.title'))
- ->setParam('{{direction}}', Locale::getText('settings.direction'))
- ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
- ->setParam('{{name}}', $profile->getAttribute('name'))
- ->setParam('{{redirect}}', $url)
- ->setParam('{{bg-body}}', '#f6f6f6')
- ->setParam('{{bg-content}}', '#ffffff')
- ->setParam('{{bg-cta}}', '#3498db')
- ->setParam('{{bg-cta-hover}}', '#34495e')
- ->setParam('{{text-content}}', '#000000')
- ->setParam('{{text-cta}}', '#ffffff')
- ;
-
- $mail
- ->setParam('event', 'account.recovery.create')
- ->setParam('recipient', $profile->getAttribute('email', ''))
- ->setParam('name', $profile->getAttribute('name', ''))
- ->setParam('subject', Locale::getText('account.emails.recovery.title'))
- ->setParam('body', $body->render())
- ->trigger();
- ;
-
- $audit
- ->setParam('userId', $profile->getId())
- ->setParam('event', 'account.recovery.create')
- ->setParam('resource', 'users/'.$profile->getId())
- ;
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($recovery->getArrayCopy(['$id', 'type', 'expire']))
- ;
+ if (empty($profile)) {
+ throw new Exception('User not found', 404); // TODO maybe hide this
}
- );
-$utopia->put('/v1/account/recovery')
+ $secret = Auth::tokenGenerator();
+ $recovery = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
+ '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]],
+ 'type' => Auth::TOKEN_TYPE_RECOVERY,
+ 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
+ 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY,
+ 'userAgent' => $request->getUserAgent('UNKNOWN'),
+ 'ip' => $request->getIP(),
+ ]);
+
+ Authorization::setRole('user:'.$profile->getId());
+
+ $recovery = $projectDB->createDocument($recovery->getArrayCopy());
+
+ if (false === $recovery) {
+ throw new Exception('Failed saving recovery to DB', 500);
+ }
+
+ $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND);
+
+ $profile = $projectDB->updateDocument($profile->getArrayCopy());
+
+ if (false === $profile) {
+ throw new Exception('Failed to save user to DB', 500);
+ }
+
+ $url = Template::parseURL($url);
+ $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]);
+ $url = Template::unParseURL($url);
+
+ $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl');
+ $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.recovery.body'));
+ $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl');
+
+ $body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', $locale->getText('account.emails.recovery.title'))
+ ->setParam('{{direction}}', $locale->getText('settings.direction'))
+ ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
+ ->setParam('{{name}}', $profile->getAttribute('name'))
+ ->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
+ ;
+
+ $mails
+ ->setParam('event', 'account.recovery.create')
+ ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name')))
+ ->setParam('recipient', $profile->getAttribute('email', ''))
+ ->setParam('name', $profile->getAttribute('name', ''))
+ ->setParam('subject', $locale->getText('account.emails.recovery.title'))
+ ->setParam('body', $body->render())
+ ->trigger();
+ ;
+
+ $audits
+ ->setParam('userId', $profile->getId())
+ ->setParam('event', 'account.recovery.create')
+ ->setParam('resource', 'users/'.$profile->getId())
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($recovery->getArrayCopy(['$id', 'type', 'expire']))
+ ;
+ }, ['request', 'response', 'projectDB', 'project', 'locale', 'mails', 'audits']);
+
+App::put('/v1/account/recovery')
->desc('Complete Password Recovery')
->groups(['api', 'account'])
->label('scope', 'public')
@@ -1162,67 +1219,69 @@ $utopia->put('/v1/account/recovery')
->label('sdk.description', '/docs/references/account/update-recovery.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}')
- ->param('userId', '', function () { return new UID(); }, 'User account UID address.')
- ->param('secret', '', function () { return new Text(256); }, 'Valid reset token.')
- ->param('password', '', function () { return new Password(); }, 'New password. Must be between 6 to 32 chars.')
- ->param('passwordAgain', '', function () {return new Password(); }, 'New password again. Must be between 6 to 32 chars.')
- ->action(
- function ($userId, $secret, $password, $passwordAgain) use ($response, $projectDB, $audit) {
- if ($password !== $passwordAgain) {
- throw new Exception('Passwords must match', 400);
- }
-
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- '$id='.$userId,
- ],
- ]);
-
- if (empty($profile)) {
- throw new Exception('User not found', 404); // TODO maybe hide this
- }
-
- $recovery = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $secret);
-
- if (!$recovery) {
- throw new Exception('Invalid recovery token', 401);
- }
-
- Authorization::setRole('user:'.$profile->getId());
-
- $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
- 'password' => Auth::passwordHash($password),
- 'password-update' => \time(),
- 'emailVerification' => true,
- ]));
-
- if (false === $profile) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- /**
- * We act like we're updating and validating
- * the recovery token but actually we don't need it anymore.
- */
- if (!$projectDB->deleteDocument($recovery)) {
- throw new Exception('Failed to remove recovery from DB', 500);
- }
-
- $audit
- ->setParam('userId', $profile->getId())
- ->setParam('event', 'account.recovery.update')
- ->setParam('resource', 'users/'.$profile->getId())
- ;
-
- $recovery = $profile->search('$id', $recovery, $profile->getAttribute('tokens', []));
-
- $response->json($recovery->getArrayCopy(['$id', 'type', 'expire']));
+ ->param('userId', '', new UID(), 'User account UID address.')
+ ->param('secret', '', new Text(256), 'Valid reset token.')
+ ->param('password', '', new Password(), 'New password. Must be between 6 to 32 chars.')
+ ->param('passwordAgain', '', new Password(), 'New password again. Must be between 6 to 32 chars.')
+ ->action(function ($userId, $secret, $password, $passwordAgain, $response, $projectDB, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+
+ if ($password !== $passwordAgain) {
+ throw new Exception('Passwords must match', 400);
}
- );
-$utopia->post('/v1/account/verification')
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ '$id='.$userId,
+ ],
+ ]);
+
+ if (empty($profile)) {
+ throw new Exception('User not found', 404); // TODO maybe hide this
+ }
+
+ $recovery = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $secret);
+
+ if (!$recovery) {
+ throw new Exception('Invalid recovery token', 401);
+ }
+
+ Authorization::setRole('user:'.$profile->getId());
+
+ $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
+ 'password' => Auth::passwordHash($password),
+ 'password-update' => \time(),
+ 'emailVerification' => true,
+ ]));
+
+ if (false === $profile) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ /**
+ * We act like we're updating and validating
+ * the recovery token but actually we don't need it anymore.
+ */
+ if (!$projectDB->deleteDocument($recovery)) {
+ throw new Exception('Failed to remove recovery from DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $profile->getId())
+ ->setParam('event', 'account.recovery.update')
+ ->setParam('resource', 'users/'.$profile->getId())
+ ;
+
+ $recovery = $profile->search('$id', $recovery, $profile->getAttribute('tokens', []));
+
+ $response->json($recovery->getArrayCopy(['$id', 'type', 'expire']));
+ }, ['response', 'projectDB', 'audits']);
+
+App::post('/v1/account/verification')
->desc('Create Email Verification')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -1232,84 +1291,92 @@ $utopia->post('/v1/account/verification')
->label('sdk.description', '/docs/references/account/create-verification.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
- ->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification 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.') // TODO add built-in confirm page
- ->action(
- function ($url) use ($request, $response, $mail, $user, $project, $projectDB, $audit) {
- $verificationSecret = Auth::tokenGenerator();
+ ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification 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']) // TODO add built-in confirm page
+ ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audits, $mails) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $mails */
+
+ $verificationSecret = Auth::tokenGenerator();
+
+ $verification = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
+ '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
+ 'type' => Auth::TOKEN_TYPE_VERIFICATION,
+ 'secret' => Auth::hash($verificationSecret), // On way hash encryption to protect DB leak
+ 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM,
+ 'userAgent' => $request->getUserAgent('UNKNOWN'),
+ 'ip' => $request->getIP(),
+ ]);
- $verification = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
- '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
- 'type' => Auth::TOKEN_TYPE_VERIFICATION,
- 'secret' => Auth::hash($verificationSecret), // On way hash encryption to protect DB leak
- 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM,
- 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
- 'ip' => $request->getIP(),
- ]);
-
- Authorization::setRole('user:'.$user->getId());
+ Authorization::setRole('user:'.$user->getId());
- $verification = $projectDB->createDocument($verification->getArrayCopy());
+ $verification = $projectDB->createDocument($verification->getArrayCopy());
- if (false === $verification) {
- throw new Exception('Failed saving verification to DB', 500);
- }
-
- $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND);
-
- $user = $projectDB->updateDocument($user->getArrayCopy());
-
- if (false === $user) {
- throw new Exception('Failed to save user to DB', 500);
- }
-
- $url = Template::parseURL($url);
- $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]);
- $url = Template::unParseURL($url);
-
- $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
- $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body'));
- $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
-
- $body
- ->setParam('{{content}}', $content->render())
- ->setParam('{{cta}}', $cta->render())
- ->setParam('{{title}}', Locale::getText('account.emails.verification.title'))
- ->setParam('{{direction}}', Locale::getText('settings.direction'))
- ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
- ->setParam('{{name}}', $user->getAttribute('name'))
- ->setParam('{{redirect}}', $url)
- ->setParam('{{bg-body}}', '#f6f6f6')
- ->setParam('{{bg-content}}', '#ffffff')
- ->setParam('{{bg-cta}}', '#3498db')
- ->setParam('{{bg-cta-hover}}', '#34495e')
- ->setParam('{{text-content}}', '#000000')
- ->setParam('{{text-cta}}', '#ffffff')
- ;
-
- $mail
- ->setParam('event', 'account.verification.create')
- ->setParam('recipient', $user->getAttribute('email'))
- ->setParam('name', $user->getAttribute('name'))
- ->setParam('subject', Locale::getText('account.emails.verification.title'))
- ->setParam('body', $body->render())
- ->trigger()
- ;
-
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'account.verification.create')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($verification->getArrayCopy(['$id', 'type', 'expire']))
- ;
+ if (false === $verification) {
+ throw new Exception('Failed saving verification to DB', 500);
}
- );
-$utopia->put('/v1/account/verification')
+ $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND);
+
+ $user = $projectDB->updateDocument($user->getArrayCopy());
+
+ if (false === $user) {
+ throw new Exception('Failed to save user to DB', 500);
+ }
+
+ $url = Template::parseURL($url);
+ $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]);
+ $url = Template::unParseURL($url);
+
+ $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl');
+ $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.verification.body'));
+ $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl');
+
+ $body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', $locale->getText('account.emails.verification.title'))
+ ->setParam('{{direction}}', $locale->getText('settings.direction'))
+ ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
+ ->setParam('{{name}}', $user->getAttribute('name'))
+ ->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
+ ;
+
+ $mails
+ ->setParam('event', 'account.verification.create')
+ ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name')))
+ ->setParam('recipient', $user->getAttribute('email'))
+ ->setParam('name', $user->getAttribute('name'))
+ ->setParam('subject', $locale->getText('account.emails.verification.title'))
+ ->setParam('body', $body->render())
+ ->trigger()
+ ;
+
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'account.verification.create')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($verification->getArrayCopy(['$id', 'type', 'expire']))
+ ;
+ }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails']);
+
+App::put('/v1/account/verification')
->desc('Complete Email Verification')
->groups(['api', 'account'])
->label('scope', 'public')
@@ -1319,54 +1386,57 @@ $utopia->put('/v1/account/verification')
->label('sdk.description', '/docs/references/account/update-verification.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->param('secret', '', function () { return new Text(256); }, 'Valid verification token.')
- ->action(
- function ($userId, $secret) use ($response, $user, $projectDB, $audit) {
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- '$id='.$userId,
- ],
- ]);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->param('secret', '', new Text(256), 'Valid verification token.')
+ ->action(function ($userId, $secret, $response, $user, $projectDB, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($profile)) {
- throw new Exception('User not found', 404); // TODO maybe hide this
- }
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ '$id='.$userId,
+ ],
+ ]);
- $verification = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret);
-
- if (!$verification) {
- throw new Exception('Invalid verification token', 401);
- }
-
- Authorization::setRole('user:'.$profile->getId());
-
- $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
- 'emailVerification' => true,
- ]));
-
- if (false === $profile) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- /**
- * We act like we're updating and validating
- * the verification token but actually we don't need it anymore.
- */
- if (!$projectDB->deleteDocument($verification)) {
- throw new Exception('Failed to remove verification from DB', 500);
- }
-
- $audit
- ->setParam('userId', $profile->getId())
- ->setParam('event', 'account.verification.update')
- ->setParam('resource', 'users/'.$user->getId())
- ;
-
- $verification = $profile->search('$id', $verification, $profile->getAttribute('tokens', []));
-
- $response->json($verification->getArrayCopy(['$id', 'type', 'expire']));
+ if (empty($profile)) {
+ throw new Exception('User not found', 404); // TODO maybe hide this
}
- );
\ No newline at end of file
+
+ $verification = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret);
+
+ if (!$verification) {
+ throw new Exception('Invalid verification token', 401);
+ }
+
+ Authorization::setRole('user:'.$profile->getId());
+
+ $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [
+ 'emailVerification' => true,
+ ]));
+
+ if (false === $profile) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ /**
+ * We act like we're updating and validating
+ * the verification token but actually we don't need it anymore.
+ */
+ if (!$projectDB->deleteDocument($verification)) {
+ throw new Exception('Failed to remove verification from DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $profile->getId())
+ ->setParam('event', 'account.verification.update')
+ ->setParam('resource', 'users/'.$user->getId())
+ ;
+
+ $verification = $profile->search('$id', $verification, $profile->getAttribute('tokens', []));
+
+ $response->json($verification->getArrayCopy(['$id', 'type', 'expire']));
+ }, ['response', 'user', 'projectDB', 'audits']);
\ No newline at end of file
diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php
index 9e888f7f7d..8349cce246 100644
--- a/app/controllers/api/avatars.php
+++ b/app/controllers/api/avatars.php
@@ -1,7 +1,6 @@
include __DIR__.'/../../config/avatars/browsers.php',
- 'credit-cards' => include __DIR__.'/../../config/avatars/credit-cards.php',
- 'flags' => include __DIR__.'/../../config/avatars/flags.php',
-];
+$avatarCallback = function ($type, $code, $width, $height, $quality, $response) {
+ /** @var Utopia\Response $response */
-$avatarCallback = function ($type, $code, $width, $height, $quality) use ($types, $response) {
$code = \strtolower($code);
$type = \strtolower($type);
+ $set = Config::getParam('avatar-'.$type, []);
- if (!\array_key_exists($type, $types)) {
+ if (empty($set)) {
throw new Exception('Avatar set not found', 404);
}
- if (!\array_key_exists($code, $types[$type])) {
+ if (!\array_key_exists($code, $set)) {
throw new Exception('Avatar not found', 404);
}
@@ -44,7 +40,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types
$output = 'png';
$date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = \md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
- $path = $types[$type][$code];
+ $path = $set[$code];
$type = 'png';
if (!\is_readable($path)) {
@@ -57,11 +53,11 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types
if ($data) {
//$output = (empty($output)) ? $type : $output;
- $response
+ return $response
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'hit')
- ->send($data, 0)
+ ->send($data)
;
}
@@ -71,403 +67,397 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types
$output = (empty($output)) ? $type : $output;
+ $data = $resize->output($output, $quality);
+
+ $cache->save($key, $data);
+
$response
->setContentType('image/png')
->addHeader('Expires', $date)
->addHeader('X-Appwrite-Cache', 'miss')
- ->send('', null)
+ ->send($data, null);
;
- $data = $resize->output($output, $quality);
-
- $cache->save($key, $data);
-
- echo $data;
-
unset($resize);
};
-$utopia->get('/v1/avatars/credit-cards/:code')
+App::get('/v1/avatars/credit-cards/:code')
->desc('Get Credit Card Icon')
->groups(['api', 'avatars'])
- ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: '.\implode(', ', \array_keys($types['credit-cards'])).'.')
- ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getCreditCard')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-credit-card.md')
- ->action(function ($code, $width, $height, $quality) use ($avatarCallback) {
- return $avatarCallback('credit-cards', $code, $width, $height, $quality);
- });
+ ->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: '.\implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))).'.')
+ ->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
+ ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) {
+ return $avatarCallback('credit-cards', $code, $width, $height, $quality, $response);
+ }, ['response']);
-$utopia->get('/v1/avatars/browsers/:code')
+App::get('/v1/avatars/browsers/:code')
->desc('Get Browser Icon')
->groups(['api', 'avatars'])
- ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['browsers'])); }, 'Browser Code.')
- ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getBrowser')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-browser.md')
- ->action(function ($code, $width, $height, $quality) use ($avatarCallback) {
- return $avatarCallback('browsers', $code, $width, $height, $quality);
- });
+ ->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
+ ->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
+ ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) {
+ return $avatarCallback('browsers', $code, $width, $height, $quality, $response);
+ }, ['response']);
-$utopia->get('/v1/avatars/flags/:code')
+App::get('/v1/avatars/flags/:code')
->desc('Get Country Flag')
->groups(['api', 'avatars'])
- ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['flags'])); }, 'Country Code. ISO Alpha-2 country code format.')
- ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFlag')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-flag.md')
- ->action(function ($code, $width, $height, $quality) use ($avatarCallback) {
- return $avatarCallback('flags', $code, $width, $height, $quality);
- });
+ ->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
+ ->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
+ ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) {
+ return $avatarCallback('flags', $code, $width, $height, $quality, $response);
+ }, ['response']);
-$utopia->get('/v1/avatars/image')
+App::get('/v1/avatars/image')
->desc('Get Image from URL')
->groups(['api', 'avatars'])
- ->param('url', '', function () { return new URL(); }, 'Image URL which you want to crop.')
- ->param('width', 400, function () { return new Range(0, 2000); }, 'Resize preview image width, Pass an integer between 0 to 2000.', true)
- ->param('height', 400, function () { return new Range(0, 2000); }, 'Resize preview image height, Pass an integer between 0 to 2000.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getImage')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-image.md')
- ->action(
- function ($url, $width, $height) use ($response) {
- $quality = 80;
- $output = 'png';
- $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
- $type = 'png';
- $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
- $data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
+ ->param('url', '', new URL(), 'Image URL which you want to crop.')
+ ->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true)
+ ->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true)
+ ->action(function ($url, $width, $height, $response) {
+ /** @var Utopia\Response $response */
- if ($data) {
- $response
- ->setContentType('image/png')
- ->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'hit')
- ->send($data, 0)
- ;
- }
+ $quality = 80;
+ $output = 'png';
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
+ $type = 'png';
+ $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
+ $data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
- if (!\extension_loaded('imagick')) {
- throw new Exception('Imagick extension is missing', 500);
- }
-
- $fetch = @\file_get_contents($url, false);
-
- if (!$fetch) {
- throw new Exception('Image not found', 404);
- }
-
- try {
- $resize = new Resize($fetch);
- } catch (\Exception $exception) {
- throw new Exception('Unable to parse image', 500);
- }
-
- $resize->crop((int) $width, (int) $height);
-
- $output = (empty($output)) ? $type : $output;
-
- $response
+ if ($data) {
+ return $response
->setContentType('image/png')
->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'miss')
- ->send('', null)
+ ->addHeader('X-Appwrite-Cache', 'hit')
+ ->send($data)
;
-
- $data = $resize->output($output, $quality);
-
- $cache->save($key, $data);
-
- echo $data;
-
- unset($resize);
}
- );
-$utopia->get('/v1/avatars/favicon')
+ if (!\extension_loaded('imagick')) {
+ throw new Exception('Imagick extension is missing', 500);
+ }
+
+ $fetch = @\file_get_contents($url, false);
+
+ if (!$fetch) {
+ throw new Exception('Image not found', 404);
+ }
+
+ try {
+ $resize = new Resize($fetch);
+ } catch (\Exception $exception) {
+ throw new Exception('Unable to parse image', 500);
+ }
+
+ $resize->crop((int) $width, (int) $height);
+
+ $output = (empty($output)) ? $type : $output;
+
+ $data = $resize->output($output, $quality);
+
+ $cache->save($key, $data);
+
+ $response
+ ->setContentType('image/png')
+ ->addHeader('Expires', $date)
+ ->addHeader('X-Appwrite-Cache', 'miss')
+ ->send($data);
+ ;
+
+ unset($resize);
+ }, ['response']);
+
+App::get('/v1/avatars/favicon')
->desc('Get Favicon')
->groups(['api', 'avatars'])
- ->param('url', '', function () { return new URL(); }, 'Website URL which you want to fetch the favicon from.')
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getFavicon')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-favicon.md')
- ->action(
- function ($url) use ($response, $request) {
- $width = 56;
- $height = 56;
- $quality = 80;
- $output = 'png';
- $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = \md5('/v2/avatars/favicon-'.$url);
- $type = 'png';
- $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
- $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
+ ->param('url', '', new URL(), 'Website URL which you want to fetch the favicon from.')
+ ->action(function ($url, $response) {
+ /** @var Utopia\Response $response */
- if ($data) {
- $response
- ->setContentType('image/png')
- ->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'hit')
- ->send($data, 0)
- ;
- }
+ $width = 56;
+ $height = 56;
+ $quality = 80;
+ $output = 'png';
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5('/v2/avatars/favicon-'.$url);
+ $type = 'png';
+ $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size
+ $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
- if (!\extension_loaded('imagick')) {
- throw new Exception('Imagick extension is missing', 500);
- }
-
- $curl = \curl_init();
-
- \curl_setopt_array($curl, [
- CURLOPT_RETURNTRANSFER => 1,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_MAXREDIRS => 3,
- CURLOPT_URL => $url,
- CURLOPT_USERAGENT => \sprintf(APP_USERAGENT,
- Config::getParam('version'),
- $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
- ),
- ]);
-
- $html = \curl_exec($curl);
-
- \curl_close($curl);
-
- if (!$html) {
- throw new Exception('Failed to fetch remote URL', 404);
- }
-
- $doc = new DOMDocument();
- $doc->strictErrorChecking = false;
- @$doc->loadHTML($html);
-
- $links = $doc->getElementsByTagName('link');
- $outputHref = '';
- $outputExt = '';
- $space = 0;
-
- foreach ($links as $link) { /* @var $link DOMElement */
- $href = $link->getAttribute('href');
- $rel = $link->getAttribute('rel');
- $sizes = $link->getAttribute('sizes');
- $absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href)));
-
- switch (\strtolower($rel)) {
- case 'icon':
- case 'shortcut icon':
- //case 'apple-touch-icon':
- $ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
-
- switch ($ext) {
- case 'ico':
- case 'png':
- case 'jpg':
- case 'jpeg':
- $size = \explode('x', \strtolower($sizes));
-
- $sizeWidth = (isset($size[0])) ? (int) $size[0] : 0;
- $sizeHeight = (isset($size[1])) ? (int) $size[1] : 0;
-
- if (($sizeWidth * $sizeHeight) >= $space) {
- $space = $sizeWidth * $sizeHeight;
- $outputHref = $absolute;
- $outputExt = $ext;
- }
-
- break;
- }
-
- break;
- }
- }
-
- if (empty($outputHref) || empty($outputExt)) {
- $default = \parse_url($url);
-
- $outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
- $outputExt = 'ico';
- }
-
- if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
- $data = @\file_get_contents($outputHref, false);
-
- if (empty($data) || (\mb_substr($data, 0, 5) === 'save($key, $data);
-
- $response
- ->setContentType('image/x-icon')
- ->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'miss')
- ->send($data, 0)
- ;
- }
-
- $fetch = @\file_get_contents($outputHref, false);
-
- if (!$fetch) {
- throw new Exception('Icon not found', 404);
- }
-
- $resize = new Resize($fetch);
-
- $resize->crop((int) $width, (int) $height);
-
- $output = (empty($output)) ? $type : $output;
-
- $response
+ if ($data) {
+ return $response
->setContentType('image/png')
->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'miss')
- ->send('', null)
+ ->addHeader('X-Appwrite-Cache', 'hit')
+ ->send($data)
;
+ }
- $data = $resize->output($output, $quality);
+ if (!\extension_loaded('imagick')) {
+ throw new Exception('Imagick extension is missing', 500);
+ }
+
+ $curl = \curl_init();
+
+ \curl_setopt_array($curl, [
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 3,
+ CURLOPT_URL => $url,
+ CURLOPT_USERAGENT => \sprintf(APP_USERAGENT,
+ App::getEnv('_APP_VERSION', 'UNKNOWN'),
+ App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
+ ),
+ ]);
+
+ $html = \curl_exec($curl);
+
+ \curl_close($curl);
+
+ if (!$html) {
+ throw new Exception('Failed to fetch remote URL', 404);
+ }
+
+ $doc = new DOMDocument();
+ $doc->strictErrorChecking = false;
+ @$doc->loadHTML($html);
+
+ $links = $doc->getElementsByTagName('link');
+ $outputHref = '';
+ $outputExt = '';
+ $space = 0;
+
+ foreach ($links as $link) { /* @var $link DOMElement */
+ $href = $link->getAttribute('href');
+ $rel = $link->getAttribute('rel');
+ $sizes = $link->getAttribute('sizes');
+ $absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href)));
+
+ switch (\strtolower($rel)) {
+ case 'icon':
+ case 'shortcut icon':
+ //case 'apple-touch-icon':
+ $ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
+
+ switch ($ext) {
+ case 'ico':
+ case 'png':
+ case 'jpg':
+ case 'jpeg':
+ $size = \explode('x', \strtolower($sizes));
+
+ $sizeWidth = (int) $size[0] ?? 0;
+ $sizeHeight = (int) $size[1] ?? 0;
+
+ if (($sizeWidth * $sizeHeight) >= $space) {
+ $space = $sizeWidth * $sizeHeight;
+ $outputHref = $absolute;
+ $outputExt = $ext;
+ }
+
+ break;
+ }
+
+ break;
+ }
+ }
+
+ if (empty($outputHref) || empty($outputExt)) {
+ $default = \parse_url($url);
+
+ $outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
+ $outputExt = 'ico';
+ }
+
+ if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
+ $data = @\file_get_contents($outputHref, false);
+
+ if (empty($data) || (\mb_substr($data, 0, 5) === 'save($key, $data);
- echo $data;
-
- unset($resize);
+ return $response
+ ->setContentType('image/x-icon')
+ ->addHeader('Expires', $date)
+ ->addHeader('X-Appwrite-Cache', 'miss')
+ ->send($data)
+ ;
}
- );
-$utopia->get('/v1/avatars/qr')
+ $fetch = @\file_get_contents($outputHref, false);
+
+ if (!$fetch) {
+ throw new Exception('Icon not found', 404);
+ }
+
+ $resize = new Resize($fetch);
+
+ $resize->crop((int) $width, (int) $height);
+
+ $output = (empty($output)) ? $type : $output;
+
+ $data = $resize->output($output, $quality);
+
+ $cache->save($key, $data);
+
+ $response
+ ->setContentType('image/png')
+ ->addHeader('Expires', $date)
+ ->addHeader('X-Appwrite-Cache', 'miss')
+ ->send($data);
+
+ unset($resize);
+ }, ['response']);
+
+App::get('/v1/avatars/qr')
->desc('Get QR Code')
->groups(['api', 'avatars'])
- ->param('text', '', function () { return new Text(512); }, 'Plain text to be converted to QR code image.')
- ->param('size', 400, function () { return new Range(0, 1000); }, 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true)
- ->param('margin', 1, function () { return new Range(0, 10); }, 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
- ->param('download', false, function () { return new Boolean(true); }, 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getQR')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-qr.md')
- ->action(
- function ($text, $size, $margin, $download) use ($response) {
- $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
+ ->param('text', '', new Text(512), 'Plain text to be converted to QR code image.')
+ ->param('size', 400, new Range(0, 1000), 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true)
+ ->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true)
+ ->param('download', false, new Boolean(true), 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true)
+ ->action(function ($text, $size, $margin, $download, $response) {
+ /** @var Utopia\Response $response */
- $renderer = new ImageRenderer(
- new RendererStyle($size, $margin),
- new ImagickImageBackEnd('png', 100)
- );
+ $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
- $writer = new Writer($renderer);
+ $renderer = new ImageRenderer(
+ new RendererStyle($size, $margin),
+ new ImagickImageBackEnd('png', 100)
+ );
- if ($download) {
- $response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
- }
+ $writer = new Writer($renderer);
- $response
- ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->setContentType('image/png')
- ->send($writer->writeString($text))
- ;
+ if ($download) {
+ $response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
}
- );
-$utopia->get('/v1/avatars/initials')
+ $response
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->setContentType('image/png')
+ ->send($writer->writeString($text))
+ ;
+ }, ['response']);
+
+App::get('/v1/avatars/initials')
->desc('Get User Initials')
->groups(['api', 'avatars'])
- ->param('name', '', function () { return new Text(512); }, 'Full Name. When empty, current user name or email will be used.', true)
- ->param('width', 500, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('height', 500, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('color', '', function () { return new HexColor(); }, 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true)
- ->param('background', '', function () { return new HexColor(); }, 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
->label('scope', 'avatars.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'avatars')
->label('sdk.method', 'getInitials')
->label('sdk.methodType', 'location')
->label('sdk.description', '/docs/references/avatars/get-initials.md')
- ->action(
- function ($name, $width, $height, $color, $background) use ($response, $user) {
- $themes = [
- ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
- ['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE
- ['color' => '#006128', 'background' => '#c9f3c6'], // GREEN
- ['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA
- ['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE
- ['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO
- ['color' => '#610038', 'background' => '#f5d1e6'], // PINK
- ['color' => '#386100', 'background' => '#dcf1bd'], // LIME
- ['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
- ['color' => '#610008', 'background' => '#f6d2d5'] // RED
- ];
+ ->param('name', '', new Text(128), 'Full Name. When empty, current user name or email will be used. Max length: 128 chars.', true)
+ ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('color', '', new HexColor(), 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true)
+ ->param('background', '', new HexColor(), 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true)
+ ->action(function ($name, $width, $height, $color, $background, $response, $user) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
- $rand = \rand(0, \count($themes)-1);
+ $themes = [
+ ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET
+ ['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE
+ ['color' => '#006128', 'background' => '#c9f3c6'], // GREEN
+ ['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA
+ ['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE
+ ['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO
+ ['color' => '#610038', 'background' => '#f5d1e6'], // PINK
+ ['color' => '#386100', 'background' => '#dcf1bd'], // LIME
+ ['color' => '#615800', 'background' => '#f1ecba'], // YELLOW
+ ['color' => '#610008', 'background' => '#f6d2d5'] // RED
+ ];
- $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
- $words = \explode(' ', \strtoupper($name));
- $initials = null;
- $code = 0;
+ $rand = \rand(0, \count($themes)-1);
- foreach ($words as $key => $w) {
- $initials .= (isset($w[0])) ? $w[0] : '';
- $code += (isset($w[0])) ? \ord($w[0]) : 0;
+ $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', ''));
+ $words = \explode(' ', \strtoupper($name));
+ $initials = null;
+ $code = 0;
- if ($key == 1) {
- break;
- }
+ foreach ($words as $key => $w) {
+ $initials .= $w[0] ?? '';
+ $code += (isset($w[0])) ? \ord($w[0]) : 0;
+
+ if ($key == 1) {
+ break;
}
-
- $length = \count($words);
- $rand = \substr($code,-1);
- $background = (!empty($background)) ? '#'.$background : $themes[$rand]['background'];
- $color = (!empty($color)) ? '#'.$color : $themes[$rand]['color'];
-
- $image = new \Imagick();
- $draw = new \ImagickDraw();
- $fontSize = \min($width, $height) / 2;
-
- $draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
- $image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
-
- $draw->setFillColor(new \ImagickPixel($color));
- $draw->setFontSize($fontSize);
-
- $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
- $draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
-
- $image->newImage($width, $height, $background);
- $image->setImageFormat("png");
- $image->drawImage($draw);
-
- //$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
-
- $response
- ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->setContentType('image/png')
- ->send($image->getImageBlob())
- ;
}
- );
\ No newline at end of file
+
+ $length = \count($words);
+ $rand = \substr($code,-1);
+ $background = (!empty($background)) ? '#'.$background : $themes[$rand]['background'];
+ $color = (!empty($color)) ? '#'.$color : $themes[$rand]['color'];
+
+ $image = new \Imagick();
+ $draw = new \ImagickDraw();
+ $fontSize = \min($width, $height) / 2;
+
+ $draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
+ $image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf");
+
+ $draw->setFillColor(new \ImagickPixel($color));
+ $draw->setFontSize($fontSize);
+
+ $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
+ $draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials);
+
+ $image->newImage($width, $height, $background);
+ $image->setImageFormat("png");
+ $image->drawImage($draw);
+
+ //$image->setImageCompressionQuality(9 - round(($quality / 100) * 9));
+
+ $response
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->setContentType('image/png')
+ ->send($image->getImageBlob())
+ ;
+ }, ['response', 'user']);
\ No newline at end of file
diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php
index dadf7e9576..bd19935754 100644
--- a/app/controllers/api/database.php
+++ b/app/controllers/api/database.php
@@ -1,11 +1,8 @@
post('/v1/database/collections')
+App::post('/v1/database/collections')
->desc('Create Collection')
->groups(['api', 'database'])
->label('webhook', 'database.collections.create')
@@ -36,72 +30,75 @@ $utopia->post('/v1/database/collections')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.method', 'createCollection')
->label('sdk.description', '/docs/references/database/create-collection.md')
- ->param('name', '', function () { return new Text(256); }, 'Collection name.')
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.')
- ->action(
- function ($name, $read, $write, $rules) use ($response, $projectDB, $webhook, $audit) {
- $parsedRules = [];
+ ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.')
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', false, ['projectDB'])
+ ->action(function ($name, $read, $write, $rules, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- foreach ($rules as &$rule) {
- $parsedRules[] = \array_merge([
- '$collection' => Database::SYSTEM_COLLECTION_RULES,
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- ], $rule);
- }
+ $parsedRules = [];
- try {
- $data = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
- 'name' => $name,
- 'dateCreated' => \time(),
- 'dateUpdated' => \time(),
- 'structure' => true,
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- 'rules' => $parsedRules,
- ]);
- } catch (AuthorizationException $exception) {
- throw new Exception('Unauthorized action', 401);
- } catch (StructureException $exception) {
- throw new Exception('Bad structure. '.$exception->getMessage(), 400);
- } catch (\Exception $exception) {
- throw new Exception('Failed saving document to DB', 500);
- }
-
- if (false === $data) {
- throw new Exception('Failed saving collection to DB', 500);
- }
-
- $data = $data->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.collections.create')
- ->setParam('resource', 'database/collection/'.$data['$id'])
- ->setParam('data', $data)
- ;
-
- /*
- * View
- */
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($data)
- ;
+ foreach ($rules as &$rule) {
+ $parsedRules[] = \array_merge([
+ '$collection' => Database::SYSTEM_COLLECTION_RULES,
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ ], $rule);
}
- );
-$utopia->get('/v1/database/collections')
+ try {
+ $data = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
+ 'name' => $name,
+ 'dateCreated' => \time(),
+ 'dateUpdated' => \time(),
+ 'structure' => true,
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ 'rules' => $parsedRules,
+ ]);
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (StructureException $exception) {
+ throw new Exception('Bad structure. '.$exception->getMessage(), 400);
+ } catch (\Exception $exception) {
+ throw new Exception('Failed saving document to DB', 500);
+ }
+
+ if (false === $data) {
+ throw new Exception('Failed saving collection to DB', 500);
+ }
+
+ $data = $data->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.collections.create')
+ ->setParam('resource', 'database/collection/'.$data['$id'])
+ ->setParam('data', $data)
+ ;
+
+ /*
+ * View
+ */
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($data)
+ ;
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::get('/v1/database/collections')
->desc('List Collections')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@@ -109,48 +106,30 @@ $utopia->get('/v1/database/collections')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.method', 'listCollections')
->label('sdk.description', '/docs/references/database/list-collections.md')
- ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
- ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
- ->param('offset', 0, function () { return new Range(0, 40000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
- ->action(
- function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
- /*$vl = new Structure($projectDB);
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- var_dump($vl->isValid(new Document([
- '$collection' => Database::SYSTEM_COLLECTION_RULES,
- '$permissions' => [
- 'read' => ['*'],
- 'write' => ['*'],
- ],
- 'label' => 'Platforms',
- 'key' => 'platforms',
- 'type' => 'document',
- 'default' => [],
- 'required' => false,
- 'array' => true,
- 'options' => [Database::SYSTEM_COLLECTION_PLATFORMS],
- ])));
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'name',
+ 'orderType' => $orderType,
+ 'orderCast' => 'string',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
+ ],
+ ]);
- var_dump($vl->getDescription());*/
+ $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
+ }, ['response', 'projectDB']);
- $results = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => 'name',
- 'orderType' => $orderType,
- 'orderCast' => 'string',
- 'search' => $search,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
- ],
- ]);
-
- $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
- }
- );
-
-$utopia->get('/v1/database/collections/:collectionId')
+App::get('/v1/database/collections/:collectionId')
->desc('Get Collection')
->groups(['api', 'database'])
->label('scope', 'collections.read')
@@ -158,20 +137,21 @@ $utopia->get('/v1/database/collections/:collectionId')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.method', 'getCollection')
->label('sdk.description', '/docs/references/database/get-collection.md')
- ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
- ->action(
- function ($collectionId) use ($response, $projectDB) {
- $collection = $projectDB->getDocument($collectionId, false);
+ ->param('collectionId', '', new UID(), 'Collection unique ID.')
+ ->action(function ($collectionId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+
+ $collection = $projectDB->getDocument($collectionId, false);
- if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
-
- $response->json($collection->getArrayCopy());
+ if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
}
- );
-// $utopia->get('/v1/database/collections/:collectionId/logs')
+ $response->json($collection->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+// App::get('/v1/database/collections/:collectionId/logs')
// ->desc('Get Collection Logs')
// ->groups(['api', 'database'])
// ->label('scope', 'collections.read')
@@ -179,7 +159,7 @@ $utopia->get('/v1/database/collections/:collectionId')
// ->label('sdk.namespace', 'database')
// ->label('sdk.method', 'getCollectionLogs')
// ->label('sdk.description', '/docs/references/database/get-collection-logs.md')
-// ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
+// ->param('collectionId', '', new UID(), 'Collection unique ID.')
// ->action(
// function ($collectionId) use ($response, $register, $projectDB, $project) {
// $collection = $projectDB->getDocument($collectionId, false);
@@ -236,7 +216,7 @@ $utopia->get('/v1/database/collections/:collectionId')
// }
// );
-$utopia->put('/v1/database/collections/:collectionId')
+App::put('/v1/database/collections/:collectionId')
->desc('Update Collection')
->groups(['api', 'database'])
->label('scope', 'collections.write')
@@ -245,71 +225,74 @@ $utopia->put('/v1/database/collections/:collectionId')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.method', 'updateCollection')
->label('sdk.description', '/docs/references/database/update-collection.md')
- ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Collection name.')
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true)
- ->action(
- function ($collectionId, $name, $read, $write, $rules) use ($response, $projectDB, $webhook, $audit) {
- $collection = $projectDB->getDocument($collectionId, false);
+ ->param('collectionId', '', new UID(), 'Collection unique ID.')
+ ->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true, ['projectDB'])
+ ->action(function ($collectionId, $name, $read, $write, $rules, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
+ $collection = $projectDB->getDocument($collectionId, false);
- $parsedRules = [];
-
- foreach ($rules as &$rule) {
- $parsedRules[] = \array_merge([
- '$collection' => Database::SYSTEM_COLLECTION_RULES,
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- ], $rule);
- }
-
- try {
- $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [
- 'name' => $name,
- 'structure' => true,
- 'dateUpdated' => \time(),
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- 'rules' => $parsedRules,
- ]));
- } catch (AuthorizationException $exception) {
- throw new Exception('Unauthorized action', 401);
- } catch (StructureException $exception) {
- throw new Exception('Bad structure. '.$exception->getMessage(), 400);
- } catch (\Exception $exception) {
- throw new Exception('Failed saving document to DB', 500);
- }
-
- if (false === $collection) {
- throw new Exception('Failed saving collection to DB', 500);
- }
-
- $data = $collection->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.collections.update')
- ->setParam('resource', 'database/collections/'.$data['$id'])
- ->setParam('data', $data)
- ;
-
- $response->json($collection->getArrayCopy());
+ if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
}
- );
-$utopia->delete('/v1/database/collections/:collectionId')
+ $parsedRules = [];
+
+ foreach ($rules as &$rule) {
+ $parsedRules[] = \array_merge([
+ '$collection' => Database::SYSTEM_COLLECTION_RULES,
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ ], $rule);
+ }
+
+ try {
+ $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [
+ 'name' => $name,
+ 'structure' => true,
+ 'dateUpdated' => \time(),
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ 'rules' => $parsedRules,
+ ]));
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (StructureException $exception) {
+ throw new Exception('Bad structure. '.$exception->getMessage(), 400);
+ } catch (\Exception $exception) {
+ throw new Exception('Failed saving document to DB', 500);
+ }
+
+ if (false === $collection) {
+ throw new Exception('Failed saving collection to DB', 500);
+ }
+
+ $data = $collection->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.collections.update')
+ ->setParam('resource', 'database/collections/'.$data['$id'])
+ ->setParam('data', $data)
+ ;
+
+ $response->json($collection->getArrayCopy());
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::delete('/v1/database/collections/:collectionId')
->desc('Delete Collection')
->groups(['api', 'database'])
->label('scope', 'collections.write')
@@ -318,36 +301,39 @@ $utopia->delete('/v1/database/collections/:collectionId')
->label('sdk.platform', [APP_PLATFORM_SERVER])
->label('sdk.method', 'deleteCollection')
->label('sdk.description', '/docs/references/database/delete-collection.md')
- ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
- ->action(
- function ($collectionId) use ($response, $projectDB, $webhook, $audit) {
- $collection = $projectDB->getDocument($collectionId, false);
+ ->param('collectionId', '', new UID(), 'Collection unique ID.')
+ ->action(function ($collectionId, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
+ $collection = $projectDB->getDocument($collectionId, false);
- if (!$projectDB->deleteDocument($collectionId)) {
- throw new Exception('Failed to remove collection from DB', 500);
- }
-
- $data = $collection->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.collections.delete')
- ->setParam('resource', 'database/collections/'.$data['$id'])
- ->setParam('data', $data)
- ;
-
- $response->noContent();
+ if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
}
- );
-$utopia->post('/v1/database/collections/:collectionId/documents')
+ if (!$projectDB->deleteDocument($collectionId)) {
+ throw new Exception('Failed to remove collection from DB', 500);
+ }
+
+ $data = $collection->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.collections.delete')
+ ->setParam('resource', 'database/collections/'.$data['$id'])
+ ->setParam('data', $data)
+ ;
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::post('/v1/database/collections/:collectionId/documents')
->desc('Create Document')
->groups(['api', 'database'])
->label('webhook', 'database.documents.create')
@@ -356,118 +342,122 @@ $utopia->post('/v1/database/collections/:collectionId/documents')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'createDocument')
->label('sdk.description', '/docs/references/database/create-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
- ->param('data', [], function () { return new JSON(); }, 'Document data as JSON object.')
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('parentDocument', '', function () { return new UID(); }, 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
- ->param('parentProperty', '', function () { return new Key(); }, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
- ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () { return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]); }, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
- ->action(
- function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, $webhook, $audit) {
- $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
+ ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('data', [], new JSON(), 'Document data as JSON object.')
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
+ ->param('parentProperty', '', new Key(), 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
+ ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND], true), 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
+ ->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
+
+ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
- if (empty($data)) {
- throw new Exception('Missing payload', 400);
+ if (empty($data)) {
+ throw new Exception('Missing payload', 400);
+ }
+
+ if (isset($data['$id'])) {
+ throw new Exception('$id is not allowed for creating new documents, try update instead', 400);
+ }
+
+ $collection = $projectDB->getDocument($collectionId, false);
+
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
+ }
+
+ $data['$collection'] = $collectionId; // Adding this param to make API easier for developers
+ $data['$permissions'] = [
+ 'read' => $read,
+ 'write' => $write,
+ ];
+
+ // Read parent document + validate not 404 + validate read / write permission like patch method
+ // Add payload to parent document property
+ if ((!empty($parentDocument)) && (!empty($parentProperty))) {
+ $parentDocument = $projectDB->getDocument($parentDocument, false);
+
+ if (empty($parentDocument->getArrayCopy())) { // Check empty
+ throw new Exception('No parent document found', 404);
}
- if (isset($data['$id'])) {
- throw new Exception('$id is not allowed for creating new documents, try update instead', 400);
- }
-
- $collection = $projectDB->getDocument($collectionId, false);
-
- if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
-
- $data['$collection'] = $collectionId; // Adding this param to make API easier for developers
- $data['$permissions'] = [
- 'read' => $read,
- 'write' => $write,
- ];
-
- // Read parent document + validate not 404 + validate read / write permission like patch method
- // Add payload to parent document property
- if ((!empty($parentDocument)) && (!empty($parentProperty))) {
- $parentDocument = $projectDB->getDocument($parentDocument, false);
-
- if (empty($parentDocument->getArrayCopy())) { // Check empty
- throw new Exception('No parent document found', 404);
- }
-
- /*
- * 1. Check child has valid structure,
- * 2. Check user have write permission for parent document
- * 3. Assign parent data (including child) to $data
- * 4. Validate the combined result has valid structure (inside $projectDB->createDocument method)
- */
-
- $new = new Document($data);
-
- $structure = new Structure($projectDB);
-
- if (!$structure->isValid($new)) {
- throw new Exception('Invalid data structure: '.$structure->getDescription(), 400);
- }
-
- $authorization = new Authorization($parentDocument, 'write');
-
- if (!$authorization->isValid($new->getPermissions())) {
- throw new Exception('Unauthorized action', 401);
- }
-
- $parentDocument
- ->setAttribute($parentProperty, $data, $parentPropertyType);
-
- $data = $parentDocument->getArrayCopy();
- }
-
- /**
- * Set default collection values
- */
- foreach ($collection->getAttribute('rules') as $key => $rule) {
- $key = (isset($rule['key'])) ? $rule['key'] : '';
- $default = (isset($rule['default'])) ? $rule['default'] : null;
-
- if (!isset($data[$key])) {
- $data[$key] = $default;
- }
- }
-
- try {
- $data = $projectDB->createDocument($data);
- } catch (AuthorizationException $exception) {
- throw new Exception('Unauthorized action', 401);
- } catch (StructureException $exception) {
- throw new Exception('Bad structure. '.$exception->getMessage(), 400);
- } catch (\Exception $exception) {
- throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
- }
-
- $data = $data->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.documents.create')
- ->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data)
- ;
-
/*
- * View
- */
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($data)
- ;
- }
- );
+ * 1. Check child has valid structure,
+ * 2. Check user have write permission for parent document
+ * 3. Assign parent data (including child) to $data
+ * 4. Validate the combined result has valid structure (inside $projectDB->createDocument method)
+ */
-$utopia->get('/v1/database/collections/:collectionId/documents')
+ $new = new Document($data);
+
+ $structure = new Structure($projectDB);
+
+ if (!$structure->isValid($new)) {
+ throw new Exception('Invalid data structure: '.$structure->getDescription(), 400);
+ }
+
+ $authorization = new Authorization($parentDocument, 'write');
+
+ if (!$authorization->isValid($new->getPermissions())) {
+ throw new Exception('Unauthorized permissions', 401);
+ }
+
+ $parentDocument
+ ->setAttribute($parentProperty, $data, $parentPropertyType);
+
+ $data = $parentDocument->getArrayCopy();
+ $collection = $projectDB->getDocument($parentDocument->getCollection(), false);
+ }
+
+ /**
+ * Set default collection values
+ */
+ foreach ($collection->getAttribute('rules') as $key => $rule) {
+ $key = $rule['key'] ?? '';
+ $default = $rule['default'] ?? null;
+
+ if (!isset($data[$key])) {
+ $data[$key] = $default;
+ }
+ }
+
+ try {
+ $data = $projectDB->createDocument($data);
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (StructureException $exception) {
+ throw new Exception('Bad structure. '.$exception->getMessage(), 400);
+ } catch (\Exception $exception) {
+ throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
+ }
+
+ $data = $data->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.documents.create')
+ ->setParam('resource', 'database/document/'.$data['$id'])
+ ->setParam('data', $data)
+ ;
+
+ /*
+ * View
+ */
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($data)
+ ;
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::get('/v1/database/collections/:collectionId/documents')
->desc('List Documents')
->groups(['api', 'database'])
->label('scope', 'documents.read')
@@ -475,59 +465,60 @@ $utopia->get('/v1/database/collections/:collectionId/documents')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'listDocuments')
->label('sdk.description', '/docs/references/database/list-documents.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
- ->param('filters', [], function () { return new ArrayList(new Text(128)); }, 'Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: \'name=John Doe\' or \'category.$id>=5bed2d152c362\'.', true)
- ->param('offset', 0, function () { return new Range(0, 900000000); }, 'Offset value. Use this value to manage pagination.', true)
- ->param('limit', 50, function () { return new Range(0, 1000); }, 'Maximum number of documents to return in response. Use this value to manage pagination.', true)
- ->param('orderField', '$id', function () { return new Text(128); }, 'Document field that results will be sorted by.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(array('DESC', 'ASC')); }, 'Order direction. Possible values are DESC for descending order, or ASC for ascending order.', true)
- ->param('orderCast', 'string', function () { return new WhiteList(array('int', 'string', 'date', 'time', 'datetime')); }, 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true)
- ->param('search', '', function () { return new Text(256); }, 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children.', true)
- ->action(
- function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search) use ($response, $projectDB, $utopia) {
- $collection = $projectDB->getDocument($collectionId, false);
+ ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('filters', [], new ArrayList(new Text(128)), 'Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: \'name=John Doe\' or \'category.$id>=5bed2d152c362\'.', true)
+ ->param('limit', 25, new Range(0, 1000), 'Maximum number of documents to return in response. Use this value to manage pagination.', true)
+ ->param('offset', 0, new Range(0, 900000000), 'Offset value. Use this value to manage pagination.', true)
+ ->param('orderField', '$id', new Text(128), 'Document field that results will be sorted by.', true)
+ ->param('orderType', 'ASC', new WhiteList(['DESC', 'ASC'], true), 'Order direction. Possible values are DESC for descending order, or ASC for ascending order.', true)
+ ->param('orderCast', 'string', new WhiteList(['int', 'string', 'date', 'time', 'datetime'], true), 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true)
+ ->param('search', '', new Text(256), 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.', true)
+ ->action(function ($collectionId, $filters, $limit, $offset, $orderField, $orderType, $orderCast, $search, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
+ $collection = $projectDB->getDocument($collectionId, false);
- $list = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => $orderField,
- 'orderType' => $orderType,
- 'orderCast' => $orderCast,
- 'search' => $search,
- 'filters' => \array_merge($filters, [
- '$collection='.$collectionId,
- ]),
- ]);
-
- if ($utopia->isDevelopment()) {
- $collection
- ->setAttribute('debug', $projectDB->getDebug())
- ->setAttribute('limit', $limit)
- ->setAttribute('offset', $offset)
- ->setAttribute('orderField', $orderField)
- ->setAttribute('orderType', $orderType)
- ->setAttribute('orderCast', $orderCast)
- ->setAttribute('filters', $filters)
- ;
- }
-
- $collection
- ->setAttribute('sum', $projectDB->getSum())
- ->setAttribute('documents', $list)
- ;
-
- /*
- * View
- */
- $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules']));
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
}
- );
-$utopia->get('/v1/database/collections/:collectionId/documents/:documentId')
+ $list = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => $orderField,
+ 'orderType' => $orderType,
+ 'orderCast' => $orderCast,
+ 'search' => $search,
+ 'filters' => \array_merge($filters, [
+ '$collection='.$collectionId,
+ ]),
+ ]);
+
+ if (App::isDevelopment()) {
+ $collection
+ ->setAttribute('debug', $projectDB->getDebug())
+ ->setAttribute('limit', $limit)
+ ->setAttribute('offset', $offset)
+ ->setAttribute('orderField', $orderField)
+ ->setAttribute('orderType', $orderType)
+ ->setAttribute('orderCast', $orderCast)
+ ->setAttribute('filters', $filters)
+ ;
+ }
+
+ $collection
+ ->setAttribute('sum', $projectDB->getSum())
+ ->setAttribute('documents', $list)
+ ;
+
+ /*
+ * View
+ */
+ $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules']));
+ }, ['response', 'projectDB']);
+
+App::get('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Get Document')
->groups(['api', 'database'])
->label('scope', 'documents.read')
@@ -535,45 +526,47 @@ $utopia->get('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'getDocument')
->label('sdk.description', '/docs/references/database/get-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
- ->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
- ->action(
- function ($collectionId, $documentId) use ($response, $request, $projectDB) {
- $document = $projectDB->getDocument($documentId, false);
- $collection = $projectDB->getDocument($collectionId, false);
+ ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('documentId', null, new UID(), 'Document unique ID.')
+ ->action(function ($collectionId, $documentId, $request, $response, $projectDB) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getId()) { // Check empty
+ $document = $projectDB->getDocument($documentId, false);
+ $collection = $projectDB->getDocument($collectionId, false);
+
+ if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getId()) { // Check empty
+ throw new Exception('No document found', 404);
+ }
+
+ $output = $document->getArrayCopy();
+
+ $paths = \explode('/', $request->getParam('q', ''));
+ $paths = \array_slice($paths, 7, \count($paths));
+
+ if (\count($paths) > 0) {
+ if (\count($paths) % 2 == 1) {
+ $output = $document->getAttribute(\implode('.', $paths));
+ } else {
+ $id = (int) \array_pop($paths);
+ $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths)));
+ }
+
+ $output = ($output instanceof Document) ? $output->getArrayCopy() : $output;
+
+ if (!\is_array($output)) {
throw new Exception('No document found', 404);
}
-
- $output = $document->getArrayCopy();
-
- $paths = \explode('/', $request->getParam('q', ''));
- $paths = \array_slice($paths, 7, \count($paths));
-
- if (\count($paths) > 0) {
- if (\count($paths) % 2 == 1) {
- $output = $document->getAttribute(\implode('.', $paths));
- } else {
- $id = (int) \array_pop($paths);
- $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths)));
- }
-
- $output = ($output instanceof Document) ? $output->getArrayCopy() : $output;
-
- if (!\is_array($output)) {
- throw new Exception('No document found', 404);
- }
- }
-
- /*
- * View
- */
- $response->json($output);
}
- );
-$utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
+ /*
+ * View
+ */
+ $response->json($output);
+ }, ['request', 'response', 'projectDB']);
+
+App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Update Document')
->groups(['api', 'database'])
->label('webhook', 'database.documents.update')
@@ -582,78 +575,81 @@ $utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'updateDocument')
->label('sdk.description', '/docs/references/database/update-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
- ->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
- ->param('data', [], function () { return new JSON(); }, 'Document data as JSON object.')
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->action(
- function ($collectionId, $documentId, $data, $read, $write) use ($response, $projectDB, $webhook, $audit) {
- $collection = $projectDB->getDocument($collectionId, false);
- $document = $projectDB->getDocument($documentId, false);
+ ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('documentId', null, new UID(), 'Document unique ID.')
+ ->param('data', [], new JSON(), 'Document data as JSON object.')
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
+ $collection = $projectDB->getDocument($collectionId, false);
+ $document = $projectDB->getDocument($documentId, false);
- if (!\is_array($data)) {
- throw new Exception('Data param should be a valid JSON', 400);
- }
+ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
- if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
-
- if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty
- throw new Exception('No document found', 404);
- }
-
- //TODO check merge read write permissions
-
- if (!empty($read)) { // Overwrite permissions only when passed
- $data['$permissions']['read'] = $read;
- }
-
- if (!empty($write)) { // Overwrite permissions only when passed
- $data['$permissions']['write'] = $write;
- }
-
- $data = \array_merge($document->getArrayCopy(), $data);
-
- $data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID
- $data['$id'] = $document->getId(); // Make sure user don't switch document unique ID
-
- if (empty($data)) {
- throw new Exception('Missing payload', 400);
- }
- try {
- $data = $projectDB->updateDocument($data);
- } catch (AuthorizationException $exception) {
- throw new Exception('Unauthorized action', 401);
- } catch (StructureException $exception) {
- throw new Exception('Bad structure. '.$exception->getMessage(), 400);
- } catch (\Exception $exception) {
- throw new Exception('Failed saving document to DB', 500);
- }
-
- $data = $data->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.documents.update')
- ->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data)
- ;
-
- /*
- * View
- */
- $response->json($data);
+ if (!\is_array($data)) {
+ throw new Exception('Data param should be a valid JSON', 400);
}
- );
-$utopia->delete('/v1/database/collections/:collectionId/documents/:documentId')
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
+ }
+
+ if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty
+ throw new Exception('No document found', 404);
+ }
+
+ //TODO check merge read write permissions
+
+ if (!empty($read)) { // Overwrite permissions only when passed
+ $data['$permissions']['read'] = $read;
+ }
+
+ if (!empty($write)) { // Overwrite permissions only when passed
+ $data['$permissions']['write'] = $write;
+ }
+
+ $data = \array_merge($document->getArrayCopy(), $data);
+
+ $data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID
+ $data['$id'] = $document->getId(); // Make sure user don't switch document unique ID
+
+ if (empty($data)) {
+ throw new Exception('Missing payload', 400);
+ }
+ try {
+ $data = $projectDB->updateDocument($data);
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (StructureException $exception) {
+ throw new Exception('Bad structure. '.$exception->getMessage(), 400);
+ } catch (\Exception $exception) {
+ throw new Exception('Failed saving document to DB', 500);
+ }
+
+ $data = $data->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.documents.update')
+ ->setParam('resource', 'database/document/'.$data['$id'])
+ ->setParam('data', $data)
+ ;
+
+ /*
+ * View
+ */
+ $response->json($data);
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::delete('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Delete Document')
->groups(['api', 'database'])
->label('scope', 'documents.write')
@@ -662,43 +658,46 @@ $utopia->delete('/v1/database/collections/:collectionId/documents/:documentId')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'deleteDocument')
->label('sdk.description', '/docs/references/database/delete-document.md')
- ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
- ->param('documentId', null, function () { return new UID(); }, 'Document unique ID.')
- ->action(
- function ($collectionId, $documentId) use ($response, $projectDB, $audit, $webhook) {
- $collection = $projectDB->getDocument($collectionId, false);
- $document = $projectDB->getDocument($documentId, false);
+ ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).')
+ ->param('documentId', null, new UID(), 'Document unique ID.')
+ ->action(function ($collectionId, $documentId, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty
- throw new Exception('No document found', 404);
- }
+ $collection = $projectDB->getDocument($collectionId, false);
+ $document = $projectDB->getDocument($documentId, false);
- if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
- throw new Exception('Collection not found', 404);
- }
-
- try {
- $projectDB->deleteDocument($documentId);
- } catch (AuthorizationException $exception) {
- throw new Exception('Unauthorized action', 401);
- } catch (StructureException $exception) {
- throw new Exception('Bad structure. '.$exception->getMessage(), 400);
- } catch (\Exception $exception) {
- throw new Exception('Failed to remove document from DB', 500);
- }
-
- $data = $document->getArrayCopy();
-
- $webhook
- ->setParam('payload', $data)
- ;
-
- $audit
- ->setParam('event', 'database.documents.delete')
- ->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data) // Audit document in case of malicious or disastrous action
- ;
-
- $response->noContent();
+ if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty
+ throw new Exception('No document found', 404);
}
- );
+
+ if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
+ throw new Exception('Collection not found', 404);
+ }
+
+ try {
+ $projectDB->deleteDocument($documentId);
+ } catch (AuthorizationException $exception) {
+ throw new Exception('Unauthorized permissions', 401);
+ } catch (StructureException $exception) {
+ throw new Exception('Bad structure. '.$exception->getMessage(), 400);
+ } catch (\Exception $exception) {
+ throw new Exception('Failed to remove document from DB', 500);
+ }
+
+ $data = $document->getArrayCopy();
+
+ $webhooks
+ ->setParam('payload', $data)
+ ;
+
+ $audits
+ ->setParam('event', 'database.documents.delete')
+ ->setParam('resource', 'database/document/'.$data['$id'])
+ ->setParam('data', $data) // Audit document in case of malicious or disastrous action
+ ;
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
\ No newline at end of file
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
new file mode 100644
index 0000000000..039eeb3f55
--- /dev/null
+++ b/app/controllers/api/functions.php
@@ -0,0 +1,539 @@
+groups(['api', 'functions'])
+ ->desc('Create Function')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'create')
+ ->label('sdk.description', '/docs/references/functions/create-function.md')
+ ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
+ ->param('env', '', new WhiteList(array_keys(Config::getParam('environments')), true), 'Execution enviornment.')
+ ->param('vars', [], new Assoc(), 'Key-value JSON object.', true)
+ ->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
+ ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
+ ->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
+ ->action(function ($name, $env, $vars, $events, $schedule, $timeout, $response, $projectDB) {
+ $function = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS,
+ '$permissions' => [
+ 'read' => [],
+ 'write' => [],
+ ],
+ 'dateCreated' => time(),
+ 'dateUpdated' => time(),
+ 'status' => 'paused',
+ 'name' => $name,
+ 'env' => $env,
+ 'tag' => '',
+ 'vars' => $vars,
+ 'events' => $events,
+ 'schedule' => $schedule,
+ 'previous' => null,
+ 'next' => null,
+ 'timeout' => $timeout,
+ ]);
+
+ if (false === $function) {
+ throw new Exception('Failed saving function to DB', 500);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($function->getArrayCopy())
+ ;
+ }, ['response', 'projectDB']);
+
+App::get('/v1/functions')
+ ->groups(['api', 'functions'])
+ ->desc('List Functions')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'list')
+ ->label('sdk.description', '/docs/references/functions/list-functions.md')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) {
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'dateCreated',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_FUNCTIONS,
+ ],
+ ]);
+
+ $response->json(['sum' => $projectDB->getSum(), 'functions' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/functions/:functionId')
+ ->groups(['api', 'functions'])
+ ->desc('Get Function')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'get')
+ ->label('sdk.description', '/docs/references/functions/get-function.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->action(function ($functionId, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('function not found', 404);
+ }
+
+ $response->json($function->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+App::put('/v1/functions/:functionId')
+ ->groups(['api', 'functions'])
+ ->desc('Update Function')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'update')
+ ->label('sdk.description', '/docs/references/functions/update-function.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
+ ->param('vars', [], new Assoc(), 'Key-value JSON object.', true)
+ ->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
+ ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
+ ->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
+ ->action(function ($functionId, $name, $vars, $events, $schedule, $timeout, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? CronExpression::factory($schedule) : null;
+ $next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null;
+
+ $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
+ 'dateUpdated' => time(),
+ 'name' => $name,
+ 'vars' => $vars,
+ 'events' => $events,
+ 'schedule' => $schedule,
+ 'previous' => null,
+ 'next' => $next,
+ 'timeout' => $timeout,
+ ]));
+
+ if (false === $function) {
+ throw new Exception('Failed saving function to DB', 500);
+ }
+
+ $response->json($function->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+App::patch('/v1/functions/:functionId/tag')
+ ->groups(['api', 'functions'])
+ ->desc('Update Function Tag')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'updateTag')
+ ->label('sdk.description', '/docs/references/functions/update-tag.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('tag', '', new UID(), 'Tag unique ID.')
+ ->action(function ($functionId, $tag, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+ $tag = $projectDB->getDocument($tag);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) {
+ throw new Exception('Tag not found', 404);
+ }
+
+ $schedule = $function->getAttribute('schedule', '');
+ $cron = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? CronExpression::factory($schedule) : null;
+ $next = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? $cron->getNextRunDate()->format('U') : null;
+
+ $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
+ 'tag' => $tag->getId(),
+ 'next' => $next,
+ ]));
+
+ if (false === $function) {
+ throw new Exception('Failed saving function to DB', 500);
+ }
+
+ $response->json($function->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/functions/:functionId')
+ ->groups(['api', 'functions'])
+ ->desc('Delete Function')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'delete')
+ ->label('sdk.description', '/docs/references/functions/delete-function.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->action(function ($functionId, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ if (!$projectDB->deleteDocument($function->getId())) {
+ throw new Exception('Failed to remove function from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'projectDB']);
+
+App::post('/v1/functions/:functionId/tags')
+ ->groups(['api', 'functions'])
+ ->desc('Create Tag')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'createTag')
+ ->label('sdk.description', '/docs/references/functions/create-tag.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('command', '', new Text('1028'), 'Code execution command.')
+ ->param('code', [], new File(), 'Gzip file containing your code.', false)
+ // ->param('code', '', new Text(128), 'Code package. Use the '.APP_NAME.' code packager to create a deployable package file.')
+ ->action(function ($functionId, $command, $code, $request, $response, $projectDB, $usage) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $file = $request->getFiles('code');
+ $device = Storage::getDevice('functions');
+ $fileType = new FileType([FileType::FILE_TYPE_GZIP]);
+ $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0));
+ $upload = new Upload();
+
+ if (empty($file)) {
+ throw new Exception('No file sent', 400);
+ }
+
+ // Make sure we handle a single file and multiple files the same way
+ $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
+ $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
+ $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
+
+ // Check if file type is allowed (feature for project settings?)
+ // if (!$fileType->isValid($file['tmp_name'])) {
+ // throw new Exception('File type not allowed', 400);
+ // }
+
+ if (!$fileSize->isValid($file['size'])) { // Check if file size is exceeding allowed limit
+ throw new Exception('File size not allowed', 400);
+ }
+
+ if (!$upload->isValid($file['tmp_name'])) {
+ throw new Exception('Invalid file', 403);
+ }
+
+ // Save to storage
+ $size = $device->getFileSize($file['tmp_name']);
+ $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION));
+
+ if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
+ throw new Exception('Failed moving file', 500);
+ }
+
+ $tag = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_TAGS,
+ '$permissions' => [
+ 'read' => [],
+ 'write' => [],
+ ],
+ 'dateCreated' => time(),
+ 'functionId' => $function->getId(),
+ 'command' => $command,
+ 'codePath' => $path,
+ 'codeSize' => $size,
+ ]);
+
+ if (false === $tag) {
+ throw new Exception('Failed saving tag to DB', 500);
+ }
+
+ $usage
+ ->setParam('storage', $tag->getAttribute('codeSize', 0))
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($tag->getArrayCopy())
+ ;
+ }, ['request', 'response', 'projectDB', 'usage']);
+
+App::get('/v1/functions/:functionId/tags')
+ ->groups(['api', 'functions'])
+ ->desc('List Tags')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'listTags')
+ ->label('sdk.description', '/docs/references/functions/list-tags.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'dateCreated',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_TAGS,
+ 'functionId='.$function->getId(),
+ ],
+ ]);
+
+ $response->json(['sum' => $projectDB->getSum(), 'tags' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/functions/:functionId/tags/:tagId')
+ ->groups(['api', 'functions'])
+ ->desc('Get Tag')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'getTag')
+ ->label('sdk.description', '/docs/references/functions/get-tag.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('tagId', '', new UID(), 'Tag unique ID.')
+ ->action(function ($functionId, $tagId, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $tag = $projectDB->getDocument($tagId);
+
+ if($tag->getAttribute('functionId') !== $function->getId()) {
+ throw new Exception('Tag not found', 404);
+ }
+
+ if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) {
+ throw new Exception('Tag not found', 404);
+ }
+
+ $response->json($tag->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/functions/:functionId/tags/:tagId')
+ ->groups(['api', 'functions'])
+ ->desc('Delete Tag')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'deleteTag')
+ ->label('sdk.description', '/docs/references/functions/delete-tag.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('tagId', '', new UID(), 'Tag unique ID.')
+ ->action(function ($functionId, $tagId, $response, $projectDB, $usage) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $tag = $projectDB->getDocument($tagId);
+
+ if($tag->getAttribute('functionId') !== $function->getId()) {
+ throw new Exception('Tag not found', 404);
+ }
+
+ if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) {
+ throw new Exception('Tag not found', 404);
+ }
+
+ $device = Storage::getDevice('functions');
+
+ if ($device->delete($tag->getAttribute('codePath', ''))) {
+ if (!$projectDB->deleteDocument($tag->getId())) {
+ throw new Exception('Failed to remove tag from DB', 500);
+ }
+ }
+
+ $usage
+ ->setParam('storage', $tag->getAttribute('codeSize', 0) * -1)
+ ;
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'usage']);
+
+App::post('/v1/functions/:functionId/executions')
+ ->groups(['api', 'functions'])
+ ->desc('Create Execution')
+ ->label('scope', 'functions.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'createExecution')
+ ->label('sdk.description', '/docs/references/functions/create-execution.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('async', 1, new Range(0, 1), 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true)
+ ->action(function ($functionId, $async, $response, $project, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Database $projectDB */
+
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $tag = $projectDB->getDocument($function->getAttribute('tag'));
+
+ if($tag->getAttribute('functionId') !== $function->getId()) {
+ throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
+ }
+
+ if (empty($tag->getId()) || Database::SYSTEM_COLLECTION_TAGS != $tag->getCollection()) {
+ throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
+ }
+
+ $execution = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_EXECUTIONS,
+ '$permissions' => [
+ 'read' => [],
+ 'write' => [],
+ ],
+ 'dateCreated' => time(),
+ 'functionId' => $function->getId(),
+ 'status' => 'waiting', // waiting / processing / completed / failed
+ 'exitCode' => 0,
+ 'stdout' => '',
+ 'stderr' => '',
+ 'time' => 0,
+ ]);
+
+ if (false === $execution) {
+ throw new Exception('Failed saving execution to DB', 500);
+ }
+
+ if((bool)$async) {
+ // Issue a TLS certificate when domain is verified
+ Resque::enqueue('v1-functions', 'FunctionsV1', [
+ 'projectId' => $project->getId(),
+ 'functionId' => $function->getId(),
+ 'executionId' => $execution->getId(),
+ 'functionTag' => $tag->getId(),
+ 'functionTrigger' => 'API',
+ ]);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($execution->getArrayCopy())
+ ;
+ }, ['response', 'project', 'projectDB']);
+
+App::get('/v1/functions/:functionId/executions')
+ ->groups(['api', 'functions'])
+ ->desc('List Executions')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'listExecutions')
+ ->label('sdk.description', '/docs/references/functions/list-executions.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($functionId, $search, $limit, $offset, $orderType, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'dateCreated',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS,
+ 'functionId='.$function->getId(),
+ ],
+ ]);
+
+ $response->json(['sum' => $projectDB->getSum(), 'executions' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/functions/:functionId/executions/:executionId')
+ ->groups(['api', 'functions'])
+ ->desc('Get Execution')
+ ->label('scope', 'functions.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'functions')
+ ->label('sdk.method', 'getExecution')
+ ->label('sdk.description', '/docs/references/functions/get-execution.md')
+ ->param('functionId', '', new UID(), 'Function unique ID.')
+ ->param('executionId', '', new UID(), 'Execution unique ID.')
+ ->action(function ($functionId, $executionId, $response, $projectDB) {
+ $function = $projectDB->getDocument($functionId);
+
+ if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
+ throw new Exception('Function not found', 404);
+ }
+
+ $execution = $projectDB->getDocument($executionId);
+
+ if($execution->getAttribute('functionId') !== $function->getId()) {
+ throw new Exception('Execution not found', 404);
+ }
+
+ if (empty($execution->getId()) || Database::SYSTEM_COLLECTION_EXECUTIONS != $execution->getCollection()) {
+ throw new Exception('Execution not found', 404);
+ }
+
+ $response->json($execution->getArrayCopy());
+ }, ['response', 'projectDB']);
\ No newline at end of file
diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php
index 0eecb23e15..cf72731c6a 100644
--- a/app/controllers/api/graphql.php
+++ b/app/controllers/api/graphql.php
@@ -1,6 +1,6 @@
post('/v1/graphql')
+App::post('/v1/graphql')
->desc('GraphQL Endpoint')
->groups(['api', 'graphql'])
->label('scope', 'public')
diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php
index 76727563ae..5ef4f2b9c8 100644
--- a/app/controllers/api/health.php
+++ b/app/controllers/api/health.php
@@ -1,13 +1,12 @@
get('/v1/health')
+App::get('/v1/health')
->desc('Get HTTP')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -15,23 +14,23 @@ $utopia->get('/v1/health')
->label('sdk.namespace', 'health')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/health/get.md')
- ->action(
- function () use ($response) {
- $response->json(['status' => 'OK']);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/version')
+ $response->json(['status' => 'OK']);
+ }, ['response']);
+
+App::get('/v1/health/version')
->desc('Get Version')
->groups(['api', 'health'])
->label('scope', 'public')
- ->action(
- function () use ($response) {
- $response->json(['version' => APP_VERSION_STABLE]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/db')
+ $response->json(['version' => APP_VERSION_STABLE]);
+ }, ['response']);
+
+App::get('/v1/health/db')
->desc('Get DB')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -39,15 +38,16 @@ $utopia->get('/v1/health/db')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getDB')
->label('sdk.description', '/docs/references/health/get-db.md')
- ->action(
- function () use ($response, $register) {
- $register->get('db'); /* @var $db PDO */
+ ->action(function ($response, $register) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Registry\Registry $register */
- $response->json(['status' => 'OK']);
- }
- );
+ $register->get('db'); /* @var $db PDO */
-$utopia->get('/v1/health/cache')
+ $response->json(['status' => 'OK']);
+ }, ['response', 'register']);
+
+App::get('/v1/health/cache')
->desc('Get Cache')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -55,15 +55,15 @@ $utopia->get('/v1/health/cache')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getCache')
->label('sdk.description', '/docs/references/health/get-cache.md')
- ->action(
- function () use ($response, $register) {
- $register->get('cache'); /* @var $cache Predis\Client */
+ ->action(function ($response, $register) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Registry\Registry $register */
+ $register->get('cache'); /* @var $cache Predis\Client */
- $response->json(['status' => 'OK']);
- }
- );
+ $response->json(['status' => 'OK']);
+ }, ['response']);
-$utopia->get('/v1/health/time')
+App::get('/v1/health/time')
->desc('Get Time')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -71,47 +71,47 @@ $utopia->get('/v1/health/time')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getTime')
->label('sdk.description', '/docs/references/health/get-time.md')
- ->action(
- function () use ($response) {
- /*
- * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server
- */
- $host = 'time.google.com'; // https://developers.google.com/time/
- $gap = 60; // Allow [X] seconds gap
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
- /* Create a socket and connect to NTP server */
- $sock = \socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ /*
+ * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server
+ */
+ $host = 'time.google.com'; // https://developers.google.com/time/
+ $gap = 60; // Allow [X] seconds gap
- \socket_connect($sock, $host, 123);
+ /* Create a socket and connect to NTP server */
+ $sock = \socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- /* Send request */
- $msg = "\010".\str_repeat("\0", 47);
+ \socket_connect($sock, $host, 123);
- \socket_send($sock, $msg, \strlen($msg), 0);
+ /* Send request */
+ $msg = "\010".\str_repeat("\0", 47);
- /* Receive response and close socket */
- \socket_recv($sock, $recv, 48, MSG_WAITALL);
- \socket_close($sock);
+ \socket_send($sock, $msg, \strlen($msg), 0);
- /* Interpret response */
- $data = \unpack('N12', $recv);
- $timestamp = \sprintf('%u', $data[9]);
+ /* Receive response and close socket */
+ \socket_recv($sock, $recv, 48, MSG_WAITALL);
+ \socket_close($sock);
- /* NTP is number of seconds since 0000 UT on 1 January 1900
- Unix time is seconds since 0000 UT on 1 January 1970 */
- $timestamp -= 2208988800;
+ /* Interpret response */
+ $data = \unpack('N12', $recv);
+ $timestamp = \sprintf('%u', $data[9]);
- $diff = ($timestamp - \time());
+ /* NTP is number of seconds since 0000 UT on 1 January 1900
+ Unix time is seconds since 0000 UT on 1 January 1970 */
+ $timestamp -= 2208988800;
- if ($diff > $gap || $diff < ($gap * -1)) {
- throw new Exception('Server time gaps detected');
- }
+ $diff = ($timestamp - \time());
- $response->json(['remote' => $timestamp, 'local' => \time(), 'diff' => $diff]);
+ if ($diff > $gap || $diff < ($gap * -1)) {
+ throw new Exception('Server time gaps detected');
}
- );
-$utopia->get('/v1/health/queue/webhooks')
+ $response->json(['remote' => $timestamp, 'local' => \time(), 'diff' => $diff]);
+ }, ['response']);
+
+App::get('/v1/health/queue/webhooks')
->desc('Get Webhooks Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -119,13 +119,13 @@ $utopia->get('/v1/health/queue/webhooks')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueWebhooks')
->label('sdk.description', '/docs/references/health/get-queue-webhooks.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-webhooks')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/queue/tasks')
+ $response->json(['size' => Resque::size('v1-webhooks')]);
+ }, ['response']);
+
+App::get('/v1/health/queue/tasks')
->desc('Get Tasks Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -133,13 +133,13 @@ $utopia->get('/v1/health/queue/tasks')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueTasks')
->label('sdk.description', '/docs/references/health/get-queue-tasks.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-tasks')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/queue/logs')
+ $response->json(['size' => Resque::size('v1-tasks')]);
+ }, ['response']);
+
+App::get('/v1/health/queue/logs')
->desc('Get Logs Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -147,13 +147,13 @@ $utopia->get('/v1/health/queue/logs')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueLogs')
->label('sdk.description', '/docs/references/health/get-queue-logs.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-audit')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/queue/usage')
+ $response->json(['size' => Resque::size('v1-audit')]);
+ }, ['response']);
+
+App::get('/v1/health/queue/usage')
->desc('Get Usage Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -161,13 +161,13 @@ $utopia->get('/v1/health/queue/usage')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueUsage')
->label('sdk.description', '/docs/references/health/get-queue-usage.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-usage')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/queue/certificates')
+ $response->json(['size' => Resque::size('v1-usage')]);
+ }, ['response']);
+
+App::get('/v1/health/queue/certificates')
->desc('Get Certificate Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -175,13 +175,13 @@ $utopia->get('/v1/health/queue/certificates')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueCertificates')
->label('sdk.description', '/docs/references/health/get-queue-certificates.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-certificates')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/queue/functions')
+ $response->json(['size' => Resque::size('v1-certificates')]);
+ }, ['response']);
+
+App::get('/v1/health/queue/functions')
->desc('Get Functions Queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -189,13 +189,13 @@ $utopia->get('/v1/health/queue/functions')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getQueueFunctions')
->label('sdk.description', '/docs/references/health/get-queue-functions.md')
- ->action(
- function () use ($response) {
- $response->json(['size' => Resque::size('v1-functions')]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/v1/health/storage/local')
+ $response->json(['size' => Resque::size('v1-functions')]);
+ }, ['response']);
+
+App::get('/v1/health/storage/local')
->desc('Get Local Storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -203,30 +203,30 @@ $utopia->get('/v1/health/storage/local')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getStorageLocal')
->label('sdk.description', '/docs/references/health/get-storage-local.md')
- ->action(
- function () use ($response) {
- foreach ([
- 'Uploads' => APP_STORAGE_UPLOADS,
- 'Cache' => APP_STORAGE_CACHE,
- 'Config' => APP_STORAGE_CONFIG,
- 'Certs' => APP_STORAGE_CERTIFICATES
- ] as $key => $volume) {
- $device = new Local($volume);
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
- if (!\is_readable($device->getRoot())) {
- throw new Exception('Device '.$key.' dir is not readable');
- }
+ foreach ([
+ 'Uploads' => APP_STORAGE_UPLOADS,
+ 'Cache' => APP_STORAGE_CACHE,
+ 'Config' => APP_STORAGE_CONFIG,
+ 'Certs' => APP_STORAGE_CERTIFICATES
+ ] as $key => $volume) {
+ $device = new Local($volume);
- if (!\is_writable($device->getRoot())) {
- throw new Exception('Device '.$key.' dir is not writable');
- }
+ if (!\is_readable($device->getRoot())) {
+ throw new Exception('Device '.$key.' dir is not readable');
}
- $response->json(['status' => 'OK']);
+ if (!\is_writable($device->getRoot())) {
+ throw new Exception('Device '.$key.' dir is not writable');
+ }
}
- );
-$utopia->get('/v1/health/anti-virus')
+ $response->json(['status' => 'OK']);
+ }, ['response']);
+
+App::get('/v1/health/anti-virus')
->desc('Get Anti virus')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -234,22 +234,22 @@ $utopia->get('/v1/health/anti-virus')
->label('sdk.namespace', 'health')
->label('sdk.method', 'getAntiVirus')
->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md')
- ->action(
- function () use ($request, $response) {
- if ($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled
- throw new Exception('Anitvirus is disabled');
- }
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
- $antiVirus = new Network('clamav', 3310);
-
- $response->json([
- 'status' => (@$antiVirus->ping()) ? 'online' : 'offline',
- 'version' => @$antiVirus->version(),
- ]);
+ if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled
+ throw new Exception('Anitvirus is disabled');
}
- );
-$utopia->get('/v1/health/stats') // Currently only used internally
+ $antiVirus = new Network('clamav', 3310);
+
+ $response->json([
+ 'status' => (@$antiVirus->ping()) ? 'online' : 'offline',
+ 'version' => @$antiVirus->version(),
+ ]);
+ }, ['response']);
+
+App::get('/v1/health/stats') // Currently only used internally
->desc('Get System Stats')
->groups(['api', 'health'])
->label('scope', 'god')
@@ -257,34 +257,35 @@ $utopia->get('/v1/health/stats') // Currently only used internally
// ->label('sdk.namespace', 'health')
// ->label('sdk.method', 'getStats')
->label('docs', false)
- ->action(
- function () use ($response, $register) {
- $device = Storage::getDevice('local');
- $cache = $register->get('cache');
+ ->action(function ($response, $register) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Registry\Registry $register */
- $cacheStats = $cache->info();
+ $device = Storage::getDevice('files');
+ $cache = $register->get('cache');
- $response
- ->json([
- 'server' => [
- 'name' => 'nginx',
- 'version' => \shell_exec('nginx -v 2>&1'),
- ],
- 'storage' => [
- 'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')),
- 'partitionTotal' => Storage::human($device->getPartitionTotalSpace()),
- 'partitionFree' => Storage::human($device->getPartitionFreeSpace()),
- ],
- 'cache' => [
- 'uptime' => (isset($cacheStats['uptime_in_seconds'])) ? $cacheStats['uptime_in_seconds'] : 0,
- 'clients' => (isset($cacheStats['connected_clients'])) ? $cacheStats['connected_clients'] : 0,
- 'hits' => (isset($cacheStats['keyspace_hits'])) ? $cacheStats['keyspace_hits'] : 0,
- 'misses' => (isset($cacheStats['keyspace_misses'])) ? $cacheStats['keyspace_misses'] : 0,
- 'memory_used' => (isset($cacheStats['used_memory'])) ? $cacheStats['used_memory'] : 0,
- 'memory_used_human' => (isset($cacheStats['used_memory_human'])) ? $cacheStats['used_memory_human'] : 0,
- 'memory_used_peak' => (isset($cacheStats['used_memory_peak'])) ? $cacheStats['used_memory_peak'] : 0,
- 'memory_used_peak_human' => (isset($cacheStats['used_memory_peak_human'])) ? $cacheStats['used_memory_peak_human'] : 0,
- ],
- ]);
- }
- );
+ $cacheStats = $cache->info();
+
+ $response
+ ->json([
+ 'server' => [
+ 'name' => 'nginx',
+ 'version' => \shell_exec('nginx -v 2>&1'),
+ ],
+ 'storage' => [
+ 'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')),
+ 'partitionTotal' => Storage::human($device->getPartitionTotalSpace()),
+ 'partitionFree' => Storage::human($device->getPartitionFreeSpace()),
+ ],
+ 'cache' => [
+ 'uptime' => $cacheStats['uptime_in_seconds'] ?? 0,
+ 'clients' => $cacheStats['connected_clients'] ?? 0,
+ 'hits' => $cacheStats['keyspace_hits'] ?? 0,
+ 'misses' => $cacheStats['keyspace_misses'] ?? 0,
+ 'memory_used' => $cacheStats['used_memory'] ?? 0,
+ 'memory_used_human' => $cacheStats['used_memory_human'] ?? 0,
+ 'memory_used_peak' => $cacheStats['used_memory_peak'] ?? 0,
+ 'memory_used_peak_human' => $cacheStats['used_memory_peak_human'] ?? 0,
+ ],
+ ]);
+ }, ['response', 'register']);
diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php
index 148ee3e8e9..8be46d8e47 100644
--- a/app/controllers/api/locale.php
+++ b/app/controllers/api/locale.php
@@ -1,12 +1,9 @@
get('/v1/locale')
+App::get('/v1/locale')
->desc('Get User Locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -14,58 +11,56 @@ $utopia->get('/v1/locale')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/locale/get-locale.md')
- ->action(
- function () use ($response, $request, $utopia) {
- $eu = include __DIR__.'/../../config/eu.php';
- $currencies = include __DIR__.'/../../config/currencies.php';
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $output = [];
- $ip = $request->getIP();
- $time = (60 * 60 * 24 * 45); // 45 days cache
- $countries = Locale::getText('countries');
- $continents = Locale::getText('continents');
+ ->action(function ($request, $response, $locale, $geodb) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var GeoIp2\Database\Reader $geodb */
- if (App::MODE_TYPE_PRODUCTION !== $utopia->getMode()) {
- $ip = '79.177.241.94';
- }
+ $eu = Config::getParam('locale-eu');
+ $currencies = Config::getParam('locale-currencies');
+ $output = [];
+ $ip = $request->getIP();
+ $time = (60 * 60 * 24 * 45); // 45 days cache
+ $countries = $locale->getText('countries');
+ $continents = $locale->getText('continents');
+
+ $output['ip'] = $ip;
- $output['ip'] = $ip;
+ $currency = null;
- $currency = null;
+ try {
+ $record = $geodb->country($ip);
+ $output['countryCode'] = $record->country->isoCode;
+ $output['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown');
+ //$output['countryTimeZone'] = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $record->country->isoCode);
+ $output['continent'] = (isset($continents[$record->continent->code])) ? $continents[$record->continent->code] : $locale->getText('locale.country.unknown');
+ $output['continentCode'] = $record->continent->code;
+ $output['eu'] = (\in_array($record->country->isoCode, $eu)) ? true : false;
- try {
- $record = $reader->country($ip);
- $output['countryCode'] = $record->country->isoCode;
- $output['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- //$output['countryTimeZone'] = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $record->country->isoCode);
- $output['continent'] = (isset($continents[$record->continent->code])) ? $continents[$record->continent->code] : Locale::getText('locale.country.unknown');
- $output['continentCode'] = $record->continent->code;
- $output['eu'] = (\in_array($record->country->isoCode, $eu)) ? true : false;
-
- foreach ($currencies as $code => $element) {
- if (isset($element['locations']) && isset($element['code']) && \in_array($record->country->isoCode, $element['locations'])) {
- $currency = $element['code'];
- }
+ foreach ($currencies as $code => $element) {
+ if (isset($element['locations']) && isset($element['code']) && \in_array($record->country->isoCode, $element['locations'])) {
+ $currency = $element['code'];
}
-
- $output['currency'] = $currency;
- } catch (\Exception $e) {
- $output['countryCode'] = '--';
- $output['country'] = Locale::getText('locale.country.unknown');
- $output['continent'] = Locale::getText('locale.country.unknown');
- $output['continentCode'] = '--';
- $output['eu'] = false;
- $output['currency'] = $currency;
}
- $response
- ->addHeader('Cache-Control', 'public, max-age='.$time)
- ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
- ->json($output);
+ $output['currency'] = $currency;
+ } catch (\Exception $e) {
+ $output['countryCode'] = '--';
+ $output['country'] = $locale->getText('locale.country.unknown');
+ $output['continent'] = $locale->getText('locale.country.unknown');
+ $output['continentCode'] = '--';
+ $output['eu'] = false;
+ $output['currency'] = $currency;
}
- );
-$utopia->get('/v1/locale/countries')
+ $response
+ ->addHeader('Cache-Control', 'public, max-age='.$time)
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
+ ->json($output);
+ }, ['request', 'response', 'locale', 'geodb']);
+
+App::get('/v1/locale/countries')
->desc('List Countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -73,17 +68,18 @@ $utopia->get('/v1/locale/countries')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountries')
->label('sdk.description', '/docs/references/locale/get-countries.md')
- ->action(
- function () use ($response) {
- $list = Locale::getText('countries'); /* @var $list array */
+ ->action(function ($response, $locale) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Locale\Locale $locale */
- \asort($list);
+ $list = $locale->getText('countries'); /* @var $list array */
- $response->json($list);
- }
- );
+ \asort($list);
-$utopia->get('/v1/locale/countries/eu')
+ $response->json($list);
+ }, ['response', 'locale']);
+
+App::get('/v1/locale/countries/eu')
->desc('List EU Countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -91,25 +87,26 @@ $utopia->get('/v1/locale/countries/eu')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountriesEU')
->label('sdk.description', '/docs/references/locale/get-countries-eu.md')
- ->action(
- function () use ($response) {
- $countries = Locale::getText('countries'); /* @var $countries array */
- $eu = include __DIR__.'/../../config/eu.php';
- $list = [];
+ ->action(function ($response, $locale) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Locale\Locale $locale */
- foreach ($eu as $code) {
- if (\array_key_exists($code, $countries)) {
- $list[$code] = $countries[$code];
- }
+ $countries = $locale->getText('countries'); /* @var $countries array */
+ $eu = Config::getParam('locale-eu');
+ $list = [];
+
+ foreach ($eu as $code) {
+ if (\array_key_exists($code, $countries)) {
+ $list[$code] = $countries[$code];
}
-
- \asort($list);
-
- $response->json($list);
}
- );
-$utopia->get('/v1/locale/countries/phones')
+ \asort($list);
+
+ $response->json($list);
+ }, ['response', 'locale']);
+
+App::get('/v1/locale/countries/phones')
->desc('List Countries Phone Codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -117,25 +114,26 @@ $utopia->get('/v1/locale/countries/phones')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCountriesPhones')
->label('sdk.description', '/docs/references/locale/get-countries-phones.md')
- ->action(
- function () use ($response) {
- $list = include __DIR__.'/../../config/phones.php'; /* @var $list array */
+ ->action(function ($response, $locale) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Locale\Locale $locale */
- $countries = Locale::getText('countries'); /* @var $countries array */
+ $list = Config::getParam('locale-phones'); /* @var $list array */
- foreach ($list as $code => $name) {
- if (\array_key_exists($code, $countries)) {
- $list[$code] = '+'.$list[$code];
- }
+ $countries = $locale->getText('countries'); /* @var $countries array */
+
+ foreach ($list as $code => $name) {
+ if (\array_key_exists($code, $countries)) {
+ $list[$code] = '+'.$list[$code];
}
-
- \asort($list);
-
- $response->json($list);
}
- );
-$utopia->get('/v1/locale/continents')
+ \asort($list);
+
+ $response->json($list);
+ }, ['response', 'locale']);
+
+App::get('/v1/locale/continents')
->desc('List Continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -143,18 +141,18 @@ $utopia->get('/v1/locale/continents')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getContinents')
->label('sdk.description', '/docs/references/locale/get-continents.md')
- ->action(
- function () use ($response) {
- $list = Locale::getText('continents'); /* @var $list array */
+ ->action(function ($response, $locale) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Locale\Locale $locale */
- \asort($list);
+ $list = $locale->getText('continents'); /* @var $list array */
- $response->json($list);
- }
- );
+ \asort($list);
+ $response->json($list);
+ }, ['response', 'locale']);
-$utopia->get('/v1/locale/currencies')
+App::get('/v1/locale/currencies')
->desc('List Currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -162,16 +160,16 @@ $utopia->get('/v1/locale/currencies')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getCurrencies')
->label('sdk.description', '/docs/references/locale/get-currencies.md')
- ->action(
- function () use ($response) {
- $currencies = include __DIR__.'/../../config/currencies.php';
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
- $response->json($currencies);
- }
- );
+ $currencies = Config::getParam('locale-currencies');
+
+ $response->json($currencies);
+ }, ['response']);
-$utopia->get('/v1/locale/languages')
+App::get('/v1/locale/languages')
->desc('List Languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -179,10 +177,10 @@ $utopia->get('/v1/locale/languages')
->label('sdk.namespace', 'locale')
->label('sdk.method', 'getLanguages')
->label('sdk.description', '/docs/references/locale/get-languages.md')
- ->action(
- function () use ($response) {
- $languages = include __DIR__.'/../../config/languages.php';
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
- $response->json($languages);
- }
- );
\ No newline at end of file
+ $languages = Config::getParam('locale-languages');
+
+ $response->json($languages);
+ }, ['response']);
\ No newline at end of file
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index cf81df66ba..3f62a66c6a 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -1,7 +1,6 @@
post('/v1/projects')
+App::post('/v1/projects')
->desc('Create Project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'create')
- ->param('name', null, function () { return new Text(100); }, 'Project name.')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('description', '', function () { return new Text(255); }, 'Project description.', true)
- ->param('logo', '', function () { return new Text(1024); }, 'Project logo.', true)
- ->param('url', '', function () { return new URL(); }, 'Project URL.', true)
- ->param('legalName', '', function () { return new Text(256); }, 'Project legal Name.', true)
- ->param('legalCountry', '', function () { return new Text(256); }, 'Project legal Country.', true)
- ->param('legalState', '', function () { return new Text(256); }, 'Project legal State.', true)
- ->param('legalCity', '', function () { return new Text(256); }, 'Project legal City.', true)
- ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal Address.', true)
- ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal Tax ID.', true)
- ->action(
- function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $user, $consoleDB, $projectDB) {
- $team = $projectDB->getDocument($teamId);
+ ->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
+ ->param('logo', '', new Text(1024), 'Project logo.', true)
+ ->param('url', '', new URL(), 'Project URL.', true)
+ ->param('legalName', '', new Text(256), 'Project legal Name. Max length: 256 chars.', true)
+ ->param('legalCountry', '', new Text(256), 'Project legal Country. Max length: 256 chars.', true)
+ ->param('legalState', '', new Text(256), 'Project legal State. Max length: 256 chars.', true)
+ ->param('legalCity', '', new Text(256), 'Project legal City. Max length: 256 chars.', true)
+ ->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true)
+ ->param('legalTaxId', '', new Text(256), 'Project legal Tax ID. Max length: 256 chars.', true)
+ ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
- $project = $consoleDB->createDocument(
- [
- '$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
- '$permissions' => [
- 'read' => ['team:'.$teamId],
- 'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'],
- ],
- 'name' => $name,
- 'description' => $description,
- 'logo' => $logo,
- 'url' => $url,
- 'legalName' => $legalName,
- 'legalCountry' => $legalCountry,
- 'legalState' => $legalState,
- 'legalCity' => $legalCity,
- 'legalAddress' => $legalAddress,
- 'legalTaxId' => $legalTaxId,
- 'teamId' => $team->getId(),
- 'platforms' => [],
- 'webhooks' => [],
- 'keys' => [],
- 'tasks' => [],
- ]
- );
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $consoleDB->createNamespace($project->getId());
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($project->getArrayCopy())
- ;
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
}
- );
-$utopia->get('/v1/projects')
- ->desc('List Projects')
- ->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
- ->label('sdk.namespace', 'projects')
- ->label('sdk.method', 'list')
- ->action(
- function () use ($request, $response, $consoleDB) {
- $results = $consoleDB->getCollection([
- 'limit' => 20,
- 'offset' => 0,
- 'orderField' => 'name',
- 'orderType' => 'ASC',
- 'orderCast' => 'string',
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_PROJECTS,
+ $project = $consoleDB->createDocument(
+ [
+ '$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
+ '$permissions' => [
+ 'read' => ['team:'.$teamId],
+ 'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'],
],
- ]);
-
- foreach ($results as $project) {
- foreach (Config::getParam('providers') as $provider => $node) {
- $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true);
-
- if (!empty($secret) && isset($secret['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']);
- $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag'])));
- }
- }
- }
-
- $response->json($results);
- }
- );
-
-$utopia->get('/v1/projects/:projectId')
- ->desc('Get Project')
- ->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
- ->label('sdk.namespace', 'projects')
- ->label('sdk.method', 'get')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
-
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
-
- foreach (Config::getParam('providers') as $provider => $node) {
- $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true);
-
- if (!empty($secret) && isset($secret['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']);
- $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag'])));
- }
- }
-
- $response->json($project->getArrayCopy());
- }
- );
-
-$utopia->get('/v1/projects/:projectId/usage')
- ->desc('Get Project')
- ->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
- ->label('sdk.namespace', 'projects')
- ->label('sdk.method', 'getUsage')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->param('range', 'last30', function () { return new WhiteList(['daily', 'monthly', 'last30', 'last90']); }, 'Date range.', true)
- ->action(
- function ($projectId, $range) use ($response, $consoleDB, $projectDB, $register) {
- $project = $consoleDB->getDocument($projectId);
-
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
-
- $period = [
- 'daily' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('today')),
- 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
- 'group' => '1m',
- ],
- 'monthly' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')),
- 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')),
- 'group' => '1d',
- ],
- 'last30' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
- 'group' => '1d',
- ],
- 'last90' => [
- 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
- 'end' => DateTime::createFromFormat('U', \strtotime('today')),
- 'group' => '1d',
- ],
- // 'yearly' => [
- // 'start' => DateTime::createFromFormat('U', strtotime('midnight first day of january')),
- // 'end' => DateTime::createFromFormat('U', strtotime('midnight last day of december')),
- // 'group' => '4w',
- // ],
- ];
-
- $client = $register->get('influxdb');
-
- $requests = [];
- $network = [];
-
- if ($client) {
- $start = $period[$range]['start']->format(DateTime::RFC3339);
- $end = $period[$range]['end']->format(DateTime::RFC3339);
- $database = $client->selectDB('telegraf');
-
- // Requests
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $requests[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
-
- // Network
- $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
- $points = $result->getPoints();
-
- foreach ($points as $point) {
- $network[] = [
- 'value' => (!empty($point['value'])) ? $point['value'] : 0,
- 'date' => \strtotime($point['time']),
- ];
- }
- }
-
- // Users
-
- $projectDB->getCollection([
- 'limit' => 0,
- 'offset' => 0,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- ],
- ]);
-
- $usersTotal = $projectDB->getSum();
-
- // Documents
-
- $collections = $projectDB->getCollection([
- 'limit' => 100,
- 'offset' => 0,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
- ],
- ]);
-
- $collectionsTotal = $projectDB->getSum();
-
- $documents = [];
-
- foreach ($collections as $collection) {
- $result = $projectDB->getCollection([
- 'limit' => 0,
- 'offset' => 0,
- 'filters' => [
- '$collection='.$collection['$id'],
- ],
- ]);
-
- $documents[] = ['name' => $collection['name'], 'total' => $projectDB->getSum()];
- }
-
- // Tasks
- $tasksTotal = \count($project->getAttribute('tasks', []));
-
- $response->json([
- 'requests' => [
- 'data' => $requests,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['value'];
- }, $requests)),
- ],
- 'network' => [
- 'data' => $network,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['value'];
- }, $network)),
- ],
- 'collections' => [
- 'data' => $collections,
- 'total' => $collectionsTotal,
- ],
- 'documents' => [
- 'data' => $documents,
- 'total' => \array_sum(\array_map(function ($item) {
- return $item['total'];
- }, $documents)),
- ],
- 'users' => [
- 'data' => [],
- 'total' => $usersTotal,
- ],
- 'tasks' => [
- 'data' => [],
- 'total' => $tasksTotal,
- ],
- 'storage' => [
- 'total' => $projectDB->getCount(
- [
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_FILES,
- ],
- ]
- ),
- ],
- ]);
- }
- );
-
-$utopia->patch('/v1/projects/:projectId')
- ->desc('Update Project')
- ->groups(['api', 'projects'])
- ->label('scope', 'projects.write')
- ->label('sdk.namespace', 'projects')
- ->label('sdk.method', 'update')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->param('name', null, function () { return new Text(100); }, 'Project name.')
- ->param('description', '', function () { return new Text(255); }, 'Project description.', true)
- ->param('logo', '', function () { return new Text(1024); }, 'Project logo.', true)
- ->param('url', '', function () { return new URL(); }, 'Project URL.', true)
- ->param('legalName', '', function () { return new Text(256); }, 'Project legal name.', true)
- ->param('legalCountry', '', function () { return new Text(256); }, 'Project legal country..', true)
- ->param('legalState', '', function () { return new Text(256); }, 'Project legal state.', true)
- ->param('legalCity', '', function () { return new Text(256); }, 'Project legal city.', true)
- ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal address.', true)
- ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal tax ID.', true)
- ->action(
- function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
-
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
-
- $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
'name' => $name,
'description' => $description,
'logo' => $logo,
@@ -346,1107 +65,1325 @@ $utopia->patch('/v1/projects/:projectId')
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => $legalTaxId,
- ]));
+ 'teamId' => $team->getId(),
+ 'platforms' => [],
+ 'webhooks' => [],
+ 'keys' => [],
+ 'tasks' => [],
+ ]
+ );
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $consoleDB->createNamespace($project->getId());
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($project->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB', 'projectDB']);
+
+App::get('/v1/projects')
+ ->desc('List Projects')
+ ->groups(['api', 'projects'])
+ ->label('scope', 'projects.read')
+ ->label('sdk.namespace', 'projects')
+ ->label('sdk.method', 'list')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+
+ $results = $consoleDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'registration',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_PROJECTS,
+ ],
+ ]);
+
+ $response->json(['sum' => $consoleDB->getSum(), 'projects' => $results]);
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId')
+ ->desc('Get Project')
+ ->groups(['api', 'projects'])
+ ->label('scope', 'projects.read')
+ ->label('sdk.namespace', 'projects')
+ ->label('sdk.method', 'get')
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+
+ $project = $consoleDB->getDocument($projectId);
+
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
+ }
+
+ $response->json($project->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/usage')
+ ->desc('Get Project')
+ ->groups(['api', 'projects'])
+ ->label('scope', 'projects.read')
+ ->label('sdk.namespace', 'projects')
+ ->label('sdk.method', 'getUsage')
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->param('range', 'last30', new WhiteList(['daily', 'monthly', 'last30', 'last90'], true), 'Date range.', true)
+ ->action(function ($projectId, $range, $response, $consoleDB, $projectDB, $register) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Utopia\Registry\Registry $register */
+
+ $project = $consoleDB->getDocument($projectId);
+
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
+ }
+
+ $period = [
+ 'daily' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('today')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
+ 'group' => '1m',
+ ],
+ 'monthly' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')),
+ 'group' => '1d',
+ ],
+ 'last30' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')),
+ 'group' => '1d',
+ ],
+ 'last90' => [
+ 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')),
+ 'end' => DateTime::createFromFormat('U', \strtotime('today')),
+ 'group' => '1d',
+ ],
+ // 'yearly' => [
+ // 'start' => DateTime::createFromFormat('U', strtotime('midnight first day of january')),
+ // 'end' => DateTime::createFromFormat('U', strtotime('midnight last day of december')),
+ // 'group' => '4w',
+ // ],
+ ];
+
+ $client = $register->get('influxdb');
+
+ $requests = [];
+ $network = [];
+
+ if ($client) {
+ $start = $period[$range]['start']->format(DateTime::RFC3339);
+ $end = $period[$range]['end']->format(DateTime::RFC3339);
+ $database = $client->selectDB('telegraf');
+
+ // Requests
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
+
+ foreach ($points as $point) {
+ $requests[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
}
- $response->json($project->getArrayCopy());
- }
- );
+ // Network
+ $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)');
+ $points = $result->getPoints();
-$utopia->patch('/v1/projects/:projectId/oauth2')
+ foreach ($points as $point) {
+ $network[] = [
+ 'value' => (!empty($point['value'])) ? $point['value'] : 0,
+ 'date' => \strtotime($point['time']),
+ ];
+ }
+ }
+
+ // Users
+
+ $projectDB->getCollection([
+ 'limit' => 0,
+ 'offset' => 0,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ ],
+ ]);
+
+ $usersTotal = $projectDB->getSum();
+
+ // Documents
+
+ $collections = $projectDB->getCollection([
+ 'limit' => 100,
+ 'offset' => 0,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
+ ],
+ ]);
+
+ $collectionsTotal = $projectDB->getSum();
+
+ $documents = [];
+
+ foreach ($collections as $collection) {
+ $result = $projectDB->getCollection([
+ 'limit' => 0,
+ 'offset' => 0,
+ 'filters' => [
+ '$collection='.$collection['$id'],
+ ],
+ ]);
+
+ $documents[] = ['name' => $collection['name'], 'total' => $projectDB->getSum()];
+ }
+
+ // Tasks
+ $tasksTotal = \count($project->getAttribute('tasks', []));
+
+ $response->json([
+ 'requests' => [
+ 'data' => $requests,
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['value'];
+ }, $requests)),
+ ],
+ 'network' => [
+ 'data' => \array_map(function ($value) {return ['value' => \round($value['value'] / 1000000, 2), 'date' => $value['date']];}, $network), // convert bytes to mb
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['value'];
+ }, $network)),
+ ],
+ 'collections' => [
+ 'data' => $collections,
+ 'total' => $collectionsTotal,
+ ],
+ 'documents' => [
+ 'data' => $documents,
+ 'total' => \array_sum(\array_map(function ($item) {
+ return $item['total'];
+ }, $documents)),
+ ],
+ 'users' => [
+ 'data' => [],
+ 'total' => $usersTotal,
+ ],
+ 'tasks' => [
+ 'data' => [],
+ 'total' => $tasksTotal,
+ ],
+ 'storage' => [
+ 'total' => $projectDB->getCount(
+ [
+ 'attribute' => 'sizeOriginal',
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_FILES,
+ ],
+ ]
+ ) +
+ $projectDB->getCount(
+ [
+ 'attribute' => 'codeSize',
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_TAGS,
+ ],
+ ]
+ ),
+ ],
+ ]);
+ }, ['response', 'consoleDB', 'projectDB', 'register']);
+
+App::patch('/v1/projects/:projectId')
+ ->desc('Update Project')
+ ->groups(['api', 'projects'])
+ ->label('scope', 'projects.write')
+ ->label('sdk.namespace', 'projects')
+ ->label('sdk.method', 'update')
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
+ ->param('description', '', new Text(256), 'Project description. Max length: 256 chars.', true)
+ ->param('logo', '', new Text(1024), 'Project logo.', true)
+ ->param('url', '', new URL(), 'Project URL.', true)
+ ->param('legalName', '', new Text(256), 'Project legal name. Max length: 256 chars.', true)
+ ->param('legalCountry', '', new Text(256), 'Project legal country. Max length: 256 chars.', true)
+ ->param('legalState', '', new Text(256), 'Project legal state. Max length: 256 chars.', true)
+ ->param('legalCity', '', new Text(256), 'Project legal city. Max length: 256 chars.', true)
+ ->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true)
+ ->param('legalTaxId', '', new Text(256), 'Project legal tax ID. Max length: 256 chars.', true)
+ ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+
+ $project = $consoleDB->getDocument($projectId);
+
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
+ }
+
+ $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
+ 'name' => $name,
+ 'description' => $description,
+ 'logo' => $logo,
+ 'url' => $url,
+ 'legalName' => $legalName,
+ 'legalCountry' => $legalCountry,
+ 'legalState' => $legalState,
+ 'legalCity' => $legalCity,
+ 'legalAddress' => $legalAddress,
+ 'legalTaxId' => $legalTaxId,
+ ]));
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response->json($project->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::patch('/v1/projects/:projectId/oauth2')
->desc('Update Project OAuth2')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateOAuth2')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'Provider Name', false)
- ->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true)
- ->param('secret', '', function () { return new text(512); }, 'Provider secret key.', true)
- ->action(
- function ($projectId, $provider, $appId, $secret) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name', false)
+ ->param('appId', '', new Text(256), 'Provider app ID. Max length: 256 chars.', true)
+ ->param('secret', '', new text(512), 'Provider secret key. Max length: 512 chars.', true)
+ ->action(function ($projectId, $provider, $appId, $secret, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
- $secret = \json_encode([
- 'data' => OpenSSL::encrypt($secret, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag),
- 'version' => '1',
- ]);
-
- $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
- 'usersOauth2'.\ucfirst($provider).'Appid' => $appId,
- 'usersOauth2'.\ucfirst($provider).'Secret' => $secret,
- ]));
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $response->json($project->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId')
+ $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [
+ 'usersOauth2'.\ucfirst($provider).'Appid' => $appId,
+ 'usersOauth2'.\ucfirst($provider).'Secret' => $secret,
+ ]));
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response->json($project->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId')
->desc('Delete Project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'delete')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->param('password', '', function () { return new UID(); }, 'Your user password for confirmation. Must be between 6 to 32 chars.')
- ->action(
- function ($projectId, $password) use ($response, $consoleDB, $user, $deletes) {
- if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
- throw new Exception('Invalid credentials', 401);
- }
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->param('password', '', new UID(), 'Your user password for confirmation. Must be between 6 to 32 chars.')
+ ->action(function ($projectId, $password, $response, $user, $consoleDB, $deletes) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $consoleDB */
+ /** @var Appwrite\Event\Event $deletes */
- $project = $consoleDB->getDocument($projectId);
+ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
+ throw new Exception('Invalid credentials', 401);
+ }
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $deletes->setParam('document', $project->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
+ }
- foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms)
- $list = $project->getAttribute('webhooks', []);
+ $deletes->setParam('document', $project->getArrayCopy());
- foreach ($list as $document) { /* @var $document Document */
- if (!$consoleDB->deleteDocument($projectId)) {
- throw new Exception('Failed to remove project document ('.$key.')] from DB', 500);
- }
+ foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms)
+ $list = $project->getAttribute('webhooks', []);
+
+ foreach ($list as $document) { /* @var $document Document */
+ if (!$consoleDB->deleteDocument($projectId)) {
+ throw new Exception('Failed to remove project document ('.$key.')] from DB', 500);
}
}
-
- if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) {
- throw new Exception('Failed to remove project team from DB', 500);
- }
-
- if (!$consoleDB->deleteDocument($projectId)) {
- throw new Exception('Failed to remove project from DB', 500);
- }
-
- $response->noContent();
}
- );
+
+ if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) {
+ throw new Exception('Failed to remove project team from DB', 500);
+ }
+
+ if (!$consoleDB->deleteDocument($projectId)) {
+ throw new Exception('Failed to remove project from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'user', 'consoleDB', 'deletes']);
// Webhooks
-$utopia->post('/v1/projects/:projectId/webhooks')
+App::post('/v1/projects/:projectId/webhooks')
->desc('Create Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createWebhook')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Webhook name.')
- ->param('events', null, function () { return new ArrayList(new Text(256)); }, 'Webhook events list.')
- ->param('url', null, function () { return new Text(2000); }, 'Webhook URL.')
- ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.')
- ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user.', true)
- ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password.', true)
- ->action(
- function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
+ ->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
+ ->param('url', null, new URL(), 'Webhook URL.')
+ ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
+ ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
+ ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
+ ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
- $httpPass = \json_encode([
- 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag),
- 'version' => '1',
- ]);
-
- $webhook = $consoleDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS,
- '$permissions' => [
- 'read' => ['team:'.$project->getAttribute('teamId', null)],
- 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
- ],
- 'name' => $name,
- 'events' => $events,
- 'url' => $url,
- 'security' => (int) $security,
- 'httpUser' => $httpUser,
- 'httpPass' => $httpPass,
- ]);
-
- if (false === $webhook) {
- throw new Exception('Failed saving webhook to DB', 500);
- }
-
- $project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND);
-
- $project = $consoleDB->updateDocument($project->getArrayCopy());
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($webhook->getArrayCopy())
- ;
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/webhooks')
+ $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
+
+ $webhook = $consoleDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS,
+ '$permissions' => [
+ 'read' => ['team:'.$project->getAttribute('teamId', null)],
+ 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
+ ],
+ 'name' => $name,
+ 'events' => $events,
+ 'url' => $url,
+ 'security' => $security,
+ 'httpUser' => $httpUser,
+ 'httpPass' => $httpPass,
+ ]);
+
+ if (false === $webhook) {
+ throw new Exception('Failed saving webhook to DB', 500);
+ }
+
+ $project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND);
+
+ $project = $consoleDB->updateDocument($project->getArrayCopy());
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($webhook->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/webhooks')
->desc('List Webhooks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listWebhooks')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $webhooks = $project->getAttribute('webhooks', []);
-
- foreach ($webhooks as $webhook) { /* @var $webhook Document */
- $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true);
-
- if (empty($httpPass) || !isset($httpPass['version'])) {
- continue;
- }
-
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
-
- $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
- }
-
- $response->json($webhooks);
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/webhooks/:webhookId')
+ $webhooks = $project->getAttribute('webhooks', []);
+
+ $response->json($webhooks);
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Get Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getWebhook')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('webhookId', null, function () { return new UID(); }, 'Webhook unique ID.')
- ->action(
- function ($projectId, $webhookId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('webhookId', null, new UID(), 'Webhook unique ID.')
+ ->action(function ($projectId, $webhookId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
-
- if (empty($webhook) || !$webhook instanceof Document) {
- throw new Exception('Webhook not found', 404);
- }
-
- $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true);
-
- if (!empty($httpPass) && isset($httpPass['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
- }
-
- $response->json($webhook->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+ $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
-$utopia->put('/v1/projects/:projectId/webhooks/:webhookId')
+ if (empty($webhook) || !$webhook instanceof Document) {
+ throw new Exception('Webhook not found', 404);
+ }
+
+ $response->json($webhook->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::put('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Update Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateWebhook')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('webhookId', null, function () { return new UID(); }, 'Webhook unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Webhook name.')
- ->param('events', null, function () { return new ArrayList(new Text(256)); }, 'Webhook events list.')
- ->param('url', null, function () { return new Text(2000); }, 'Webhook URL.')
- ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.') ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user.', true)
- ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password.', true)
- ->action(
- function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('webhookId', null, new UID(), 'Webhook unique ID.')
+ ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
+ ->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
+ ->param('url', null, new URL(), 'Webhook URL.')
+ ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
+ ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
+ ->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
+ ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
- $httpPass = \json_encode([
- 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag),
- 'version' => '1',
- ]);
-
- $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
-
- if (empty($webhook) || !$webhook instanceof Document) {
- throw new Exception('Webhook not found', 404);
- }
-
- $webhook
- ->setAttribute('name', $name)
- ->setAttribute('events', $events)
- ->setAttribute('url', $url)
- ->setAttribute('security', (int) $security)
- ->setAttribute('httpUser', $httpUser)
- ->setAttribute('httpPass', $httpPass)
- ;
-
- if (false === $consoleDB->updateDocument($webhook->getArrayCopy())) {
- throw new Exception('Failed saving webhook to DB', 500);
- }
-
- $response->json($webhook->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId/webhooks/:webhookId')
+ $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
+
+ $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
+
+ if (empty($webhook) || !$webhook instanceof Document) {
+ throw new Exception('Webhook not found', 404);
+ }
+
+ $webhook
+ ->setAttribute('name', $name)
+ ->setAttribute('events', $events)
+ ->setAttribute('url', $url)
+ ->setAttribute('security', $security)
+ ->setAttribute('httpUser', $httpUser)
+ ->setAttribute('httpPass', $httpPass)
+ ;
+
+ if (false === $consoleDB->updateDocument($webhook->getArrayCopy())) {
+ throw new Exception('Failed saving webhook to DB', 500);
+ }
+
+ $response->json($webhook->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Delete Webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteWebhook')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('webhookId', null, function () { return new UID(); }, 'Webhook unique ID.')
- ->action(
- function ($projectId, $webhookId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('webhookId', null, new UID(), 'Webhook unique ID.')
+ ->action(function ($projectId, $webhookId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
-
- if (empty($webhook) || !$webhook instanceof Document) {
- throw new Exception('Webhook not found', 404);
- }
-
- if (!$consoleDB->deleteDocument($webhook->getId())) {
- throw new Exception('Failed to remove webhook from DB', 500);
- }
-
- $response->noContent();
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+
+ $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', []));
+
+ if (empty($webhook) || !$webhook instanceof Document) {
+ throw new Exception('Webhook not found', 404);
+ }
+
+ if (!$consoleDB->deleteDocument($webhook->getId())) {
+ throw new Exception('Failed to remove webhook from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'consoleDB']);
// Keys
-$utopia->post('/v1/projects/:projectId/keys')
+App::post('/v1/projects/:projectId/keys')
->desc('Create Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createKey')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Key name.')
- ->param('scopes', null, function () use ($scopes) { return new ArrayList(new WhiteList($scopes)); }, 'Key scopes list.')
- ->action(
- function ($projectId, $name, $scopes) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
+ ->param('scopes', null, new ArrayList(new WhiteList(Config::getParam('scopes'), true)), 'Key scopes list.')
+ ->action(function ($projectId, $name, $scopes, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $key = $consoleDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_KEYS,
- '$permissions' => [
- 'read' => ['team:'.$project->getAttribute('teamId', null)],
- 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
- ],
- 'name' => $name,
- 'scopes' => $scopes,
- 'secret' => \bin2hex(\random_bytes(128)),
- ]);
-
- if (false === $key) {
- throw new Exception('Failed saving key to DB', 500);
- }
-
- $project->setAttribute('keys', $key, Document::SET_TYPE_APPEND);
-
- $project = $consoleDB->updateDocument($project->getArrayCopy());
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($key->getArrayCopy())
- ;
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/keys')
+ $key = $consoleDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_KEYS,
+ '$permissions' => [
+ 'read' => ['team:'.$project->getAttribute('teamId', null)],
+ 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
+ ],
+ 'name' => $name,
+ 'scopes' => $scopes,
+ 'secret' => \bin2hex(\random_bytes(128)),
+ ]);
+
+ if (false === $key) {
+ throw new Exception('Failed saving key to DB', 500);
+ }
+
+ $project->setAttribute('keys', $key, Document::SET_TYPE_APPEND);
+
+ $project = $consoleDB->updateDocument($project->getArrayCopy());
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($key->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/keys')
->desc('List Keys')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
+
+ $project = $consoleDB->getDocument($projectId);
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
-
- $response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/keys/:keyId')
+ $response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/keys/:keyId')
->desc('Get Key')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.')
- ->action(
- function ($projectId, $keyId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('keyId', null, new UID(), 'Key unique ID.')
+ ->action(function ($projectId, $keyId, $response, $consoleDB) {
+ $project = $consoleDB->getDocument($projectId);
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
-
- $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
-
- if (empty($key) || !$key instanceof Document) {
- throw new Exception('Key not found', 404);
- }
-
- $response->json($key->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->put('/v1/projects/:projectId/keys/:keyId')
+ $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
+
+ if (empty($key) || !$key instanceof Document) {
+ throw new Exception('Key not found', 404);
+ }
+
+ $response->json($key->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::put('/v1/projects/:projectId/keys/:keyId')
->desc('Update Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateKey')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Key name.')
- ->param('scopes', null, function () use ($scopes) { return new ArrayList(new WhiteList($scopes)); }, 'Key scopes list')
- ->action(
- function ($projectId, $keyId, $name, $scopes) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('keyId', null, new UID(), 'Key unique ID.')
+ ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
+ ->param('scopes', null, new ArrayList(new WhiteList(Config::getParam('scopes'), true)), 'Key scopes list')
+ ->action(function ($projectId, $keyId, $name, $scopes, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
-
- if (empty($key) || !$key instanceof Document) {
- throw new Exception('Key not found', 404);
- }
-
- $key
- ->setAttribute('name', $name)
- ->setAttribute('scopes', $scopes)
- ;
-
- if (false === $consoleDB->updateDocument($key->getArrayCopy())) {
- throw new Exception('Failed saving key to DB', 500);
- }
-
- $response->json($key->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId/keys/:keyId')
+ $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
+
+ if (empty($key) || !$key instanceof Document) {
+ throw new Exception('Key not found', 404);
+ }
+
+ $key
+ ->setAttribute('name', $name)
+ ->setAttribute('scopes', $scopes)
+ ;
+
+ if (false === $consoleDB->updateDocument($key->getArrayCopy())) {
+ throw new Exception('Failed saving key to DB', 500);
+ }
+
+ $response->json($key->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId/keys/:keyId')
->desc('Delete Key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteKey')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.')
- ->action(
- function ($projectId, $keyId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('keyId', null, new UID(), 'Key unique ID.')
+ ->action(function ($projectId, $keyId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
-
- if (empty($key) || !$key instanceof Document) {
- throw new Exception('Key not found', 404);
- }
-
- if (!$consoleDB->deleteDocument($key->getId())) {
- throw new Exception('Failed to remove key from DB', 500);
- }
-
- $response->noContent();
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+
+ $key = $project->search('$id', $keyId, $project->getAttribute('keys', []));
+
+ if (empty($key) || !$key instanceof Document) {
+ throw new Exception('Key not found', 404);
+ }
+
+ if (!$consoleDB->deleteDocument($key->getId())) {
+ throw new Exception('Failed to remove key from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'consoleDB']);
// Tasks
-$utopia->post('/v1/projects/:projectId/tasks')
+App::post('/v1/projects/:projectId/tasks')
->desc('Create Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createTask')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Task name.')
- ->param('status', null, function () { return new WhiteList(['play', 'pause']); }, 'Task status.')
- ->param('schedule', null, function () { return new Cron(); }, 'Task schedule CRON syntax.')
- ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.') ->param('httpMethod', '', function () { return new WhiteList(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT']); }, 'Task HTTP method.')
- ->param('httpUrl', '', function () { return new URL(); }, 'Task HTTP URL')
- ->param('httpHeaders', null, function () { return new ArrayList(new Text(256)); }, 'Task HTTP headers list.', true)
- ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user.', true)
- ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password.', true)
- ->action(
- function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('name', null, new Text(128), 'Task name. Max length: 128 chars.')
+ ->param('status', null, new WhiteList(['play', 'pause'], true), 'Task status.')
+ ->param('schedule', null, new Cron(), 'Task schedule CRON syntax.')
+ ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
+ ->param('httpMethod', '', new WhiteList(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'], true), 'Task HTTP method.')
+ ->param('httpUrl', '', new URL(), 'Task HTTP URL')
+ ->param('httpHeaders', null, new ArrayList(new Text(256)), 'Task HTTP headers list.', true)
+ ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true)
+ ->param('httpPass', '', new Text(256), 'Task HTTP password. Max length: 256 chars.', true)
+ ->action(function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $cron = CronExpression::factory($schedule);
- $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
-
- $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
- $httpPass = \json_encode([
- 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag),
- 'version' => '1',
- ]);
-
- $task = $consoleDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_TASKS,
- '$permissions' => [
- 'read' => ['team:'.$project->getAttribute('teamId', null)],
- 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
- ],
- 'name' => $name,
- 'status' => $status,
- 'schedule' => $schedule,
- 'updated' => \time(),
- 'previous' => null,
- 'next' => $next,
- 'security' => (int) $security,
- 'httpMethod' => $httpMethod,
- 'httpUrl' => $httpUrl,
- 'httpHeaders' => $httpHeaders,
- 'httpUser' => $httpUser,
- 'httpPass' => $httpPass,
- 'log' => '{}',
- 'failures' => 0,
- ]);
-
- if (false === $task) {
- throw new Exception('Failed saving tasks to DB', 500);
- }
-
- $project->setAttribute('tasks', $task, Document::SET_TYPE_APPEND);
-
- $project = $consoleDB->updateDocument($project->getArrayCopy());
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- if ($next) {
- ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy());
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($task->getArrayCopy())
- ;
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/tasks')
+ $cron = CronExpression::factory($schedule);
+ $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
+
+ $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
+
+ $task = $consoleDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_TASKS,
+ '$permissions' => [
+ 'read' => ['team:'.$project->getAttribute('teamId', null)],
+ 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
+ ],
+ 'name' => $name,
+ 'status' => $status,
+ 'schedule' => $schedule,
+ 'updated' => \time(),
+ 'previous' => null,
+ 'next' => $next,
+ 'security' => $security,
+ 'httpMethod' => $httpMethod,
+ 'httpUrl' => $httpUrl,
+ 'httpHeaders' => $httpHeaders,
+ 'httpUser' => $httpUser,
+ 'httpPass' => $httpPass,
+ 'log' => '{}',
+ 'failures' => 0,
+ ]);
+
+ if (false === $task) {
+ throw new Exception('Failed saving tasks to DB', 500);
+ }
+
+ $project->setAttribute('tasks', $task, Document::SET_TYPE_APPEND);
+
+ $project = $consoleDB->updateDocument($project->getArrayCopy());
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ if ($next) {
+ ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy());
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($task->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/tasks')
->desc('List Tasks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listTasks')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $tasks = $project->getAttribute('tasks', []);
-
- foreach ($tasks as $task) { /* @var $task Document */
- $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true);
-
- if (empty($httpPass) || !isset($httpPass['version'])) {
- continue;
- }
-
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
-
- $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
- }
-
- $response->json($tasks);
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/tasks/:taskId')
+ $tasks = $project->getAttribute('tasks', []);
+
+ $response->json($tasks);
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/tasks/:taskId')
->desc('Get Task')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getTask')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('taskId', null, function () { return new UID(); }, 'Task unique ID.')
- ->action(
- function ($projectId, $taskId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('taskId', null, new UID(), 'Task unique ID.')
+ ->action(function ($projectId, $taskId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
-
- if (empty($task) || !$task instanceof Document) {
- throw new Exception('Task not found', 404);
- }
-
- $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true);
-
- if (!empty($httpPass) && isset($httpPass['version'])) {
- $key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
- $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag'])));
- }
-
- $response->json($task->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->put('/v1/projects/:projectId/tasks/:taskId')
+ $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
+
+ if (empty($task) || !$task instanceof Document) {
+ throw new Exception('Task not found', 404);
+ }
+
+ $response->json($task->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::put('/v1/projects/:projectId/tasks/:taskId')
->desc('Update Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateTask')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('taskId', null, function () { return new UID(); }, 'Task unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Task name.')
- ->param('status', null, function () { return new WhiteList(['play', 'pause']); }, 'Task status.')
- ->param('schedule', null, function () { return new Cron(); }, 'Task schedule CRON syntax.')
- ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.')
- ->param('httpMethod', '', function () { return new WhiteList(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT']); }, 'Task HTTP method.')
- ->param('httpUrl', '', function () { return new URL(); }, 'Task HTTP URL.')
- ->param('httpHeaders', null, function () { return new ArrayList(new Text(256)); }, 'Task HTTP headers list.', true)
- ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user.', true)
- ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password.', true)
- ->action(
- function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('taskId', null, new UID(), 'Task unique ID.')
+ ->param('name', null, new Text(128), 'Task name. Max length: 128 chars.')
+ ->param('status', null, new WhiteList(['play', 'pause'], true), 'Task status.')
+ ->param('schedule', null, new Cron(), 'Task schedule CRON syntax.')
+ ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
+ ->param('httpMethod', '', new WhiteList(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'], true), 'Task HTTP method.')
+ ->param('httpUrl', '', new URL(), 'Task HTTP URL.')
+ ->param('httpHeaders', null, new ArrayList(new Text(256)), 'Task HTTP headers list.', true)
+ ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true)
+ ->param('httpPass', '', new Text(256), 'Task HTTP password. Max length: 256 chars.', true)
+ ->action(function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
-
- if (empty($task) || !$task instanceof Document) {
- throw new Exception('Task not found', 404);
- }
-
- $cron = CronExpression::factory($schedule);
- $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
-
- $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
- $httpPass = \json_encode([
- 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag),
- 'version' => '1',
- ]);
-
- $task
- ->setAttribute('name', $name)
- ->setAttribute('status', $status)
- ->setAttribute('schedule', $schedule)
- ->setAttribute('updated', \time())
- ->setAttribute('next', $next)
- ->setAttribute('security', (int) $security)
- ->setAttribute('httpMethod', $httpMethod)
- ->setAttribute('httpUrl', $httpUrl)
- ->setAttribute('httpHeaders', $httpHeaders)
- ->setAttribute('httpUser', $httpUser)
- ->setAttribute('httpPass', $httpPass)
- ;
-
- if (false === $consoleDB->updateDocument($task->getArrayCopy())) {
- throw new Exception('Failed saving tasks to DB', 500);
- }
-
- if ($next) {
- ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy());
- }
-
- $response->json($task->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId/tasks/:taskId')
+ $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
+
+ if (empty($task) || !$task instanceof Document) {
+ throw new Exception('Task not found', 404);
+ }
+
+ $cron = CronExpression::factory($schedule);
+ $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null;
+
+ $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true);
+
+ $task
+ ->setAttribute('name', $name)
+ ->setAttribute('status', $status)
+ ->setAttribute('schedule', $schedule)
+ ->setAttribute('updated', \time())
+ ->setAttribute('next', $next)
+ ->setAttribute('security', $security)
+ ->setAttribute('httpMethod', $httpMethod)
+ ->setAttribute('httpUrl', $httpUrl)
+ ->setAttribute('httpHeaders', $httpHeaders)
+ ->setAttribute('httpUser', $httpUser)
+ ->setAttribute('httpPass', $httpPass)
+ ;
+
+ if (false === $consoleDB->updateDocument($task->getArrayCopy())) {
+ throw new Exception('Failed saving tasks to DB', 500);
+ }
+
+ if ($next) {
+ ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy());
+ }
+
+ $response->json($task->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId/tasks/:taskId')
->desc('Delete Task')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteTask')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('taskId', null, function () { return new UID(); }, 'Task unique ID.')
- ->action(
- function ($projectId, $taskId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('taskId', null, new UID(), 'Task unique ID.')
+ ->action(function ($projectId, $taskId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
-
- if (empty($task) || !$task instanceof Document) {
- throw new Exception('Task not found', 404);
- }
-
- if (!$consoleDB->deleteDocument($task->getId())) {
- throw new Exception('Failed to remove tasks from DB', 500);
- }
-
- $response->noContent();
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+
+ $task = $project->search('$id', $taskId, $project->getAttribute('tasks', []));
+
+ if (empty($task) || !$task instanceof Document) {
+ throw new Exception('Task not found', 404);
+ }
+
+ if (!$consoleDB->deleteDocument($task->getId())) {
+ throw new Exception('Failed to remove tasks from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'consoleDB']);
// Platforms
-$utopia->post('/v1/projects/:projectId/platforms')
+App::post('/v1/projects/:projectId/platforms')
->desc('Create Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createPlatform')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('type', null, function () { return new WhiteList(['web', 'flutter-ios', 'flutter-android', 'ios', 'android', 'unity']); }, 'Platform type.')
- ->param('name', null, function () { return new Text(256); }, 'Platform name.')
- ->param('key', '', function () { return new Text(256); }, 'Package name for android or bundle ID for iOS.', true)
- ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID.', true)
- ->param('hostname', '', function () { return new Text(256); }, 'Platform client hostname.', true)
- ->action(
- function ($projectId, $type, $name, $key, $store, $hostname) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('type', null, new WhiteList(['web', 'flutter-ios', 'flutter-android', 'ios', 'android', 'unity'], true), 'Platform type.')
+ ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
+ ->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
+ ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
+ ->param('hostname', '', new Text(256), 'Platform client hostname. Max length: 256 chars.', true)
+ ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $platform = $consoleDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
- '$permissions' => [
- 'read' => ['team:'.$project->getAttribute('teamId', null)],
- 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
- ],
- 'type' => $type,
- 'name' => $name,
- 'key' => $key,
- 'store' => $store,
- 'hostname' => $hostname,
- 'dateCreated' => \time(),
- 'dateUpdated' => \time(),
- ]);
-
- if (false === $platform) {
- throw new Exception('Failed saving platform to DB', 500);
- }
-
- $project->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND);
-
- $project = $consoleDB->updateDocument($project->getArrayCopy());
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($platform->getArrayCopy())
- ;
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+
+ $platform = $consoleDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
+ '$permissions' => [
+ 'read' => ['team:'.$project->getAttribute('teamId', null)],
+ 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
+ ],
+ 'type' => $type,
+ 'name' => $name,
+ 'key' => $key,
+ 'store' => $store,
+ 'hostname' => $hostname,
+ 'dateCreated' => \time(),
+ 'dateUpdated' => \time(),
+ ]);
+
+ if (false === $platform) {
+ throw new Exception('Failed saving platform to DB', 500);
+ }
+
+ $project->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND);
+
+ $project = $consoleDB->updateDocument($project->getArrayCopy());
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($platform->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB']);
-$utopia->get('/v1/projects/:projectId/platforms')
+App::get('/v1/projects/:projectId/platforms')
->desc('List Platforms')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $platforms = $project->getAttribute('platforms', []);
-
- $response->json($platforms);
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/platforms/:platformId')
+ $platforms = $project->getAttribute('platforms', []);
+
+ $response->json($platforms);
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.')
- ->action(
- function ($projectId, $platformId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('platformId', null, new UID(), 'Platform unique ID.')
+ ->action(function ($projectId, $platformId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
-
- if (empty($platform) || !$platform instanceof Document) {
- throw new Exception('Platform not found', 404);
- }
-
- $response->json($platform->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->put('/v1/projects/:projectId/platforms/:platformId')
+ $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
+
+ if (empty($platform) || !$platform instanceof Document) {
+ throw new Exception('Platform not found', 404);
+ }
+
+ $response->json($platform->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updatePlatform')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.')
- ->param('name', null, function () { return new Text(256); }, 'Platform name.')
- ->param('key', '', function () { return new Text(256); }, 'Package name for android or bundle ID for iOS.', true)
- ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID.', true)
- ->param('hostname', '', function () { return new Text(256); }, 'Platform client URL.', true)
- ->action(
- function ($projectId, $platformId, $name, $key, $store, $hostname) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('platformId', null, new UID(), 'Platform unique ID.')
+ ->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
+ ->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
+ ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
+ ->param('hostname', '', new Text(256), 'Platform client URL. Max length: 256 chars.', true)
+ ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
-
- if (empty($platform) || !$platform instanceof Document) {
- throw new Exception('Platform not found', 404);
- }
-
- $platform
- ->setAttribute('name', $name)
- ->setAttribute('dateUpdated', \time())
- ->setAttribute('key', $key)
- ->setAttribute('store', $store)
- ->setAttribute('hostname', $hostname)
- ;
-
- if (false === $consoleDB->updateDocument($platform->getArrayCopy())) {
- throw new Exception('Failed saving platform to DB', 500);
- }
-
- $response->json($platform->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId/platforms/:platformId')
+ $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
+
+ if (empty($platform) || !$platform instanceof Document) {
+ throw new Exception('Platform not found', 404);
+ }
+
+ $platform
+ ->setAttribute('name', $name)
+ ->setAttribute('dateUpdated', \time())
+ ->setAttribute('key', $key)
+ ->setAttribute('store', $store)
+ ->setAttribute('hostname', $hostname)
+ ;
+
+ if (false === $consoleDB->updateDocument($platform->getArrayCopy())) {
+ throw new Exception('Failed saving platform to DB', 500);
+ }
+
+ $response->json($platform->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete Platform')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deletePlatform')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.')
- ->action(
- function ($projectId, $platformId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('platformId', null, new UID(), 'Platform unique ID.')
+ ->action(function ($projectId, $platformId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
-
- if (empty($platform) || !$platform instanceof Document) {
- throw new Exception('Platform not found', 404);
- }
-
- if (!$consoleDB->deleteDocument($platform->getId())) {
- throw new Exception('Failed to remove platform from DB', 500);
- }
-
- $response->noContent();
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
+
+ $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', []));
+
+ if (empty($platform) || !$platform instanceof Document) {
+ throw new Exception('Platform not found', 404);
+ }
+
+ if (!$consoleDB->deleteDocument($platform->getId())) {
+ throw new Exception('Failed to remove platform from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'consoleDB']);
// Domains
-$utopia->post('/v1/projects/:projectId/domains')
+App::post('/v1/projects/:projectId/domains')
->desc('Create Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createDomain')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('domain', null, function () { return new DomainValidator(); }, 'Domain name.')
- ->action(
- function ($projectId, $domain) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('domain', null, new DomainValidator(), 'Domain name.')
+ ->action(function ($projectId, $domain, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $document = $project->search('domain', $domain, $project->getAttribute('domains', []));
-
- if (!empty($document)) {
- throw new Exception('Domain already exists', 409);
- }
-
- $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', ''));
-
- if (!$target->isKnown() || $target->isTest()) {
- throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500);
- }
-
- $domain = new Domain($domain);
-
- $domain = $consoleDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_DOMAINS,
- '$permissions' => [
- 'read' => ['team:'.$project->getAttribute('teamId', null)],
- 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
- ],
- 'updated' => \time(),
- 'domain' => $domain->get(),
- 'tld' => $domain->getSuffix(),
- 'registerable' => $domain->getRegisterable(),
- 'verification' => false,
- 'certificateId' => null,
- ]);
-
- if (false === $domain) {
- throw new Exception('Failed saving domain to DB', 500);
- }
-
- $project->setAttribute('domains', $domain, Document::SET_TYPE_APPEND);
-
- $project = $consoleDB->updateDocument($project->getArrayCopy());
-
- if (false === $project) {
- throw new Exception('Failed saving project to DB', 500);
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($domain->getArrayCopy())
- ;
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/domains')
+ $document = $project->search('domain', $domain, $project->getAttribute('domains', []));
+
+ if (!empty($document)) {
+ throw new Exception('Domain already exists', 409);
+ }
+
+ $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
+
+ if (!$target->isKnown() || $target->isTest()) {
+ throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500);
+ }
+
+ $domain = new Domain($domain);
+
+ $domain = $consoleDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_DOMAINS,
+ '$permissions' => [
+ 'read' => ['team:'.$project->getAttribute('teamId', null)],
+ 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
+ ],
+ 'updated' => \time(),
+ 'domain' => $domain->get(),
+ 'tld' => $domain->getSuffix(),
+ 'registerable' => $domain->getRegisterable(),
+ 'verification' => false,
+ 'certificateId' => null,
+ ]);
+
+ if (false === $domain) {
+ throw new Exception('Failed saving domain to DB', 500);
+ }
+
+ $project->setAttribute('domains', $domain, Document::SET_TYPE_APPEND);
+
+ $project = $consoleDB->updateDocument($project->getArrayCopy());
+
+ if (false === $project) {
+ throw new Exception('Failed saving project to DB', 500);
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($domain->getArrayCopy())
+ ;
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/domains')
->desc('List Domains')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listDomains')
- ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
- ->action(
- function ($projectId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', '', new UID(), 'Project unique ID.')
+ ->action(function ($projectId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $domains = $project->getAttribute('domains', []);
-
- $response->json($domains);
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->get('/v1/projects/:projectId/domains/:domainId')
+ $domains = $project->getAttribute('domains', []);
+
+ $response->json($domains);
+ }, ['response', 'consoleDB']);
+
+App::get('/v1/projects/:projectId/domains/:domainId')
->desc('Get Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getDomain')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.')
- ->action(
- function ($projectId, $domainId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('domainId', null, new UID(), 'Domain unique ID.')
+ ->action(function ($projectId, $domainId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
-
- if (empty($domain) || !$domain instanceof Document) {
- throw new Exception('Domain not found', 404);
- }
-
- $response->json($domain->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->patch('/v1/projects/:projectId/domains/:domainId/verification')
+ $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
+
+ if (empty($domain) || !$domain instanceof Document) {
+ throw new Exception('Domain not found', 404);
+ }
+
+ $response->json($domain->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::patch('/v1/projects/:projectId/domains/:domainId/verification')
->desc('Update Domain Verification Status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateDomainVerification')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.')
- ->action(
- function ($projectId, $domainId) use ($request, $response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('domainId', null, new UID(), 'Domain unique ID.')
+ ->action(function ($projectId, $domainId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
-
- if (empty($domain) || !$domain instanceof Document) {
- throw new Exception('Domain not found', 404);
- }
-
- $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', ''));
-
- if (!$target->isKnown() || $target->isTest()) {
- throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500);
- }
-
- if ($domain->getAttribute('verification') === true) {
- return $response->json($domain->getArrayCopy());
- }
-
- // Verify Domain with DNS records
- $validator = new CNAME($target->get());
-
- if (!$validator->isValid($domain->getAttribute('domain', ''))) {
- throw new Exception('Failed to verify domain', 401);
- }
-
- $domain
- ->setAttribute('verification', true)
- ;
-
- if (false === $consoleDB->updateDocument($domain->getArrayCopy())) {
- throw new Exception('Failed saving domains to DB', 500);
- }
-
- // Issue a TLS certificate when domain is verified
- Resque::enqueue('v1-certificates', 'CertificatesV1', [
- 'document' => $domain->getArrayCopy(),
- 'domain' => $domain->getAttribute('domain'),
- ]);
-
- $response->json($domain->getArrayCopy());
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
-$utopia->delete('/v1/projects/:projectId/domains/:domainId')
+ $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
+
+ if (empty($domain) || !$domain instanceof Document) {
+ throw new Exception('Domain not found', 404);
+ }
+
+ $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
+
+ if (!$target->isKnown() || $target->isTest()) {
+ throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500);
+ }
+
+ if ($domain->getAttribute('verification') === true) {
+ return $response->json($domain->getArrayCopy());
+ }
+
+ // Verify Domain with DNS records
+ $validator = new CNAME($target->get());
+
+ if (!$validator->isValid($domain->getAttribute('domain', ''))) {
+ throw new Exception('Failed to verify domain', 401);
+ }
+
+ $domain
+ ->setAttribute('verification', true)
+ ;
+
+ if (false === $consoleDB->updateDocument($domain->getArrayCopy())) {
+ throw new Exception('Failed saving domains to DB', 500);
+ }
+
+ // Issue a TLS certificate when domain is verified
+ Resque::enqueue('v1-certificates', 'CertificatesV1', [
+ 'document' => $domain->getArrayCopy(),
+ 'domain' => $domain->getAttribute('domain'),
+ ]);
+
+ $response->json($domain->getArrayCopy());
+ }, ['response', 'consoleDB']);
+
+App::delete('/v1/projects/:projectId/domains/:domainId')
->desc('Delete Domain')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteDomain')
- ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
- ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.')
- ->action(
- function ($projectId, $domainId) use ($response, $consoleDB) {
- $project = $consoleDB->getDocument($projectId);
+ ->param('projectId', null, new UID(), 'Project unique ID.')
+ ->param('domainId', null, new UID(), 'Domain unique ID.')
+ ->action(function ($projectId, $domainId, $response, $consoleDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $consoleDB */
- if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
- throw new Exception('Project not found', 404);
- }
+ $project = $consoleDB->getDocument($projectId);
- $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
-
- if (empty($domain) || !$domain instanceof Document) {
- throw new Exception('Domain not found', 404);
- }
-
- if (!$consoleDB->deleteDocument($domain->getId())) {
- throw new Exception('Failed to remove domains from DB', 500);
- }
-
- $response->noContent();
+ if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
+ throw new Exception('Project not found', 404);
}
- );
\ No newline at end of file
+
+ $domain = $project->search('$id', $domainId, $project->getAttribute('domains', []));
+
+ if (empty($domain) || !$domain instanceof Document) {
+ throw new Exception('Domain not found', 404);
+ }
+
+ if (!$consoleDB->deleteDocument($domain->getId())) {
+ throw new Exception('Failed to remove domains from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'consoleDB']);
\ No newline at end of file
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 9a12b56048..18cfb58f46 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -1,7 +1,6 @@
getId()));
-
-$fileLogos = [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554
- 'default' => __DIR__.'/../../config/files/none.png',
-
- // Video Files
- 'video/mp4' => __DIR__.'/../../config/files/video.png',
- 'video/x-flv' => __DIR__.'/../../config/files/video.png',
- 'application/x-mpegURL' => __DIR__.'/../../config/files/video.png',
- 'video/MP2T' => __DIR__.'/../../config/files/video.png',
- 'video/3gpp' => __DIR__.'/../../config/files/video.png',
- 'video/quicktime' => __DIR__.'/../../config/files/video.png',
- 'video/x-msvideo' => __DIR__.'/../../config/files/video.png',
- 'video/x-ms-wmv' => __DIR__.'/../../config/files/video.png',
-
- // // Microsoft Word
- 'application/msword' => __DIR__.'/../../config/files/word.png',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/../../config/files/word.png',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/../../config/files/word.png',
- 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/../../config/files/word.png',
-
- // // Microsoft Excel
- 'application/vnd.ms-excel' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/../../config/files/excel.png',
- 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/../../config/files/excel.png',
-
- // // Microsoft Power Point
- 'application/vnd.ms-powerpoint' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png',
- 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png',
-
- // Adobe PDF
- 'application/pdf' => __DIR__.'/../../config/files/pdf.png',
-];
-
-$inputs = [
- 'jpg' => 'image/jpeg',
- 'jpeg' => 'image/jpeg',
- 'gif' => 'image/gif',
- 'png' => 'image/png',
-];
-
-$outputs = [
- 'jpg' => 'image/jpeg',
- 'jpeg' => 'image/jpeg',
- 'gif' => 'image/gif',
- 'png' => 'image/png',
- 'webp' => 'image/webp',
-];
-
-$mimes = [
- 'image/jpeg',
- 'image/jpeg',
- 'image/gif',
- 'image/png',
- 'image/webp',
-
- // Video Files
- 'video/mp4',
- 'video/x-flv',
- 'application/x-mpegURL',
- 'video/MP2T',
- 'video/3gpp',
- 'video/quicktime',
- 'video/x-msvideo',
- 'video/x-ms-wmv',
-
- // Microsoft Word
- 'application/msword',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
- 'application/vnd.ms-word.document.macroEnabled.12',
-
- // Microsoft Excel
- 'application/vnd.ms-excel',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
- 'application/vnd.ms-excel.sheet.macroEnabled.12',
- 'application/vnd.ms-excel.template.macroEnabled.12',
- 'application/vnd.ms-excel.addin.macroEnabled.12',
- 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
-
- // Microsoft Power Point
- 'application/vnd.ms-powerpoint',
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- 'application/vnd.openxmlformats-officedocument.presentationml.template',
- 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
- 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
- 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
- 'application/vnd.ms-powerpoint.template.macroEnabled.12',
- 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
-
- // Microsoft Access
- 'application/vnd.ms-access',
-
- // Adobe PDF
- 'application/pdf',
-];
-
-$utopia->post('/v1/storage/files')
+App::post('/v1/storage/files')
->desc('Create File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@@ -142,133 +33,133 @@ $utopia->post('/v1/storage/files')
->label('sdk.description', '/docs/references/storage/create-file.md')
->label('sdk.consumes', 'multipart/form-data')
->label('sdk.methodType', 'upload')
- ->param('file', [], function () { return new File(); }, 'Binary File.', false)
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- // ->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true)
- ->action(
- function ($file, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $webhook, $audit, $usage) {
- $file = $request->getFiles('file');
- $read = (empty($read)) ? ['user:'.$user->getId()] : $read;
- $write = (empty($write)) ? ['user:'.$user->getId()] : $write;
+ ->param('file', [], new File(), 'Binary file.', false)
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->action(function ($file, $read, $write, $request, $response, $user, $projectDB, $webhooks, $audits, $usage) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $usage */
- /*
- * Validators
- */
- //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG));
- $fileSize = new FileSize($request->getServer('_APP_STORAGE_LIMIT', 0));
- $upload = new Upload();
+ $file = $request->getFiles('file');
+ $read = (empty($read)) ? ['user:'.$user->getId()] : $read;
+ $write = (empty($write)) ? ['user:'.$user->getId()] : $write;
- if (empty($file)) {
- throw new Exception('No file sent', 400);
- }
+ /*
+ * Validators
+ */
+ //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG));
+ $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0));
+ $upload = new Upload();
- // Make sure we handle a single file and multiple files the same way
- $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
- $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
- $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
+ if (empty($file)) {
+ throw new Exception('No file sent', 400);
+ }
- // Check if file type is allowed (feature for project settings?)
- //if (!$fileType->isValid($file['tmp_name'])) {
- //throw new Exception('File type not allowed', 400);
- //}
+ // Make sure we handle a single file and multiple files the same way
+ $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
+ $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
+ $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
- // Check if file size is exceeding allowed limit
- if (!$fileSize->isValid($file['size'])) {
- throw new Exception('File size not allowed', 400);
- }
+ // Check if file type is allowed (feature for project settings?)
+ //if (!$fileType->isValid($file['tmp_name'])) {
+ //throw new Exception('File type not allowed', 400);
+ //}
- /*
- * Models
- */
- $device = Storage::getDevice('local');
+ if (!$fileSize->isValid($file['size'])) { // Check if file size is exceeding allowed limit
+ throw new Exception('File size not allowed', 400);
+ }
- if (!$upload->isValid($file['tmp_name'])) {
+ $device = Storage::getDevice('files');
+
+ if (!$upload->isValid($file['tmp_name'])) {
+ throw new Exception('Invalid file', 403);
+ }
+
+ // Save to storage
+ $size = $device->getFileSize($file['tmp_name']);
+ $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION));
+
+ if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
+ throw new Exception('Failed moving file', 500);
+ }
+
+ $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
+
+ if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
+ $antiVirus = new Network('clamav', 3310);
+
+ if (!$antiVirus->fileScan($path)) {
+ $device->delete($path);
throw new Exception('Invalid file', 403);
}
-
- // Save to storage
- $size = $device->getFileSize($file['tmp_name']);
- $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION));
-
- if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
- throw new Exception('Failed moving file', 500);
- }
-
- $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
-
- if ($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled
- $antiVirus = new Network('clamav', 3310);
-
- // Check if file size is exceeding allowed limit
- if (!$antiVirus->fileScan($path)) {
- $device->delete($path);
- throw new Exception('Invalid file', 403);
- }
- }
-
- // Compression
- $compressor = new GZIP();
- $data = $device->read($path);
- $data = $compressor->compress($data);
- $key = $request->getServer('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
-
- if (!$device->write($path, $data)) {
- throw new Exception('Failed to save file', 500);
- }
-
- $sizeActual = $device->getFileSize($path);
-
- $file = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_FILES,
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- 'dateCreated' => \time(),
- 'folderId' => $folderId,
- 'name' => $file['name'],
- 'path' => $path,
- 'signature' => $device->getFileHash($path),
- 'mimeType' => $mimeType,
- 'sizeOriginal' => $size,
- 'sizeActual' => $sizeActual,
- 'algorithm' => $compressor->getName(),
- 'token' => \bin2hex(\random_bytes(64)),
- 'comment' => '',
- 'fileOpenSSLVersion' => '1',
- 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
- 'fileOpenSSLTag' => \bin2hex($tag),
- 'fileOpenSSLIV' => \bin2hex($iv),
- ]);
-
- if (false === $file) {
- throw new Exception('Failed saving file to DB', 500);
- }
-
- $webhook
- ->setParam('payload', $file->getArrayCopy())
- ;
-
- $audit
- ->setParam('event', 'storage.files.create')
- ->setParam('resource', 'storage/files/'.$file->getId())
- ;
-
- $usage
- ->setParam('storage', $sizeActual)
- ;
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($file->getArrayCopy())
- ;
}
- );
-$utopia->get('/v1/storage/files')
+ // Compression
+ $compressor = new GZIP();
+ $data = $device->read($path);
+ $data = $compressor->compress($data);
+ $key = App::getEnv('_APP_OPENSSL_KEY_V1');
+ $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
+ $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
+
+ if (!$device->write($path, $data)) {
+ throw new Exception('Failed to save file', 500);
+ }
+
+ $sizeActual = $device->getFileSize($path);
+
+ $file = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_FILES,
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ 'dateCreated' => \time(),
+ 'folderId' => '',
+ 'name' => $file['name'],
+ 'path' => $path,
+ 'signature' => $device->getFileHash($path),
+ 'mimeType' => $mimeType,
+ 'sizeOriginal' => $size,
+ 'sizeActual' => $sizeActual,
+ 'algorithm' => $compressor->getName(),
+ 'token' => \bin2hex(\random_bytes(64)),
+ 'comment' => '',
+ 'fileOpenSSLVersion' => '1',
+ 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
+ 'fileOpenSSLTag' => \bin2hex($tag),
+ 'fileOpenSSLIV' => \bin2hex($iv),
+ ]);
+
+ if (false === $file) {
+ throw new Exception('Failed saving file to DB', 500);
+ }
+
+ $webhooks
+ ->setParam('payload', $file->getArrayCopy())
+ ;
+
+ $audits
+ ->setParam('event', 'storage.files.create')
+ ->setParam('resource', 'storage/files/'.$file->getId())
+ ;
+
+ $usage
+ ->setParam('storage', $sizeActual)
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($file->getArrayCopy())
+ ;
+ }, ['request', 'response', 'user', 'projectDB', 'webhooks', 'audits', 'usage']);
+
+App::get('/v1/storage/files')
->desc('List Files')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -276,33 +167,34 @@ $utopia->get('/v1/storage/files')
->label('sdk.namespace', 'storage')
->label('sdk.method', 'listFiles')
->label('sdk.description', '/docs/references/storage/list-files.md')
- ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
- ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
- ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
- ->action(
- function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
- $results = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => 'dateCreated',
- 'orderType' => $orderType,
- 'orderCast' => 'int',
- 'search' => $search,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_FILES,
- ],
- ]);
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- $results = \array_map(function ($value) { /* @var $value \Database\Document */
- return $value->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']);
- }, $results);
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'dateCreated',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_FILES,
+ ],
+ ]);
- $response->json(['sum' => $projectDB->getSum(), 'files' => $results]);
- }
- );
+ $results = \array_map(function ($value) { /* @var $value \Database\Document */
+ return $value->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']);
+ }, $results);
-$utopia->get('/v1/storage/files/:fileId')
+ $response->json(['sum' => $projectDB->getSum(), 'files' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/storage/files/:fileId')
->desc('Get File')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -310,20 +202,21 @@ $utopia->get('/v1/storage/files/:fileId')
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
- ->action(
- function ($fileId) use ($response, $projectDB) {
- $file = $projectDB->getDocument($fileId);
+ ->param('fileId', '', new UID(), 'File unique ID.')
+ ->action(function ($fileId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
+ $file = $projectDB->getDocument($fileId);
- $response->json($file->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']));
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
}
- );
-$utopia->get('/v1/storage/files/:fileId/preview')
+ $response->json($file->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']));
+ }, ['response', 'projectDB']);
+
+App::get('/v1/storage/files/:fileId/preview')
->desc('Get File Preview')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -333,120 +226,123 @@ $utopia->get('/v1/storage/files/:fileId/preview')
->label('sdk.description', '/docs/references/storage/get-file-preview.md')
->label('sdk.response.type', 'image/*')
->label('sdk.methodType', 'location')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID')
- ->param('width', 0, function () { return new Range(0, 4000); }, 'Resize preview image width, Pass an integer between 0 to 4000.', true)
- ->param('height', 0, function () { return new Range(0, 4000); }, 'Resize preview image height, Pass an integer between 0 to 4000.', true)
- ->param('quality', 100, function () { return new Range(0, 100); }, 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
- ->param('background', '', function () { return new HexColor(); }, 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
- ->param('output', null, function () use ($outputs) { return new WhiteList(\array_merge(\array_keys($outputs), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true)
- ->action(
- function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs, $fileLogos) {
- $storage = 'local';
+ ->param('fileId', '', new UID(), 'File unique ID')
+ ->param('width', 0, new Range(0, 4000), 'Resize preview image width, Pass an integer between 0 to 4000.', true)
+ ->param('height', 0, new Range(0, 4000), 'Resize preview image height, Pass an integer between 0 to 4000.', true)
+ ->param('quality', 100, new Range(0, 100), 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
+ ->param('background', '', new HexColor(), 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true)
+ ->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
+ ->action(function ($fileId, $width, $height, $quality, $background, $output, $request, $response, $project, $projectDB) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Database $projectDB */
- if (!\extension_loaded('imagick')) {
- throw new Exception('Imagick extension is missing', 500);
- }
+ $storage = 'files';
- if (!Storage::exists($storage)) {
- throw new Exception('No such storage device', 400);
- }
+ if (!\extension_loaded('imagick')) {
+ throw new Exception('Imagick extension is missing', 500);
+ }
- if ((\strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
- $output = 'jpg';
- }
+ if (!Storage::exists($storage)) {
+ throw new Exception('No such storage device', 400);
+ }
- $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
- $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output);
+ if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
+ $output = 'jpg';
+ }
- $file = $projectDB->getDocument($fileId);
+ $inputs = Config::getParam('storage-inputs');
+ $outputs = Config::getParam('storage-outputs');
+ $fileLogos = Config::getParam('storage-logos');
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
+ $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
+ $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output);
- $path = $file->getAttribute('path');
+ $file = $projectDB->getDocument($fileId);
+
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
+ }
+
+ $path = $file->getAttribute('path');
+ $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
+ $algorithm = $file->getAttribute('algorithm');
+ $cipher = $file->getAttribute('fileOpenSSLCipher');
+ $mime = $file->getAttribute('mimeType');
+
+ if (!\in_array($mime, $inputs)) {
+ $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
+ $algorithm = null;
+ $cipher = null;
+ $background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
- $algorithm = $file->getAttribute('algorithm');
- $cipher = $file->getAttribute('fileOpenSSLCipher');
- $mime = $file->getAttribute('mimeType');
+ $key = \md5($path.$width.$height.$quality.$background.$storage.$output);
+ }
- if (!\in_array($mime, $inputs)) {
- $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default'];
- $algorithm = null;
- $cipher = null;
- $background = (empty($background)) ? 'eceff1' : $background;
- $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
- $key = \md5($path.$width.$height.$quality.$background.$storage.$output);
- }
+ $compressor = new GZIP();
+ $device = Storage::getDevice('files');
- $compressor = new GZIP();
- $device = Storage::getDevice('local');
+ if (!\file_exists($path)) {
+ throw new Exception('File not found', 404);
+ }
- if (!\file_exists($path)) {
- throw new Exception('File not found', 404);
- }
-
- $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size
- $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
-
- if ($data) {
- $output = (empty($output)) ? $type : $output;
-
- $response
- ->setContentType((\in_array($output, $outputs)) ? $outputs[$output] : $outputs['jpg'])
- ->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'hit')
- ->send($data)
- ;
-
- return;
- }
-
- $source = $device->read($path);
-
- if (!empty($cipher)) { // Decrypt
- $source = OpenSSL::decrypt(
- $source,
- $file->getAttribute('fileOpenSSLCipher'),
- $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
- 0,
- \hex2bin($file->getAttribute('fileOpenSSLIV')),
- \hex2bin($file->getAttribute('fileOpenSSLTag'))
- );
- }
-
- if (!empty($algorithm)) {
- $source = $compressor->decompress($source);
- }
-
- $resize = new Resize($source);
-
- $resize->crop((int) $width, (int) $height);
-
- if (!empty($background)) {
- $resize->setBackground('#'.$background);
- }
+ $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size
+ $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
+ if ($data) {
$output = (empty($output)) ? $type : $output;
- $response
- ->setContentType($outputs[$output])
+ return $response
+ ->setContentType((\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'])
->addHeader('Expires', $date)
- ->addHeader('X-Appwrite-Cache', 'miss')
- ->send('')
+ ->addHeader('X-Appwrite-Cache', 'hit')
+ ->send($data)
;
-
- $data = $resize->output($output, $quality);
-
- $cache->save($key, $data);
-
- echo $data;
-
- unset($resize);
}
- );
-$utopia->get('/v1/storage/files/:fileId/download')
+ $source = $device->read($path);
+
+ if (!empty($cipher)) { // Decrypt
+ $source = OpenSSL::decrypt(
+ $source,
+ $file->getAttribute('fileOpenSSLCipher'),
+ App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
+ 0,
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
+ );
+ }
+
+ if (!empty($algorithm)) {
+ $source = $compressor->decompress($source);
+ }
+
+ $resize = new Resize($source);
+
+ $resize->crop((int) $width, (int) $height);
+
+ if (!empty($background)) {
+ $resize->setBackground('#'.$background);
+ }
+
+ $output = (empty($output)) ? $type : $output;
+
+ $data = $resize->output($output, $quality);
+
+ $cache->save($key, $data);
+
+ $response
+ ->setContentType($outputs[$output])
+ ->addHeader('Expires', $date)
+ ->addHeader('X-Appwrite-Cache', 'miss')
+ ->send($data)
+ ;
+
+ unset($resize);
+ }, ['request', 'response', 'project', 'projectDB']);
+
+App::get('/v1/storage/files/:fileId/download')
->desc('Get File for Download')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -456,51 +352,52 @@ $utopia->get('/v1/storage/files/:fileId/download')
->label('sdk.description', '/docs/references/storage/get-file-download.md')
->label('sdk.response.type', '*')
->label('sdk.methodType', 'location')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
- ->action(
- function ($fileId) use ($response, $request, $projectDB) {
- $file = $projectDB->getDocument($fileId);
+ ->param('fileId', '', new UID(), 'File unique ID.')
+ ->action(function ($fileId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
+ $file = $projectDB->getDocument($fileId);
- $path = $file->getAttribute('path', '');
-
- if (!\file_exists($path)) {
- throw new Exception('File not found in '.$path, 404);
- }
-
- $compressor = new GZIP();
- $device = Storage::getDevice('local');
-
- $source = $device->read($path);
-
- if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
- $source = OpenSSL::decrypt(
- $source,
- $file->getAttribute('fileOpenSSLCipher'),
- $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
- 0,
- \hex2bin($file->getAttribute('fileOpenSSLIV')),
- \hex2bin($file->getAttribute('fileOpenSSLTag'))
- );
- }
-
- $source = $compressor->decompress($source);
-
- // Response
- $response
- ->setContentType($file->getAttribute('mimeType'))
- ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"')
- ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->addHeader('X-Peak', \memory_get_peak_usage())
- ->send($source)
- ;
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
}
- );
-$utopia->get('/v1/storage/files/:fileId/view')
+ $path = $file->getAttribute('path', '');
+
+ if (!\file_exists($path)) {
+ throw new Exception('File not found in '.$path, 404);
+ }
+
+ $compressor = new GZIP();
+ $device = Storage::getDevice('files');
+
+ $source = $device->read($path);
+
+ if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
+ $source = OpenSSL::decrypt(
+ $source,
+ $file->getAttribute('fileOpenSSLCipher'),
+ App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
+ 0,
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
+ );
+ }
+
+ $source = $compressor->decompress($source);
+
+ // Response
+ $response
+ ->setContentType($file->getAttribute('mimeType'))
+ ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"')
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->addHeader('X-Peak', \memory_get_peak_usage())
+ ->send($source)
+ ;
+ }, ['response', 'projectDB']);
+
+App::get('/v1/storage/files/:fileId/view')
->desc('Get File for View')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -510,68 +407,70 @@ $utopia->get('/v1/storage/files/:fileId/view')
->label('sdk.description', '/docs/references/storage/get-file-view.md')
->label('sdk.response.type', '*')
->label('sdk.methodType', 'location')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
- ->param('as', '', function () { return new WhiteList(['pdf', /*'html',*/ 'text']); }, 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true)
- ->action(
- function ($fileId, $as) use ($response, $request, $projectDB, $mimes) {
- $file = $projectDB->getDocument($fileId);
+ ->param('fileId', '', new UID(), 'File unique ID.')
+ ->param('as', '', new WhiteList(['pdf', /*'html',*/ 'text'], true), 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true)
+ ->action(function ($fileId, $as, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
+ $file = $projectDB->getDocument($fileId);
+ $mimes = Config::getParam('storage-mimes');
- $path = $file->getAttribute('path', '');
-
- if (!\file_exists($path)) {
- throw new Exception('File not found in '.$path, 404);
- }
-
- $compressor = new GZIP();
- $device = Storage::getDevice('local');
-
- $contentType = 'text/plain';
-
- if (\in_array($file->getAttribute('mimeType'), $mimes)) {
- $contentType = $file->getAttribute('mimeType');
- }
-
- $source = $device->read($path);
-
- if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
- $source = OpenSSL::decrypt(
- $source,
- $file->getAttribute('fileOpenSSLCipher'),
- $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
- 0,
- \hex2bin($file->getAttribute('fileOpenSSLIV')),
- \hex2bin($file->getAttribute('fileOpenSSLTag'))
- );
- }
-
- $output = $compressor->decompress($source);
- $fileName = $file->getAttribute('name', '');
-
- $contentTypes = [
- 'pdf' => 'application/pdf',
- 'text' => 'text/plain',
- ];
-
- $contentType = (\array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType;
-
- // Response
- $response
- ->setContentType($contentType)
- ->addHeader('Content-Security-Policy', 'script-src none;')
- ->addHeader('X-Content-Type-Options', 'nosniff')
- ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"')
- ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
- ->addHeader('X-Peak', \memory_get_peak_usage())
- ->send($output)
- ;
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
}
- );
-$utopia->put('/v1/storage/files/:fileId')
+ $path = $file->getAttribute('path', '');
+
+ if (!\file_exists($path)) {
+ throw new Exception('File not found in '.$path, 404);
+ }
+
+ $compressor = new GZIP();
+ $device = Storage::getDevice('files');
+
+ $contentType = 'text/plain';
+
+ if (\in_array($file->getAttribute('mimeType'), $mimes)) {
+ $contentType = $file->getAttribute('mimeType');
+ }
+
+ $source = $device->read($path);
+
+ if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
+ $source = OpenSSL::decrypt(
+ $source,
+ $file->getAttribute('fileOpenSSLCipher'),
+ App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
+ 0,
+ \hex2bin($file->getAttribute('fileOpenSSLIV')),
+ \hex2bin($file->getAttribute('fileOpenSSLTag'))
+ );
+ }
+
+ $output = $compressor->decompress($source);
+ $fileName = $file->getAttribute('name', '');
+
+ $contentTypes = [
+ 'pdf' => 'application/pdf',
+ 'text' => 'text/plain',
+ ];
+
+ $contentType = (\array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType;
+
+ // Response
+ $response
+ ->setContentType($contentType)
+ ->addHeader('Content-Security-Policy', 'script-src none;')
+ ->addHeader('X-Content-Type-Options', 'nosniff')
+ ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"')
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
+ ->addHeader('X-Peak', \memory_get_peak_usage())
+ ->send($output)
+ ;
+ }, ['response', 'projectDB']);
+
+App::put('/v1/storage/files/:fileId')
->desc('Update File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@@ -580,44 +479,46 @@ $utopia->put('/v1/storage/files/:fileId')
->label('sdk.namespace', 'storage')
->label('sdk.method', 'updateFile')
->label('sdk.description', '/docs/references/storage/update-file.md')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
- ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
- //->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true)
- ->action(
- function ($fileId, $read, $write, $folderId = '') use ($response, $projectDB, $audit, $webhook) {
- $file = $projectDB->getDocument($fileId);
+ ->param('fileId', '', new UID(), 'File unique ID.')
+ ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
+ ->action(function ($fileId, $read, $write, $response, $projectDB, $webhooks, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
+ $file = $projectDB->getDocument($fileId);
- $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [
- '$permissions' => [
- 'read' => $read,
- 'write' => $write,
- ],
- 'folderId' => $folderId,
- ]));
-
- if (false === $file) {
- throw new Exception('Failed saving file to DB', 500);
- }
-
- $webhook
- ->setParam('payload', $file->getArrayCopy())
- ;
-
- $audit
- ->setParam('event', 'storage.files.update')
- ->setParam('resource', 'storage/files/'.$file->getId())
- ;
-
- $response->json($file->getArrayCopy());
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
}
- );
-$utopia->delete('/v1/storage/files/:fileId')
+ $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [
+ '$permissions' => [
+ 'read' => $read,
+ 'write' => $write,
+ ],
+ 'folderId' => '',
+ ]));
+
+ if (false === $file) {
+ throw new Exception('Failed saving file to DB', 500);
+ }
+
+ $webhooks
+ ->setParam('payload', $file->getArrayCopy())
+ ;
+
+ $audits
+ ->setParam('event', 'storage.files.update')
+ ->setParam('resource', 'storage/files/'.$file->getId())
+ ;
+
+ $response->json($file->getArrayCopy());
+ }, ['response', 'projectDB', 'webhooks', 'audits']);
+
+App::delete('/v1/storage/files/:fileId')
->desc('Delete File')
->groups(['api', 'storage'])
->label('scope', 'files.write')
@@ -626,41 +527,45 @@ $utopia->delete('/v1/storage/files/:fileId')
->label('sdk.namespace', 'storage')
->label('sdk.method', 'deleteFile')
->label('sdk.description', '/docs/references/storage/delete-file.md')
- ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
- ->action(
- function ($fileId) use ($response, $projectDB, $webhook, $audit, $usage) {
- $file = $projectDB->getDocument($fileId);
+ ->param('fileId', '', new UID(), 'File unique ID.')
+ ->action(function ($fileId, $response, $projectDB, $webhooks, $audits, $usage) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $usage */
+
+ $file = $projectDB->getDocument($fileId);
- if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
- throw new Exception('File not found', 404);
- }
-
- $device = Storage::getDevice('local');
-
- if ($device->delete($file->getAttribute('path', ''))) {
- if (!$projectDB->deleteDocument($fileId)) {
- throw new Exception('Failed to remove file from DB', 500);
- }
- }
-
- $webhook
- ->setParam('payload', $file->getArrayCopy())
- ;
-
- $audit
- ->setParam('event', 'storage.files.delete')
- ->setParam('resource', 'storage/files/'.$file->getId())
- ;
-
- $usage
- ->setParam('storage', $file->getAttribute('size', 0) * -1)
- ;
-
- $response->noContent();
+ if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
+ throw new Exception('File not found', 404);
}
- );
-// $utopia->get('/v1/storage/files/:fileId/scan')
+ $device = Storage::getDevice('files');
+
+ if ($device->delete($file->getAttribute('path', ''))) {
+ if (!$projectDB->deleteDocument($fileId)) {
+ throw new Exception('Failed to remove file from DB', 500);
+ }
+ }
+
+ $webhooks
+ ->setParam('payload', $file->getArrayCopy())
+ ;
+
+ $audits
+ ->setParam('event', 'storage.files.delete')
+ ->setParam('resource', 'storage/files/'.$file->getId())
+ ;
+
+ $usage
+ ->setParam('storage', $file->getAttribute('size', 0) * -1)
+ ;
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'webhooks', 'audits', 'usage']);
+
+// App::get('/v1/storage/files/:fileId/scan')
// ->desc('Scan Storage')
// ->groups(['api', 'storage'])
// ->label('scope', 'god')
@@ -668,8 +573,8 @@ $utopia->delete('/v1/storage/files/:fileId')
// ->label('sdk.namespace', 'storage')
// ->label('sdk.method', 'getFileScan')
// ->label('sdk.hide', true)
-// ->param('fileId', '', function () { return new UID(); }, 'File unique ID.')
-// ->param('storage', 'local', function () { return new WhiteList(['local']);})
+// ->param('fileId', '', new UID(), 'File unique ID.')
+// ->param('storage', 'files', new WhiteList(['files']);})
// ->action(
// function ($fileId, $storage) use ($response, $request, $projectDB) {
// $file = $projectDB->getDocument($fileId);
@@ -693,7 +598,7 @@ $utopia->delete('/v1/storage/files/:fileId')
// $source = OpenSSL::decrypt(
// $source,
// $file->getAttribute('fileOpenSSLCipher'),
-// $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
+// App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
// 0,
// hex2bin($file->getAttribute('fileOpenSSLIV')),
// hex2bin($file->getAttribute('fileOpenSSLTag'))
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index d2e8cda0ea..ae9e44015f 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -1,9 +1,7 @@
post('/v1/teams')
+App::post('/v1/teams')
->desc('Create Team')
->groups(['api', 'teams'])
->label('scope', 'teams.write')
@@ -29,63 +27,66 @@ $utopia->post('/v1/teams')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/teams/create-team.md')
- ->param('name', null, function () { return new Text(100); }, 'Team name.')
- ->param('roles', ['owner'], function () { return new ArrayList(new Key()); }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
- ->action(
- function ($name, $roles) use ($response, $projectDB, $user, $mode) {
- Authorization::disable();
+ ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
+ ->param('roles', ['owner'], new ArrayList(new Key()), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.', true)
+ ->action(function ($name, $roles, $response, $user, $projectDB, $mode) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var bool $mode */
- $team = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_TEAMS,
+ Authorization::disable();
+
+ $team = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_TEAMS,
+ '$permissions' => [
+ 'read' => ['team:{self}'],
+ 'write' => ['team:{self}/owner'],
+ ],
+ 'name' => $name,
+ 'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0,
+ 'dateCreated' => \time(),
+ ]);
+
+ Authorization::reset();
+
+ if (false === $team) {
+ throw new Exception('Failed saving team to DB', 500);
+ }
+
+ if ($mode !== APP_MODE_ADMIN && $user->getId()) { // Don't add user on server mode
+ $membership = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'$permissions' => [
- 'read' => ['team:{self}'],
- 'write' => ['team:{self}/owner'],
+ 'read' => ['user:'.$user->getId(), 'team:'.$team->getId()],
+ 'write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'],
],
- 'name' => $name,
- 'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0,
- 'dateCreated' => \time(),
+ 'userId' => $user->getId(),
+ 'teamId' => $team->getId(),
+ 'roles' => $roles,
+ 'invited' => \time(),
+ 'joined' => \time(),
+ 'confirm' => true,
+ 'secret' => '',
]);
- Authorization::reset();
+ // Attach user to team
+ $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
- if (false === $team) {
- throw new Exception('Failed saving team to DB', 500);
+ $user = $projectDB->updateDocument($user->getArrayCopy());
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
}
-
- if ($mode !== APP_MODE_ADMIN && $user->getId()) { // Don't add user on app/server mode
- $membership = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
- '$permissions' => [
- 'read' => ['user:'.$user->getId(), 'team:'.$team->getId()],
- 'write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'],
- ],
- 'userId' => $user->getId(),
- 'teamId' => $team->getId(),
- 'roles' => $roles,
- 'invited' => \time(),
- 'joined' => \time(),
- 'confirm' => true,
- 'secret' => '',
- ]);
-
- // Attach user to team
- $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
-
- $user = $projectDB->updateDocument($user->getArrayCopy());
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($team->getArrayCopy())
- ;
}
- );
-$utopia->get('/v1/teams')
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json($team->getArrayCopy())
+ ;
+ }, ['response', 'user', 'projectDB', 'mode']);
+
+App::get('/v1/teams')
->desc('List Teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -93,29 +94,30 @@ $utopia->get('/v1/teams')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/teams/list-teams.md')
- ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
- ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
- ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
- ->action(
- function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
- $results = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => 'dateCreated',
- 'orderType' => $orderType,
- 'orderCast' => 'int',
- 'search' => $search,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_TEAMS,
- ],
- ]);
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- $response->json(['sum' => $projectDB->getSum(), 'teams' => $results]);
- }
- );
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'dateCreated',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_TEAMS,
+ ],
+ ]);
-$utopia->get('/v1/teams/:teamId')
+ $response->json(['sum' => $projectDB->getSum(), 'teams' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/teams/:teamId')
->desc('Get Team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -123,20 +125,21 @@ $utopia->get('/v1/teams/:teamId')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/teams/get-team.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->action(
- function ($teamId) use ($response, $projectDB) {
- $team = $projectDB->getDocument($teamId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->action(function ($teamId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
- $response->json($team->getArrayCopy([]));
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
}
- );
-$utopia->put('/v1/teams/:teamId')
+ $response->json($team->getArrayCopy([]));
+ }, ['response', 'projectDB']);
+
+App::put('/v1/teams/:teamId')
->desc('Update Team')
->groups(['api', 'teams'])
->label('scope', 'teams.write')
@@ -144,29 +147,30 @@ $utopia->put('/v1/teams/:teamId')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/teams/update-team.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('name', null, function () { return new Text(100); }, 'Team name.')
- ->action(
- function ($teamId, $name) use ($response, $projectDB) {
- $team = $projectDB->getDocument($teamId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
+ ->action(function ($teamId, $name, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
- $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
- 'name' => $name,
- ]));
-
- if (false === $team) {
- throw new Exception('Failed saving team to DB', 500);
- }
-
- $response->json($team->getArrayCopy());
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
}
- );
-$utopia->delete('/v1/teams/:teamId')
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
+ 'name' => $name,
+ ]));
+
+ if (false === $team) {
+ throw new Exception('Failed saving team to DB', 500);
+ }
+
+ $response->json($team->getArrayCopy());
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/teams/:teamId')
->desc('Delete Team')
->groups(['api', 'teams'])
->label('scope', 'teams.write')
@@ -174,39 +178,40 @@ $utopia->delete('/v1/teams/:teamId')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/teams/delete-team.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->action(
- function ($teamId) use ($response, $projectDB) {
- $team = $projectDB->getDocument($teamId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->action(function ($teamId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
- $memberships = $projectDB->getCollection([
- 'limit' => 2000, // TODO add members limit
- 'offset' => 0,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
- 'teamId='.$teamId,
- ],
- ]);
-
- foreach ($memberships as $member) {
- if (!$projectDB->deleteDocument($member->getId())) {
- throw new Exception('Failed to remove membership for team from DB', 500);
- }
- }
-
- if (!$projectDB->deleteDocument($teamId)) {
- throw new Exception('Failed to remove team from DB', 500);
- }
-
- $response->noContent();
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
}
- );
-$utopia->post('/v1/teams/:teamId/memberships')
+ $memberships = $projectDB->getCollection([
+ 'limit' => 2000, // TODO add members limit
+ 'offset' => 0,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
+ 'teamId='.$teamId,
+ ],
+ ]);
+
+ foreach ($memberships as $member) {
+ if (!$projectDB->deleteDocument($member->getId())) {
+ throw new Exception('Failed to remove membership for team from DB', 500);
+ }
+ }
+
+ if (!$projectDB->deleteDocument($teamId)) {
+ throw new Exception('Failed to remove team from DB', 500);
+ }
+
+ $response->noContent();
+ }, ['response', 'projectDB']);
+
+App::post('/v1/teams/:teamId/memberships')
->desc('Create Team Membership')
->groups(['api', 'teams'])
->label('scope', 'teams.write')
@@ -214,189 +219,196 @@ $utopia->post('/v1/teams/:teamId/memberships')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'createMembership')
->label('sdk.description', '/docs/references/teams/create-team-membership.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('email', '', function () { return new Email(); }, 'New team member email.')
- ->param('name', '', function () { return new Text(100); }, 'New team member name.', true)
- ->param('roles', [], function () { return new ArrayList(new Key()); }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
- ->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation 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.') // TODO add our own built-in confirm page
- ->action(
- function ($teamId, $email, $name, $roles, $url) use ($response, $mail, $project, $user, $audit, $projectDB, &$mode) {
- $name = (empty($name)) ? $email : $name;
- $team = $projectDB->getDocument($teamId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('email', '', new Email(), 'New team member email.')
+ ->param('name', '', new Text(128), 'New team member name. Max length: 128 chars.', true)
+ ->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
+ ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation 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']) // TODO add our own built-in confirm page
+ ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $projectDB, $locale, $audits, $mails, $mode) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $mails */
+ /** @var bool $mode */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $name = (empty($name)) ? $email : $name;
+ $team = $projectDB->getDocument($teamId);
- $memberships = $projectDB->getCollection([
- 'limit' => 50,
- 'offset' => 0,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
- 'teamId='.$team->getId(),
- ],
- ]);
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
+ }
- $invitee = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
+ $memberships = $projectDB->getCollection([
+ 'limit' => 50,
+ 'offset' => 0,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
+ 'teamId='.$team->getId(),
+ ],
+ ]);
- if (empty($invitee)) { // Create new user if no user with same email found
+ $invitee = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
- Authorization::disable();
+ if (empty($invitee)) { // Create new user if no user with same email found
- try {
- $invitee = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_USERS,
- '$permissions' => [
- 'read' => ['user:{self}', '*'],
- 'write' => ['user:{self}'],
- ],
- 'email' => $email,
- 'emailVerification' => false,
- 'status' => Auth::USER_STATUS_UNACTIVATED,
- 'password' => Auth::passwordHash(Auth::passwordGenerator()),
- 'password-update' => \time(),
- 'registration' => \time(),
- 'reset' => false,
- 'name' => $name,
- 'tokens' => [],
- ], ['email' => $email]);
- } catch (Duplicate $th) {
- throw new Exception('Account already exists', 409);
- }
+ Authorization::disable();
- Authorization::reset();
-
- if (false === $invitee) {
- throw new Exception('Failed saving user to DB', 500);
- }
- }
-
- $isOwner = false;
-
- foreach ($memberships as $member) {
- if ($member->getAttribute('userId') == $invitee->getId()) {
- throw new Exception('User has already been invited or is already a member of this team', 409);
- }
-
- if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) {
- $isOwner = true;
- }
- }
-
- if (!$isOwner && APP_MODE_ADMIN !== $mode && $user->getId()) { // Not owner, not admin, not app (server)
- throw new Exception('User is not allowed to send invitations for this team', 401);
- }
-
- $secret = Auth::tokenGenerator();
-
- $membership = new Document([
- '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
- '$permissions' => [
- 'read' => ['*'],
- 'write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
- ],
- 'userId' => $invitee->getId(),
- 'teamId' => $team->getId(),
- 'roles' => $roles,
- 'invited' => \time(),
- 'joined' => (APP_MODE_ADMIN === $mode || !$user->getId()) ? \time() : 0,
- 'confirm' => (APP_MODE_ADMIN === $mode || !$user->getId()),
- 'secret' => Auth::hash($secret),
- ]);
-
- if (APP_MODE_ADMIN === $mode || !$user->getId()) { // Allow admin to create membership
- Authorization::disable();
- $membership = $projectDB->createDocument($membership->getArrayCopy());
-
- $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
- 'sum' => $team->getAttribute('sum', 0) + 1,
- ]));
-
- // Attach user to team
- $invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
-
- $invitee = $projectDB->updateDocument($invitee->getArrayCopy());
-
- if (false === $invitee) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- Authorization::reset();
- } else {
- $membership = $projectDB->createDocument($membership->getArrayCopy());
- }
-
- if (false === $membership) {
- throw new Exception('Failed saving membership to DB', 500);
- }
-
- $url = Template::parseURL($url);
- $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
- $url = Template::unParseURL($url);
-
- $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl');
- $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.invitation.body'));
- $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl');
-
- $body
- ->setParam('{{content}}', $content->render())
- ->setParam('{{cta}}', $cta->render())
- ->setParam('{{title}}', Locale::getText('account.emails.invitation.title'))
- ->setParam('{{direction}}', Locale::getText('settings.direction'))
- ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
- ->setParam('{{team}}', $team->getAttribute('name', '[TEAM-NAME]'))
- ->setParam('{{owner}}', $user->getAttribute('name', ''))
- ->setParam('{{redirect}}', $url)
- ->setParam('{{bg-body}}', '#f6f6f6')
- ->setParam('{{bg-content}}', '#ffffff')
- ->setParam('{{bg-cta}}', '#3498db')
- ->setParam('{{bg-cta-hover}}', '#34495e')
- ->setParam('{{text-content}}', '#000000')
- ->setParam('{{text-cta}}', '#ffffff')
- ;
-
- if (APP_MODE_ADMIN !== $mode && $user->getId()) { // No need in comfirmation when in admin or app mode
- $mail
- ->setParam('event', 'teams.membership.create')
- ->setParam('recipient', $email)
- ->setParam('name', $name)
- ->setParam('subject', \sprintf(Locale::getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]'])))
- ->setParam('body', $body->render())
- ->trigger();
- ;
- }
-
- $audit
- ->setParam('userId', $invitee->getId())
- ->setParam('event', 'teams.membership.create')
- ->setParam('resource', 'teams/'.$teamId)
- ;
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint
- ->json(\array_merge($membership->getArrayCopy([
- '$id',
- 'userId',
- 'teamId',
- 'roles',
- 'invited',
- 'joined',
- 'confirm',
- ]), [
+ try {
+ $invitee = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_USERS,
+ '$permissions' => [
+ 'read' => ['user:{self}', '*'],
+ 'write' => ['user:{self}'],
+ ],
'email' => $email,
+ 'emailVerification' => false,
+ 'status' => Auth::USER_STATUS_UNACTIVATED,
+ 'password' => Auth::passwordHash(Auth::passwordGenerator()),
+ 'password-update' => \time(),
+ 'registration' => \time(),
+ 'reset' => false,
'name' => $name,
- ]))
+ 'tokens' => [],
+ ], ['email' => $email]);
+ } catch (Duplicate $th) {
+ throw new Exception('Account already exists', 409);
+ }
+
+ Authorization::reset();
+
+ if (false === $invitee) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+ }
+
+ $isOwner = false;
+
+ foreach ($memberships as $member) {
+ if ($member->getAttribute('userId') == $invitee->getId()) {
+ throw new Exception('User has already been invited or is already a member of this team', 409);
+ }
+
+ if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) {
+ $isOwner = true;
+ }
+ }
+
+ if (!$isOwner && APP_MODE_ADMIN !== $mode && $user->getId()) { // Not owner, not admin, not app (server)
+ throw new Exception('User is not allowed to send invitations for this team', 401);
+ }
+
+ $secret = Auth::tokenGenerator();
+
+ $membership = new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
+ '$permissions' => [
+ 'read' => ['*'],
+ 'write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
+ ],
+ 'userId' => $invitee->getId(),
+ 'teamId' => $team->getId(),
+ 'roles' => $roles,
+ 'invited' => \time(),
+ 'joined' => (APP_MODE_ADMIN === $mode || !$user->getId()) ? \time() : 0,
+ 'confirm' => (APP_MODE_ADMIN === $mode || !$user->getId()),
+ 'secret' => Auth::hash($secret),
+ ]);
+
+ if (APP_MODE_ADMIN === $mode || !$user->getId()) { // Allow admin to create membership
+ Authorization::disable();
+ $membership = $projectDB->createDocument($membership->getArrayCopy());
+
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
+ 'sum' => $team->getAttribute('sum', 0) + 1,
+ ]));
+
+ // Attach user to team
+ $invitee->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
+
+ $invitee = $projectDB->updateDocument($invitee->getArrayCopy());
+
+ if (false === $invitee) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ Authorization::reset();
+ } else {
+ $membership = $projectDB->createDocument($membership->getArrayCopy());
+ }
+
+ if (false === $membership) {
+ throw new Exception('Failed saving membership to DB', 500);
+ }
+
+ $url = Template::parseURL($url);
+ $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
+ $url = Template::unParseURL($url);
+
+ $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl');
+ $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.invitation.body'));
+ $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl');
+
+ $body
+ ->setParam('{{content}}', $content->render())
+ ->setParam('{{cta}}', $cta->render())
+ ->setParam('{{title}}', $locale->getText('account.emails.invitation.title'))
+ ->setParam('{{direction}}', $locale->getText('settings.direction'))
+ ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
+ ->setParam('{{team}}', $team->getAttribute('name', '[TEAM-NAME]'))
+ ->setParam('{{owner}}', $user->getAttribute('name', ''))
+ ->setParam('{{redirect}}', $url)
+ ->setParam('{{bg-body}}', '#f6f6f6')
+ ->setParam('{{bg-content}}', '#ffffff')
+ ->setParam('{{bg-cta}}', '#3498db')
+ ->setParam('{{bg-cta-hover}}', '#34495e')
+ ->setParam('{{text-content}}', '#000000')
+ ->setParam('{{text-cta}}', '#ffffff')
+ ;
+
+ if (APP_MODE_ADMIN !== $mode && $user->getId()) { // No need in comfirmation when in admin or app mode
+ $mails
+ ->setParam('event', 'teams.membership.create')
+ ->setParam('from', ($project->getId() === 'console') ? '' : \sprintf($locale->getText('account.emails.team'), $project->getAttribute('name')))
+ ->setParam('recipient', $email)
+ ->setParam('name', $name)
+ ->setParam('subject', \sprintf($locale->getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]'])))
+ ->setParam('body', $body->render())
+ ->trigger();
;
}
- );
-$utopia->get('/v1/teams/:teamId/memberships')
+ $audits
+ ->setParam('userId', $invitee->getId())
+ ->setParam('event', 'teams.membership.create')
+ ->setParam('resource', 'teams/'.$teamId)
+ ;
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint
+ ->json(\array_merge($membership->getArrayCopy([
+ '$id',
+ 'userId',
+ 'teamId',
+ 'roles',
+ 'invited',
+ 'joined',
+ 'confirm',
+ ]), [
+ 'email' => $email,
+ 'name' => $name,
+ ]))
+ ;
+ }, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails', 'mode']);
+
+App::get('/v1/teams/:teamId/memberships')
->desc('Get Team Memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -404,57 +416,58 @@ $utopia->get('/v1/teams/:teamId/memberships')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getMemberships')
->label('sdk.description', '/docs/references/teams/get-team-members.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
- ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
- ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
- ->action(
- function ($teamId, $search, $limit, $offset, $orderType) use ($response, $projectDB) {
- $team = $projectDB->getDocument($teamId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
- $memberships = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => 'joined',
- 'orderType' => $orderType,
- 'orderCast' => 'int',
- 'search' => $search,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
- 'teamId='.$teamId,
- ],
- ]);
-
- $users = [];
-
- foreach ($memberships as $membership) {
- if (empty($membership->getAttribute('userId', null))) {
- continue;
- }
-
- $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']);
-
- $users[] = \array_merge($temp, $membership->getArrayCopy([
- '$id',
- 'userId',
- 'teamId',
- 'roles',
- 'invited',
- 'joined',
- 'confirm',
- ]));
- }
-
- $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]);
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
}
- );
-$utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
+ $memberships = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'joined',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
+ 'teamId='.$teamId,
+ ],
+ ]);
+
+ $users = [];
+
+ foreach ($memberships as $membership) {
+ if (empty($membership->getAttribute('userId', null))) {
+ continue;
+ }
+
+ $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']);
+
+ $users[] = \array_merge($temp, $membership->getArrayCopy([
+ '$id',
+ 'userId',
+ 'teamId',
+ 'roles',
+ 'invited',
+ 'joined',
+ 'confirm',
+ ]));
+ }
+
+ $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]);
+ }, ['response', 'projectDB']);
+
+App::patch('/v1/teams/:teamId/memberships/:inviteId/status')
->desc('Update Team Membership Status')
->groups(['api', 'teams'])
->label('scope', 'public')
@@ -462,131 +475,129 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updateMembershipStatus')
->label('sdk.description', '/docs/references/teams/update-team-membership-status.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('inviteId', '', function () { return new UID(); }, 'Invite unique ID.')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->param('secret', '', function () { return new Text(256); }, 'Secret key.')
- ->action(
- function ($teamId, $inviteId, $userId, $secret) use ($response, $request, $user, $audit, $projectDB) {
- $protocol = Config::getParam('protocol');
- $membership = $projectDB->getDocument($inviteId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('inviteId', '', new UID(), 'Invite unique ID.')
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->param('secret', '', new Text(256), 'Secret key.')
+ ->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $projectDB, $audits) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
- throw new Exception('Invite not found', 404);
- }
+ $protocol = $request->getProtocol();
+ $membership = $projectDB->getDocument($inviteId);
- if ($membership->getAttribute('teamId') !== $teamId) {
- throw new Exception('Team IDs don\'t match', 404);
- }
+ if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
+ throw new Exception('Invite not found', 404);
+ }
- Authorization::disable();
+ if ($membership->getAttribute('teamId') !== $teamId) {
+ throw new Exception('Team IDs don\'t match', 404);
+ }
- $team = $projectDB->getDocument($teamId);
-
- Authorization::reset();
+ Authorization::disable();
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
+ $team = $projectDB->getDocument($teamId);
+
+ Authorization::reset();
- if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
- throw new Exception('Secret key not valid', 401);
- }
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
+ }
- if ($userId != $membership->getAttribute('userId')) {
- throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
- }
+ if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
+ throw new Exception('Secret key not valid', 401);
+ }
- if (empty($user->getId())) {
- $user = $projectDB->getCollectionFirst([ // Get user
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- '$id='.$userId,
- ],
- ]);
- }
+ if ($userId != $membership->getAttribute('userId')) {
+ throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
+ }
- if ($membership->getAttribute('userId') !== $user->getId()) {
- throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
- }
+ if (empty($user->getId())) {
+ $user = $projectDB->getCollectionFirst([ // Get user
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ '$id='.$userId,
+ ],
+ ]);
+ }
- $membership // Attach user to team
- ->setAttribute('joined', \time())
- ->setAttribute('confirm', true)
- ;
+ if ($membership->getAttribute('userId') !== $user->getId()) {
+ throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
+ }
- $user
- ->setAttribute('emailVerification', true)
- ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND)
- ;
+ $membership // Attach user to team
+ ->setAttribute('joined', \time())
+ ->setAttribute('confirm', true)
+ ;
- // Log user in
- $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
- $secret = Auth::tokenGenerator();
+ $user
+ ->setAttribute('emailVerification', true)
+ ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND)
+ ;
- $user->setAttribute('tokens', new Document([
- '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
- '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
- 'type' => Auth::TOKEN_TYPE_LOGIN,
- 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
- 'expire' => $expiry,
- 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
- 'ip' => $request->getIP(),
- ]), Document::SET_TYPE_APPEND);
+ // Log user in
+ $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
+ $secret = Auth::tokenGenerator();
- Authorization::setRole('user:'.$userId);
+ $user->setAttribute('tokens', new Document([
+ '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
+ '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]],
+ 'type' => Auth::TOKEN_TYPE_LOGIN,
+ 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
+ 'expire' => $expiry,
+ 'userAgent' => $request->getUserAgent('UNKNOWN'),
+ 'ip' => $request->getIP(),
+ ]), Document::SET_TYPE_APPEND);
- $user = $projectDB->updateDocument($user->getArrayCopy());
+ Authorization::setRole('user:'.$userId);
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
+ $user = $projectDB->updateDocument($user->getArrayCopy());
- Authorization::disable();
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
- $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
- 'sum' => $team->getAttribute('sum', 0) + 1,
- ]));
+ Authorization::disable();
- Authorization::reset();
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
+ 'sum' => $team->getAttribute('sum', 0) + 1,
+ ]));
- if (false === $team) {
- throw new Exception('Failed saving team to DB', 500);
- }
+ Authorization::reset();
- $audit
- ->setParam('userId', $user->getId())
- ->setParam('event', 'teams.membership.update')
- ->setParam('resource', 'teams/'.$teamId)
- ;
+ if (false === $team) {
+ throw new Exception('Failed saving team to DB', 500);
+ }
- if (!Config::getParam('domainVerification')) {
- $response
- ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
- ;
- }
+ $audits
+ ->setParam('userId', $user->getId())
+ ->setParam('event', 'teams.membership.update')
+ ->setParam('resource', 'teams/'.$teamId)
+ ;
+ if (!Config::getParam('domainVerification')) {
$response
- ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
- ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
- ->json(\array_merge($membership->getArrayCopy([
- '$id',
- 'userId',
- 'teamId',
- 'roles',
- 'invited',
- 'joined',
- 'confirm',
- ]), [
- 'email' => $user->getAttribute('email'),
- 'name' => $user->getAttribute('name'),
- ]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
- );
-$utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
+ $response
+ ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ;
+
+ $response->dynamic(new Document(\array_merge($membership->getArrayCopy(), [
+ 'email' => $user->getAttribute('email'),
+ 'name' => $user->getAttribute('name'),
+ ])), Response::MODEL_MEMBERSHIP);
+
+ }, ['request', 'response', 'user', 'projectDB', 'audits']);
+
+App::delete('/v1/teams/:teamId/memberships/:inviteId')
->desc('Delete Team Membership')
->groups(['api', 'teams'])
->label('scope', 'teams.write')
@@ -594,46 +605,48 @@ $utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
->label('sdk.namespace', 'teams')
->label('sdk.method', 'deleteMembership')
->label('sdk.description', '/docs/references/teams/delete-team-membership.md')
- ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
- ->param('inviteId', '', function () { return new UID(); }, 'Invite unique ID.')
- ->action(
- function ($teamId, $inviteId) use ($response, $projectDB, $audit) {
- $membership = $projectDB->getDocument($inviteId);
+ ->param('teamId', '', new UID(), 'Team unique ID.')
+ ->param('inviteId', '', new UID(), 'Invite unique ID.')
+ ->action(function ($teamId, $inviteId, $response, $projectDB, $audits) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $audits */
- if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
- throw new Exception('Invite not found', 404);
- }
+ $membership = $projectDB->getDocument($inviteId);
- if ($membership->getAttribute('teamId') !== $teamId) {
- throw new Exception('Team IDs don\'t match', 404);
- }
-
- $team = $projectDB->getDocument($teamId);
-
- if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
- throw new Exception('Team not found', 404);
- }
-
- if (!$projectDB->deleteDocument($membership->getId())) {
- throw new Exception('Failed to remove membership from DB', 500);
- }
-
- if ($membership->getAttribute('confirm')) { // Count only confirmed members
- $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
- 'sum' => $team->getAttribute('sum', 0) - 1,
- ]));
- }
-
- if (false === $team) {
- throw new Exception('Failed saving team to DB', 500);
- }
-
- $audit
- ->setParam('userId', $membership->getAttribute('userId'))
- ->setParam('event', 'teams.membership.delete')
- ->setParam('resource', 'teams/'.$teamId)
- ;
-
- $response->noContent();
+ if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
+ throw new Exception('Invite not found', 404);
}
- );
+
+ if ($membership->getAttribute('teamId') !== $teamId) {
+ throw new Exception('Team IDs don\'t match', 404);
+ }
+
+ $team = $projectDB->getDocument($teamId);
+
+ if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
+ throw new Exception('Team not found', 404);
+ }
+
+ if (!$projectDB->deleteDocument($membership->getId())) {
+ throw new Exception('Failed to remove membership from DB', 500);
+ }
+
+ if ($membership->getAttribute('confirm')) { // Count only confirmed members
+ $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [
+ 'sum' => $team->getAttribute('sum', 0) - 1,
+ ]));
+ }
+
+ if (false === $team) {
+ throw new Exception('Failed saving team to DB', 500);
+ }
+
+ $audits
+ ->setParam('userId', $membership->getAttribute('userId'))
+ ->setParam('event', 'teams.membership.delete')
+ ->setParam('resource', 'teams/'.$teamId)
+ ;
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'audits']);
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index 74a257d299..21894e3131 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -1,9 +1,7 @@
post('/v1/users')
+App::post('/v1/users')
->desc('Create User')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -29,68 +26,69 @@ $utopia->post('/v1/users')
->label('sdk.namespace', 'users')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/users/create-user.md')
- ->param('email', '', function () { return new Email(); }, 'User email.')
- ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
- ->param('name', '', function () { return new Text(100); }, 'User name.', true)
- ->action(
- function ($email, $password, $name) use ($response, $projectDB) {
- $profile = $projectDB->getCollectionFirst([ // Get user by email address
- 'limit' => 1,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- 'email='.$email,
- ],
- ]);
+ ->param('email', '', new Email(), 'User email.')
+ ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.')
+ ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
+ ->action(function ($email, $password, $name, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (!empty($profile)) {
- throw new Exception('User already registered', 409);
- }
+ $profile = $projectDB->getCollectionFirst([ // Get user by email address
+ 'limit' => 1,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ 'email='.$email,
+ ],
+ ]);
- try {
- $user = $projectDB->createDocument([
- '$collection' => Database::SYSTEM_COLLECTION_USERS,
- '$permissions' => [
- 'read' => ['*'],
- 'write' => ['user:{self}'],
- ],
- 'email' => $email,
- 'emailVerification' => false,
- 'status' => Auth::USER_STATUS_UNACTIVATED,
- 'password' => Auth::passwordHash($password),
- 'password-update' => \time(),
- 'registration' => \time(),
- 'reset' => false,
- 'name' => $name,
- ], ['email' => $email]);
- } catch (Duplicate $th) {
- throw new Exception('Account already exists', 409);
- }
-
- $oauth2Keys = [];
-
- foreach (Config::getParam('providers') as $key => $provider) {
- if (!$provider['enabled']) {
- continue;
- }
-
- $oauth2Keys[] = 'oauth2'.\ucfirst($key);
- $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
- }
-
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json(\array_merge($user->getArrayCopy(\array_merge([
- '$id',
- 'status',
- 'email',
- 'registration',
- 'emailVerification',
- 'name',
- ], $oauth2Keys)), ['roles' => []]));
+ if (!empty($profile)) {
+ throw new Exception('User already registered', 409);
}
- );
-
-$utopia->get('/v1/users')
+
+ try {
+ $user = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_USERS,
+ '$permissions' => [
+ 'read' => ['*'],
+ 'write' => ['user:{self}'],
+ ],
+ 'email' => $email,
+ 'emailVerification' => false,
+ 'status' => Auth::USER_STATUS_UNACTIVATED,
+ 'password' => Auth::passwordHash($password),
+ 'password-update' => \time(),
+ 'registration' => \time(),
+ 'reset' => false,
+ 'name' => $name,
+ ], ['email' => $email]);
+ } catch (Duplicate $th) {
+ throw new Exception('Account already exists', 409);
+ }
+
+ $oauth2Keys = [];
+
+ foreach (Config::getParam('providers') as $key => $provider) {
+ if (!$provider['enabled']) {
+ continue;
+ }
+
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
+ }
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_CREATED)
+ ->json(\array_merge($user->getArrayCopy(\array_merge([
+ '$id',
+ 'status',
+ 'email',
+ 'registration',
+ 'emailVerification',
+ 'name',
+ ], $oauth2Keys)), ['roles' => []]));
+ }, ['response', 'projectDB']);
+
+App::get('/v1/users')
->desc('List Users')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -98,82 +96,39 @@ $utopia->get('/v1/users')
->label('sdk.namespace', 'users')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/users/list-users.md')
- ->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
- ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
- ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
- ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
- ->action(
- function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
- $results = $projectDB->getCollection([
- 'limit' => $limit,
- 'offset' => $offset,
- 'orderField' => 'registration',
- 'orderType' => $orderType,
- 'orderCast' => 'int',
- 'search' => $search,
- 'filters' => [
- '$collection='.Database::SYSTEM_COLLECTION_USERS,
- ],
- ]);
+ ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
+ ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
+ ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
+ ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
+ ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- $oauth2Keys = [];
+ $results = $projectDB->getCollection([
+ 'limit' => $limit,
+ 'offset' => $offset,
+ 'orderField' => 'registration',
+ 'orderType' => $orderType,
+ 'orderCast' => 'int',
+ 'search' => $search,
+ 'filters' => [
+ '$collection='.Database::SYSTEM_COLLECTION_USERS,
+ ],
+ ]);
- foreach (Config::getParam('providers') as $key => $provider) {
- if (!$provider['enabled']) {
- continue;
- }
+ $oauth2Keys = [];
- $oauth2Keys[] = 'oauth2'.\ucfirst($key);
- $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
+ foreach (Config::getParam('providers') as $key => $provider) {
+ if (!$provider['enabled']) {
+ continue;
}
- $results = \array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */
- return $value->getArrayCopy(\array_merge(
- [
- '$id',
- 'status',
- 'email',
- 'registration',
- 'emailVerification',
- 'name',
- ],
- $oauth2Keys
- ));
- }, $results);
-
- $response->json(['sum' => $projectDB->getSum(), 'users' => $results]);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
- );
-$utopia->get('/v1/users/:userId')
- ->desc('Get User')
- ->groups(['api', 'users'])
- ->label('scope', 'users.read')
- ->label('sdk.platform', [APP_PLATFORM_SERVER])
- ->label('sdk.namespace', 'users')
- ->label('sdk.method', 'get')
- ->label('sdk.description', '/docs/references/users/get-user.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->action(
- function ($userId) use ($response, $projectDB) {
- $user = $projectDB->getDocument($userId);
-
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
-
- $oauth2Keys = [];
-
- foreach (Config::getParam('providers') as $key => $provider) {
- if (!$provider['enabled']) {
- continue;
- }
-
- $oauth2Keys[] = 'oauth2'.\ucfirst($key);
- $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
- }
-
- $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ $results = \array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */
+ return $value->getArrayCopy(\array_merge(
[
'$id',
'status',
@@ -183,11 +138,56 @@ $utopia->get('/v1/users/:userId')
'name',
],
$oauth2Keys
- )), ['roles' => []]));
- }
- );
+ ));
+ }, $results);
-$utopia->get('/v1/users/:userId/prefs')
+ $response->json(['sum' => $projectDB->getSum(), 'users' => $results]);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/users/:userId')
+ ->desc('Get User')
+ ->groups(['api', 'users'])
+ ->label('scope', 'users.read')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'users')
+ ->label('sdk.method', 'get')
+ ->label('sdk.description', '/docs/references/users/get-user.md')
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->action(function ($userId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+
+ $user = $projectDB->getDocument($userId);
+
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
+ }
+
+ $oauth2Keys = [];
+
+ foreach (Config::getParam('providers') as $key => $provider) {
+ if (!$provider['enabled']) {
+ continue;
+ }
+
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
+ }
+
+ $response->json(\array_merge($user->getArrayCopy(\array_merge(
+ [
+ '$id',
+ 'status',
+ 'email',
+ 'registration',
+ 'emailVerification',
+ 'name',
+ ],
+ $oauth2Keys
+ )), ['roles' => []]));
+ }, ['response', 'projectDB']);
+
+App::get('/v1/users/:userId/prefs')
->desc('Get User Preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -195,29 +195,30 @@ $utopia->get('/v1/users/:userId/prefs')
->label('sdk.namespace', 'users')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/users/get-user-prefs.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->action(
- function ($userId) use ($response, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->action(function ($userId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $prefs = $user->getAttribute('prefs', '');
-
- try {
- $prefs = \json_decode($prefs, true);
- $prefs = ($prefs) ? $prefs : [];
- } catch (\Exception $error) {
- throw new Exception('Failed to parse prefs', 500);
- }
-
- $response->json($prefs);
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
-$utopia->get('/v1/users/:userId/sessions')
+ $prefs = $user->getAttribute('prefs', '');
+
+ try {
+ $prefs = \json_decode($prefs, true);
+ $prefs = ($prefs) ? $prefs : [];
+ } catch (\Exception $error) {
+ throw new Exception('Failed to parse prefs', 500);
+ }
+
+ $response->json($prefs);
+ }, ['response', 'projectDB']);
+
+App::get('/v1/users/:userId/sessions')
->desc('Get User Sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -225,63 +226,65 @@ $utopia->get('/v1/users/:userId/sessions')
->label('sdk.namespace', 'users')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/users/get-user-sessions.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->action(
- function ($userId) use ($response, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->action(function ($userId, $response, $projectDB, $locale, $geodb) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var GeoIp2\Database\Reader $geodb */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $tokens = $user->getAttribute('tokens', []);
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $sessions = [];
- $index = 0;
- $countries = Locale::getText('countries');
-
- foreach ($tokens as $token) { /* @var $token Document */
- if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
- continue;
- }
-
- $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
-
- $dd = new DeviceDetector($userAgent);
-
- // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
- // $dd->skipBotDetection();
-
- $dd->parse();
-
- $sessions[$index] = [
- '$id' => $token->getId(),
- 'OS' => $dd->getOs(),
- 'client' => $dd->getClient(),
- 'device' => $dd->getDevice(),
- 'brand' => $dd->getBrand(),
- 'model' => $dd->getModel(),
- 'ip' => $token->getAttribute('ip', ''),
- 'geo' => [],
- ];
-
- try {
- $record = $reader->country($token->getAttribute('ip', ''));
- $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
- $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- } catch (\Exception $e) {
- $sessions[$index]['geo']['isoCode'] = '--';
- $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
- }
-
- ++$index;
- }
-
- $response->json($sessions);
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
-$utopia->get('/v1/users/:userId/logs')
+ $tokens = $user->getAttribute('tokens', []);
+ $sessions = [];
+ $index = 0;
+ $countries = $locale->getText('countries');
+
+ foreach ($tokens as $token) { /* @var $token Document */
+ if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
+ continue;
+ }
+
+ $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
+
+ $dd = new DeviceDetector($userAgent);
+
+ // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
+ // $dd->skipBotDetection();
+
+ $dd->parse();
+
+ $sessions[$index] = [
+ '$id' => $token->getId(),
+ 'OS' => $dd->getOs(),
+ 'client' => $dd->getClient(),
+ 'device' => $dd->getDevice(),
+ 'brand' => $dd->getBrand(),
+ 'model' => $dd->getModel(),
+ 'ip' => $token->getAttribute('ip', ''),
+ 'geo' => [],
+ ];
+
+ try {
+ $record = $geodb->country($token->getAttribute('ip', ''));
+ $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode);
+ $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown');
+ } catch (\Exception $e) {
+ $sessions[$index]['geo']['isoCode'] = '--';
+ $sessions[$index]['geo']['country'] = $locale->getText('locale.country.unknown');
+ }
+
+ ++$index;
+ }
+
+ $response->json($sessions);
+ }, ['response', 'projectDB', 'locale', 'geodb']);
+
+App::get('/v1/users/:userId/logs')
->desc('Get User Logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -289,80 +292,84 @@ $utopia->get('/v1/users/:userId/logs')
->label('sdk.namespace', 'users')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/users/get-user-logs.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->action(
- function ($userId) use ($response, $register, $projectDB, $project) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->action(function ($userId, $response, $register, $project, $projectDB, $locale, $geodb) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\Registry\Registry $register */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var GeoIp2\Database\Reader $geodb */
+
+ $user = $projectDB->getDocument($userId);
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
-
- $adapter = new AuditAdapter($register->get('db'));
- $adapter->setNamespace('app_'.$project->getId());
-
- $audit = new Audit($adapter);
-
- $countries = Locale::getText('countries');
-
- $logs = $audit->getLogsByUserAndActions($user->getId(), [
- 'account.create',
- 'account.delete',
- 'account.update.name',
- 'account.update.email',
- 'account.update.password',
- 'account.update.prefs',
- 'account.sessions.create',
- 'account.sessions.delete',
- 'account.recovery.create',
- 'account.recovery.update',
- 'account.verification.create',
- 'account.verification.update',
- 'teams.membership.create',
- 'teams.membership.update',
- 'teams.membership.delete',
- ]);
-
- $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
- $output = [];
-
- foreach ($logs as $i => &$log) {
- $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
-
- $dd = new DeviceDetector($log['userAgent']);
-
- $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
-
- $dd->parse();
-
- $output[$i] = [
- 'event' => $log['event'],
- 'ip' => $log['ip'],
- 'time' => \strtotime($log['time']),
- 'OS' => $dd->getOs(),
- 'client' => $dd->getClient(),
- 'device' => $dd->getDevice(),
- 'brand' => $dd->getBrand(),
- 'model' => $dd->getModel(),
- 'geo' => [],
- ];
-
- try {
- $record = $reader->country($log['ip']);
- $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
- $output[$i]['geo']['country'] = $record->country->name;
- $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
- } catch (\Exception $e) {
- $output[$i]['geo']['isoCode'] = '--';
- $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
- }
- }
-
- $response->json($output);
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
-$utopia->patch('/v1/users/:userId/status')
+ $adapter = new AuditAdapter($register->get('db'));
+ $adapter->setNamespace('app_'.$project->getId());
+
+ $audit = new Audit($adapter);
+
+ $countries = $locale->getText('countries');
+
+ $logs = $audit->getLogsByUserAndActions($user->getId(), [
+ 'account.create',
+ 'account.delete',
+ 'account.update.name',
+ 'account.update.email',
+ 'account.update.password',
+ 'account.update.prefs',
+ 'account.sessions.create',
+ 'account.sessions.delete',
+ 'account.recovery.create',
+ 'account.recovery.update',
+ 'account.verification.create',
+ 'account.verification.update',
+ 'teams.membership.create',
+ 'teams.membership.update',
+ 'teams.membership.delete',
+ ]);
+
+ $output = [];
+
+ foreach ($logs as $i => &$log) {
+ $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
+
+ $dd = new DeviceDetector($log['userAgent']);
+
+ $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
+
+ $dd->parse();
+
+ $output[$i] = [
+ 'event' => $log['event'],
+ 'ip' => $log['ip'],
+ 'time' => \strtotime($log['time']),
+ 'OS' => $dd->getOs(),
+ 'client' => $dd->getClient(),
+ 'device' => $dd->getDevice(),
+ 'brand' => $dd->getBrand(),
+ 'model' => $dd->getModel(),
+ 'geo' => [],
+ ];
+
+ try {
+ $record = $geodb->country($log['ip']);
+ $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode);
+ $output[$i]['geo']['country'] = $record->country->name;
+ $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown');
+ } catch (\Exception $e) {
+ $output[$i]['geo']['isoCode'] = '--';
+ $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown');
+ }
+ }
+
+ $response->json($output);
+ }, ['response', 'register', 'project', 'projectDB', 'locale', 'geodb']);
+
+App::patch('/v1/users/:userId/status')
->desc('Update User Status')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -370,48 +377,49 @@ $utopia->patch('/v1/users/:userId/status')
->label('sdk.namespace', 'users')
->label('sdk.method', 'updateStatus')
->label('sdk.description', '/docs/references/users/update-user-status.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->param('status', '', function () { return new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED]); }, 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED)
- ->action(
- function ($userId, $status) use ($response, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->param('status', '', new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED], true), 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED)
+ ->action(function ($userId, $status, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'status' => (int)$status,
- ]));
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $oauth2Keys = [];
-
- foreach (Config::getParam('providers') as $key => $provider) {
- if (!$provider['enabled']) {
- continue;
- }
-
- $oauth2Keys[] = 'oauth2'.\ucfirst($key);
- $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
- }
-
- $response
- ->json(\array_merge($user->getArrayCopy(\array_merge([
- '$id',
- 'status',
- 'email',
- 'registration',
- 'emailVerification',
- 'name',
- ], $oauth2Keys)), ['roles' => []]));
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
-$utopia->patch('/v1/users/:userId/prefs')
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'status' => (int)$status,
+ ]));
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $oauth2Keys = [];
+
+ foreach (Config::getParam('providers') as $key => $provider) {
+ if (!$provider['enabled']) {
+ continue;
+ }
+
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key);
+ $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
+ }
+
+ $response
+ ->json(\array_merge($user->getArrayCopy(\array_merge([
+ '$id',
+ 'status',
+ 'email',
+ 'registration',
+ 'emailVerification',
+ 'name',
+ ], $oauth2Keys)), ['roles' => []]));
+ }, ['response', 'projectDB']);
+
+App::patch('/v1/users/:userId/prefs')
->desc('Update User Preferences')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -419,42 +427,42 @@ $utopia->patch('/v1/users/:userId/prefs')
->label('sdk.namespace', 'users')
->label('sdk.method', 'updatePrefs')
->label('sdk.description', '/docs/references/users/update-user-prefs.md')
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.')
- ->action(
- function ($userId, $prefs) use ($response, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
+ ->action(function ($userId, $prefs, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $old = \json_decode($user->getAttribute('prefs', '{}'), true);
- $old = ($old) ? $old : [];
-
- $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
- 'prefs' => \json_encode(\array_merge($old, $prefs)),
- ]));
-
- if (false === $user) {
- throw new Exception('Failed saving user to DB', 500);
- }
-
- $prefs = $user->getAttribute('prefs', '');
-
- try {
- $prefs = \json_decode($prefs, true);
- $prefs = ($prefs) ? $prefs : [];
- } catch (\Exception $error) {
- throw new Exception('Failed to parse prefs', 500);
- }
-
- $response->json($prefs);
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
+ $old = \json_decode($user->getAttribute('prefs', '{}'), true);
+ $old = ($old) ? $old : [];
-$utopia->delete('/v1/users/:userId/sessions/:sessionId')
+ $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [
+ 'prefs' => \json_encode(\array_merge($old, $prefs)),
+ ]));
+
+ if (false === $user) {
+ throw new Exception('Failed saving user to DB', 500);
+ }
+
+ $prefs = $user->getAttribute('prefs', '');
+
+ try {
+ $prefs = \json_decode($prefs, true);
+ $prefs = ($prefs) ? $prefs : [];
+ } catch (\Exception $error) {
+ throw new Exception('Failed to parse prefs', 500);
+ }
+
+ $response->json($prefs);
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete User Session')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -463,31 +471,32 @@ $utopia->delete('/v1/users/:userId/sessions/:sessionId')
->label('sdk.method', 'deleteSession')
->label('sdk.description', '/docs/references/users/delete-user-session.md')
->label('abuse-limit', 100)
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->param('sessionId', null, function () { return new UID(); }, 'User unique session ID.')
- ->action(
- function ($userId, $sessionId) use ($response, $request, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->param('sessionId', null, new UID(), 'User unique session ID.')
+ ->action(function ($userId, $sessionId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $tokens = $user->getAttribute('tokens', []);
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
+ }
- foreach ($tokens as $token) { /* @var $token Document */
- if ($sessionId == $token->getId()) {
- if (!$projectDB->deleteDocument($token->getId())) {
- throw new Exception('Failed to remove token from DB', 500);
- }
+ $tokens = $user->getAttribute('tokens', []);
+
+ foreach ($tokens as $token) { /* @var $token Document */
+ if ($sessionId == $token->getId()) {
+ if (!$projectDB->deleteDocument($token->getId())) {
+ throw new Exception('Failed to remove token from DB', 500);
}
}
-
- $response->json(array('result' => 'success'));
}
- );
-$utopia->delete('/v1/users/:userId/sessions')
+ $response->json(array('result' => 'success'));
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/users/:userId/sessions')
->desc('Delete User Sessions')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -496,23 +505,69 @@ $utopia->delete('/v1/users/:userId/sessions')
->label('sdk.method', 'deleteSessions')
->label('sdk.description', '/docs/references/users/delete-user-sessions.md')
->label('abuse-limit', 100)
- ->param('userId', '', function () { return new UID(); }, 'User unique ID.')
- ->action(
- function ($userId) use ($response, $request, $projectDB) {
- $user = $projectDB->getDocument($userId);
+ ->param('userId', '', new UID(), 'User unique ID.')
+ ->action(function ($userId, $response, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
- if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
- throw new Exception('User not found', 404);
- }
+ $user = $projectDB->getDocument($userId);
- $tokens = $user->getAttribute('tokens', []);
-
- foreach ($tokens as $token) { /* @var $token Document */
- if (!$projectDB->deleteDocument($token->getId())) {
- throw new Exception('Failed to remove token from DB', 500);
- }
- }
-
- $response->json(array('result' => 'success'));
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
}
- );
+
+ $tokens = $user->getAttribute('tokens', []);
+
+ foreach ($tokens as $token) { /* @var $token Document */
+ if (!$projectDB->deleteDocument($token->getId())) {
+ throw new Exception('Failed to remove token from DB', 500);
+ }
+ }
+
+ $response->json(array('result' => 'success'));
+ }, ['response', 'projectDB']);
+
+App::delete('/v1/users/:userId')
+ ->desc('Delete User')
+ ->groups(['api', 'users'])
+ ->label('scope', 'users.write')
+ ->label('sdk.platform', [APP_PLATFORM_SERVER])
+ ->label('sdk.namespace', 'users')
+ ->label('sdk.method', 'deleteUser')
+ ->label('sdk.description', '/docs/references/users/delete-user.md')
+ ->label('abuse-limit', 100)
+ ->param('userId', '', function () {return new UID();}, 'User unique ID.')
+ ->action(function ($userId, $response, $projectDB, $deletes) {
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var Appwrite\Event\Event $deletes */
+
+ $user = $projectDB->getDocument($userId);
+
+ if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
+ throw new Exception('User not found', 404);
+ }
+ if (!$projectDB->deleteDocument($userId)) {
+ throw new Exception('Failed to remove user from DB', 500);
+ }
+
+ if (!$projectDB->deleteUniqueKey(md5('users:email='.$user->getAttribute('email', null)))) {
+ throw new Exception('Failed to remove unique key from DB', 500);
+ }
+
+ $reservedId = $projectDB->createDocument([
+ '$collection' => Database::SYSTEM_COLLECTION_RESERVED,
+ '$id' => $userId,
+ '$permissions' => [
+ 'read' => ['*'],
+ ],
+ ]);
+
+ if (false === $reservedId) {
+ throw new Exception('Failed saving reserved id to DB', 500);
+ }
+
+ $deletes->setParam('document', $user);
+
+ $response->noContent();
+ }, ['response', 'projectDB', 'deletes']);
diff --git a/app/app.php b/app/controllers/general.php
similarity index 54%
rename from app/app.php
rename to app/controllers/general.php
index e92660b5a0..e7350515cd 100644
--- a/app/app.php
+++ b/app/controllers/general.php
@@ -1,11 +1,10 @@
getAttribute('platforms', []), function ($node) {
- if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
- return true;
- }
-
- return false;
- }));
-
-$clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) {
- return $node['hostname'];
- }, \array_filter($project->getAttribute('platforms', []), function ($node) {
- if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
- return true;
- }
-
- return false;
- }))));
-
-$utopia->init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $mail, $audit, $usage, $clients) {
+App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $clients) {
+ /** @var Appwrite\Swoole\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Appwrite\Database\Document $console */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Document $user */
+ /** @var Utopia\Locale\Locale $locale */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $usage */
+ /** @var bool $mode */
+ /** @var array $clients */
+ Authorization::$roles = ['*'];
+
+ $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
+
+ if (\in_array($localeParam, Config::getParam('locale-codes'))) {
+ $locale->setDefault($localeParam);
+ };
+
$route = $utopia->match($request);
if(!empty($route->getLabel('sdk.platform', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) {
throw new Exception('Missing or unknown project ID', 400);
}
- $referrer = $request->getServer('HTTP_REFERER', '');
- $origin = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_HOST);
- $protocol = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_SCHEME);
- $port = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_PORT);
+ $console->setAttribute('platforms', [ // Allways allow current host
+ '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS,
+ 'name' => 'Current Host',
+ 'type' => 'web',
+ 'hostname' => $request->getHostname(),
+ ], Document::SET_TYPE_APPEND);
- $refDomain = $protocol.'://'.((\in_array($origin, $clients))
+ $referrer = $request->getReferer();
+ $origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST);
+ $protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
+ $port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT);
+
+ $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()).'://'.((\in_array($origin, $clients))
? $origin : 'localhost') . (!empty($port) ? ':'.$port : '');
- $selfDomain = new Domain(Config::getParam('hostname'));
+ $selfDomain = new Domain($request->getHostname());
$endDomain = new Domain($origin);
+ // var_dump('referer', $referrer);
+ // var_dump('origin', $origin);
+ // var_dump('port', $request->getPort());
+ // var_dump('hostname', $request->getHostname());
+ // var_dump('protocol', $request->getProtocol());
+ // var_dump('method', $request->getMethod());
+ // var_dump('ip', $request->getIP());
+ // var_dump('-----------------');
+ // var_dump($request->debug());
+
Config::setParam('domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
$endDomain->getRegisterable() !== '');
+ Config::setParam('cookieDomain', (
+ $request->getHostname() === 'localhost' ||
+ $request->getHostname() === 'localhost:'.$request->getPort() ||
+ (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false)
+ )
+ ? null
+ : '.'.$request->getHostname()
+ );
+
+ Storage::setDevice('files', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId()));
+ Storage::setDevice('functions', new Local(APP_STORAGE_FUNCTIONS.'/app-'.$project->getId()));
+
/*
* Security Headers
*
* As recommended at:
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
- if ($utopia->getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
- if(Config::getParam('protocol') !== 'https') {
- return $response->redirect('https://' . Config::getParam('domain').$request->getServer('REQUEST_URI'));
+ if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
+ if($request->getProtocol() !== 'https') {
+ return $response->redirect('https://'.$request->getHostname().$request->getURI());
}
$response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days
@@ -89,7 +108,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
$response
->addHeader('Server', 'Appwrite')
- ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url='.\urlencode($request->getServer('REQUEST_URI')))
+ ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url='.\urlencode($request->getURI()))
//->addHeader('X-Frame-Options', ($refDomain == 'http://localhost') ? 'SAMEORIGIN' : 'ALLOW-FROM ' . $refDomain)
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
@@ -104,13 +123,13 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non-web platforms which are not requiredto send an origin header
*/
- $origin = $request->getServer('HTTP_ORIGIN', $request->getServer('HTTP_REFERER', ''));
+ $origin = $request->getOrigin($request->getReferer(''));
$originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', [])));
if(!$originValidator->isValid($origin)
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
&& $route->getLabel('origin', false) !== '*'
- && empty($request->getHeader('X-Appwrite-Key', ''))) {
+ && empty($request->getHeader('x-appwrite-key', ''))) {
throw new Exception($originValidator->getDescription(), 403);
}
@@ -143,7 +162,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
// Check if given key match project API keys
- $key = $project->search('secret', $request->getHeader('X-Appwrite-Key', ''), $project->getAttribute('keys', []));
+ $key = $project->search('secret', $request->getHeader('x-appwrite-key', ''), $project->getAttribute('keys', []));
/*
* Try app auth when we have project key and no user
@@ -153,7 +172,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
$user = new Document([
'$id' => 0,
'status' => Auth::USER_STATUS_ACTIVATED,
- 'email' => 'app.'.$project->getId().'@service.'.Config::getParam('domain'),
+ 'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);
@@ -198,43 +217,50 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
/*
* Background Jobs
*/
- $webhook
+ $webhooks
->setParam('projectId', $project->getId())
->setParam('event', $route->getLabel('webhook', ''))
->setParam('payload', [])
;
- $audit
+ $audits
->setParam('projectId', $project->getId())
->setParam('userId', $user->getId())
->setParam('event', '')
->setParam('resource', '')
- ->setParam('userAgent', $request->getServer('HTTP_USER_AGENT', ''))
+ ->setParam('userAgent', $request->getUserAgent(''))
->setParam('ip', $request->getIP())
->setParam('data', [])
;
$usage
->setParam('projectId', $project->getId())
- ->setParam('url', $request->getServer('HTTP_HOST', '').$request->getServer('REQUEST_URI', ''))
- ->setParam('method', $request->getServer('REQUEST_METHOD', 'UNKNOWN'))
- ->setParam('request', 0)
- ->setParam('response', 0)
+ ->setParam('httpRequest', 1)
+ ->setParam('httpUrl', $request->getHostname().$request->getURI())
+ ->setParam('httpMethod', $request->getMethod())
+ ->setParam('networkRequestSize', 0)
+ ->setParam('networkResponseSize', 0)
->setParam('storage', 0)
;
-});
+}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'webhooks', 'audits', 'usage', 'clients']);
-$utopia->shutdown(function () use ($response, $request, $webhook, $audit, $usage, $deletes, $mode, $project, $utopia) {
+App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audits, $usage, $deletes, $mode) {
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Event\Event $webhooks */
+ /** @var Appwrite\Event\Event $audits */
+ /** @var Appwrite\Event\Event $usage */
+ /** @var Appwrite\Event\Event $deletes */
+ /** @var bool $mode */
- /*
- * Trigger events for background workers
- */
- if (!empty($webhook->getParam('event'))) {
- $webhook->trigger();
+ if (!empty($webhooks->getParam('event'))) {
+ $webhooks->trigger();
}
- if (!empty($audit->getParam('event'))) {
- $audit->trigger();
+ if (!empty($audits->getParam('event'))) {
+ $audits->trigger();
}
if (!empty($deletes->getParam('document'))) {
@@ -246,29 +272,47 @@ $utopia->shutdown(function () use ($response, $request, $webhook, $audit, $usage
if($project->getId()
&& $mode !== APP_MODE_ADMIN
&& !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage and admin mode
+
$usage
- ->setParam('request', $request->getSize() + $usage->getParam('storage'))
- ->setParam('response', $response->getSize())
+ ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage'))
+ ->setParam('networkResponseSize', $response->getSize())
->trigger()
;
}
-});
+}, ['utopia', 'request', 'response', 'project', 'webhooks', 'audits', 'usage', 'deletes', 'mode']);
-$utopia->options(function () use ($request, $response) {
- $origin = $request->getServer('HTTP_ORIGIN');
+App::options(function ($request, $response) {
+ /** @var Appwrite\Swoole\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+
+ $origin = $request->getOrigin();
$response
+ ->addHeader('Server', 'Appwrite')
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-SDK-Version, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
->addHeader('Access-Control-Allow-Origin', $origin)
->addHeader('Access-Control-Allow-Credentials', 'true')
->send();
-});
+}, ['request', 'response']);
-$utopia->error(function ($error /* @var $error Exception */) use ($request, $response, $utopia, $project) {
- $env = Config::getParam('env');
- $version = Config::getParam('version');
+App::error(function ($error, $utopia, $request, $response, $layout, $project) {
+ /** @var Exception $error */
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Utopia\View $layout */
+ /** @var Appwrite\Database\Document $project */
+
+ if(php_sapi_name() === 'cli') {
+ var_dump(get_class($error));
+ var_dump($error->getMessage());
+ var_dump($error->getFile());
+ var_dump($error->getLine());
+ }
+
+ $version = App::getEnv('_APP_VERSION', 'UNKNOWN');
switch ($error->getCode()) {
case 400: // Error allowed publicly
@@ -287,9 +331,9 @@ $utopia->error(function ($error /* @var $error Exception */) use ($request, $res
$message = 'Server Error';
}
- $_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
+ //$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
- $output = ((App::MODE_TYPE_DEVELOPMENT == $env)) ? [
+ $output = ((App::isDevelopment())) ? [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
@@ -313,7 +357,6 @@ $utopia->error(function ($error /* @var $error Exception */) use ($request, $res
$template = ($route) ? $route->getLabel('error', null) : null;
if ($template) {
- $layout = new View(__DIR__.'/views/layouts/default.phtml');
$comp = new View($template);
$comp
@@ -331,103 +374,95 @@ $utopia->error(function ($error /* @var $error Exception */) use ($request, $res
->setParam('litespeed', false)
;
- $response->send($layout->render());
+ $response->html($layout->render());
}
- $response
- ->json($output)
- ;
-});
+ $response->dynamic(new Document($output),
+ $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_LOCALE);
+
+}, ['error', 'utopia', 'request', 'response', 'layout', 'project']);
-$utopia->get('/manifest.json')
+App::get('/manifest.json')
->desc('Progressive app manifest file')
->label('scope', 'public')
->label('docs', false)
- ->action(
- function () use ($response) {
- $response->json([
- 'name' => APP_NAME,
- 'short_name' => APP_NAME,
- 'start_url' => '.',
- 'url' => 'https://appwrite.io/',
- 'display' => 'standalone',
- 'background_color' => '#fff',
- 'theme_color' => '#f02e65',
- 'description' => 'End to end backend server for frontend and mobile apps. 👩💻👨💻',
- 'icons' => [
- [
- 'src' => 'images/favicon.png',
- 'sizes' => '256x256',
- 'type' => 'image/png',
- ],
- ],
- ]);
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/robots.txt')
+ $response->json([
+ 'name' => APP_NAME,
+ 'short_name' => APP_NAME,
+ 'start_url' => '.',
+ 'url' => 'https://appwrite.io/',
+ 'display' => 'standalone',
+ 'background_color' => '#fff',
+ 'theme_color' => '#f02e65',
+ 'description' => 'End to end backend server for frontend and mobile apps. 👩💻👨💻',
+ 'icons' => [
+ [
+ 'src' => 'images/favicon.png',
+ 'sizes' => '256x256',
+ 'type' => 'image/png',
+ ],
+ ],
+ ]);
+ }, ['response']);
+
+App::get('/robots.txt')
->desc('Robots.txt File')
->label('scope', 'public')
->label('docs', false)
- ->action(
- function () use ($response) {
- $template = new View(__DIR__.'/views/general/robots.phtml');
- $response->text($template->render(false));
- }
- );
+ ->action(function ($response) {
+ $template = new View(__DIR__.'/views/general/robots.phtml');
+ $response->text($template->render(false));
+ }, ['response']);
-$utopia->get('/humans.txt')
+App::get('/humans.txt')
->desc('Humans.txt File')
->label('scope', 'public')
->label('docs', false)
- ->action(
- function () use ($response) {
- $template = new View(__DIR__.'/views/general/humans.phtml');
- $response->text($template->render(false));
- }
- );
+ ->action(function ($response) {
+ $template = new View(__DIR__.'/views/general/humans.phtml');
+ $response->text($template->render(false));
+ }, ['response']);
-$utopia->get('/.well-known/acme-challenge')
+App::get('/.well-known/acme-challenge')
->desc('SSL Verification')
->label('scope', 'public')
->label('docs', false)
- ->action(
- function () use ($request, $response) {
- $base = \realpath(APP_STORAGE_CERTIFICATES);
- $path = \str_replace('/.well-known/acme-challenge/', '', $request->getParam('q'));
- $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path);
+ ->action(function ($request, $response) {
+ $base = \realpath(APP_STORAGE_CERTIFICATES);
+ $path = \str_replace('/.well-known/acme-challenge/', '', $request->getParam('q'));
+ $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path);
- if(!$base) {
- throw new Exception('Storage error', 500);
- }
-
- if(!$absolute) {
- throw new Exception('Unknown path', 404);
- }
-
- if(!\substr($absolute, 0, \strlen($base)) === $base) {
- throw new Exception('Invalid path', 401);
- }
-
- if(!\file_exists($absolute)) {
- throw new Exception('Unknown path', 404);
- }
-
- $content = @\file_get_contents($absolute);
-
- if(!$content) {
- throw new Exception('Failed to get contents', 500);
- }
-
- $response->text($content);
+ if(!$base) {
+ throw new Exception('Storage error', 500);
}
- );
-include_once __DIR__ . '/controllers/shared/api.php';
-include_once __DIR__ . '/controllers/shared/web.php';
+ if(!$absolute) {
+ throw new Exception('Unknown path', 404);
+ }
+
+ if(!\substr($absolute, 0, \strlen($base)) === $base) {
+ throw new Exception('Invalid path', 401);
+ }
+
+ if(!\file_exists($absolute)) {
+ throw new Exception('Unknown path', 404);
+ }
+
+ $content = @\file_get_contents($absolute);
+
+ if(!$content) {
+ throw new Exception('Failed to get contents', 500);
+ }
+
+ $response->text($content);
+ }, ['request', 'response']);
+
+include_once __DIR__ . '/shared/api.php';
+include_once __DIR__ . '/shared/web.php';
foreach(Config::getParam('services', []) as $service) {
include_once $service['controller'];
-}
-
-$utopia->run($request, $response);
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/app/controllers/mock.php b/app/controllers/mock.php
index 097501fbba..3f36a2238f 100644
--- a/app/controllers/mock.php
+++ b/app/controllers/mock.php
@@ -2,339 +2,360 @@
global $utopia, $request, $response;
+use Utopia\App;
+use Utopia\Response;
use Utopia\Validator\Numeric;
use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
-use Utopia\Response;
use Utopia\Validator\Host;
use Appwrite\Storage\Validator\File;
-$result = [];
-
-$utopia->get('/v1/mock/tests/foo')
+App::get('/v1/mock/tests/foo')
->desc('Mock a get request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'foo')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->post('/v1/mock/tests/foo')
+App::post('/v1/mock/tests/foo')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'foo')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->patch('/v1/mock/tests/foo')
+App::patch('/v1/mock/tests/foo')
->desc('Mock a patch request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'foo')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a get request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->put('/v1/mock/tests/foo')
+App::put('/v1/mock/tests/foo')
->desc('Mock a put request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'foo')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->delete('/v1/mock/tests/foo')
+App::delete('/v1/mock/tests/foo')
->desc('Mock a delete request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'foo')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->get('/v1/mock/tests/bar')
+App::get('/v1/mock/tests/bar')
->desc('Mock a get request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'bar')
->label('sdk.method', 'get')
->label('sdk.description', 'Mock a get request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->post('/v1/mock/tests/bar')
+App::post('/v1/mock/tests/bar')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'bar')
->label('sdk.method', 'post')
->label('sdk.description', 'Mock a post request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->patch('/v1/mock/tests/bar')
+App::patch('/v1/mock/tests/bar')
->desc('Mock a patch request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'bar')
->label('sdk.method', 'patch')
->label('sdk.description', 'Mock a get request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->put('/v1/mock/tests/bar')
+App::put('/v1/mock/tests/bar')
->desc('Mock a put request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'bar')
->label('sdk.method', 'put')
->label('sdk.description', 'Mock a put request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->delete('/v1/mock/tests/bar')
+App::delete('/v1/mock/tests/bar')
->desc('Mock a delete request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'bar')
->label('sdk.method', 'delete')
->label('sdk.description', 'Mock a delete request for SDK tests')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->action(
- function ($x, $y, $z) {
- }
- );
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->action(function ($x, $y, $z) {
+ });
-$utopia->post('/v1/mock/tests/general/upload')
+App::post('/v1/mock/tests/general/upload')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'upload')
->label('sdk.description', 'Mock a delete request for SDK tests')
->label('sdk.consumes', 'multipart/form-data')
- ->param('x', '', function () { return new Text(100); }, 'Sample string param')
- ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param')
- ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param')
- ->param('file', [], function () { return new File(); }, 'Sample file param', false)
- ->action(
- function ($x, $y, $z, $file) use ($request) {
- $file = $request->getFiles('file');
- $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']];
- $file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']];
- $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']];
+ ->label('sdk.mock', true)
+ ->param('x', '', new Text(100), 'Sample string param')
+ ->param('y', '', new Numeric(), 'Sample numeric param')
+ ->param('z', null, new ArrayList(new Text(256)), 'Sample array param')
+ ->param('file', [], new File(), 'Sample file param', false)
+ ->action(function ($x, $y, $z, $file, $request) {
+ /** @var Utopia\Swoole\Request $request */
+
+ $file = $request->getFiles('file');
+ $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']];
+ $file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']];
+ $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']];
- foreach ($file['name'] as $i => $name) {
- if ($name !== 'file.png') {
- throw new Exception('Wrong file name', 400);
- }
- }
-
- foreach ($file['size'] as $i => $size) {
- if ($size !== 38756) {
- throw new Exception('Wrong file size', 400);
- }
- }
-
- foreach ($file['tmp_name'] as $i => $tmpName) {
- if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
- throw new Exception('Wrong file uploaded', 400);
- }
+ foreach ($file['name'] as $i => $name) {
+ if ($name !== 'file.png') {
+ throw new Exception('Wrong file name', 400);
}
}
- );
-$utopia->get('/v1/mock/tests/general/redirect')
+ foreach ($file['size'] as $i => $size) {
+ if ($size !== 38756) {
+ throw new Exception('Wrong file size', 400);
+ }
+ }
+
+ foreach ($file['tmp_name'] as $i => $tmpName) {
+ if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') {
+ throw new Exception('Wrong file uploaded', 400);
+ }
+ }
+ }, ['request']);
+
+App::get('/v1/mock/tests/general/redirect')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirect')
->label('sdk.description', 'Mock a redirect request for SDK tests')
- ->action(
- function () use ($response) {
- $response->redirect('/v1/mock/tests/general/redirected');
- }
- );
+ ->label('sdk.mock', true)
+ ->action(function ($response) {
+ /** @var Appwrite\Utopia\Response $response */
-$utopia->get('/v1/mock/tests/general/redirected')
+ $response->redirect('/v1/mock/tests/general/redirected');
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/redirected')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'redirected')
->label('sdk.description', 'Mock a redirected request for SDK tests')
- ->action(
- function () {
- }
- );
+ ->label('sdk.mock', true)
+ ->action(function () {
+ });
-$utopia->get('/v1/mock/tests/general/set-cookie')
+App::get('/v1/mock/tests/general/set-cookie')
->desc('Mock a cookie request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'setCookie')
->label('sdk.description', 'Mock a set cookie request for SDK tests')
- ->action(
- function () use ($response) {
- $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', 'localhost', true, true);
- }
- );
+ ->label('sdk.mock', true)
+ ->action(function ($response) {
+ /** @var Appwrite\Utopia\Response $response */
-$utopia->get('/v1/mock/tests/general/get-cookie')
+ $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', 'localhost', true, true);
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/get-cookie')
->desc('Mock a cookie request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'getCookie')
->label('sdk.description', 'Mock a get cookie request for SDK tests')
- ->action(
- function () use ($request) {
- if ($request->getCookie('cookieName', '') !== 'cookieValue') {
- throw new Exception('Missing cookie value', 400);
- }
- }
- );
+ ->label('sdk.mock', true)
+ ->action(function ($request) {
+ /** @var Utopia\Swoole\Request $request */
-$utopia->get('/v1/mock/tests/general/empty')
+ if ($request->getCookie('cookieName', '') !== 'cookieValue') {
+ throw new Exception('Missing cookie value', 400);
+ }
+ }, ['request']);
+
+App::get('/v1/mock/tests/general/empty')
->desc('Mock a post request for SDK tests')
+ ->groups(['mock'])
->label('scope', 'public')
->label('sdk.namespace', 'general')
->label('sdk.method', 'empty')
->label('sdk.description', 'Mock a redirected request for SDK tests')
- ->action(
- function () use ($response) {
- $response->noContent();
- exit();
- }
- );
+ ->label('sdk.mock', true)
+ ->action(function ($response) {
+ /** @var Appwrite\Utopia\Response $response */
-$utopia->get('/v1/mock/tests/general/oauth2')
+ $response->noContent();
+ exit();
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/oauth2')
->desc('Mock an OAuth2 login route')
+ ->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
- ->param('client_id', '', function () { return new Text(100); }, 'OAuth2 Client ID.')
- ->param('redirect_uri', '', function () { return new Host(['localhost']); }, 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
- ->param('scope', '', function () { return new Text(100); }, 'OAuth2 scope list.')
- ->param('state', '', function () { return new Text(1024); }, 'OAuth2 state.')
- ->action(
- function ($clientId, $redirectURI, $scope, $state) use ($response) {
- $response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state]));
- }
- );
+ ->label('sdk.mock', true)
+ ->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
+ ->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
+ ->param('scope', '', new Text(100), 'OAuth2 scope list.')
+ ->param('state', '', new Text(1024), 'OAuth2 state.')
+ ->action(function ($clientId, $redirectURI, $scope, $state, $response) {
+ /** @var Appwrite\Utopia\Response $response */
-$utopia->get('/v1/mock/tests/general/oauth2/token')
+ $response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state]));
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/oauth2/token')
->desc('Mock an OAuth2 login route')
+ ->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
- ->param('client_id', '', function () { return new Text(100); }, 'OAuth2 Client ID.')
- ->param('redirect_uri', '', function () { return new Host(['localhost']); }, 'OAuth2 Redirect URI.')
- ->param('client_secret', '', function () { return new Text(100); }, 'OAuth2 scope list.')
- ->param('code', '', function () { return new Text(100); }, 'OAuth2 state.')
- ->action(
- function ($clientId, $redirectURI, $clientSecret, $code) use ($response) {
- if ($clientId != '1') {
- throw new Exception('Invalid client ID');
- }
+ ->label('sdk.mock', true)
+ ->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
+ ->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.')
+ ->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
+ ->param('code', '', new Text(100), 'OAuth2 state.')
+ ->action(function ($clientId, $redirectURI, $clientSecret, $code, $response) {
+ /** @var Appwrite\Utopia\Response $response */
- if ($clientSecret != '123456') {
- throw new Exception('Invalid client secret');
- }
-
- if ($code != 'abcdef') {
- throw new Exception('Invalid token');
- }
-
- $response->json(['access_token' => '123456']);
+ if ($clientId != '1') {
+ throw new Exception('Invalid client ID');
}
- );
-$utopia->get('/v1/mock/tests/general/oauth2/user')
+ if ($clientSecret != '123456') {
+ throw new Exception('Invalid client secret');
+ }
+
+ if ($code != 'abcdef') {
+ throw new Exception('Invalid token');
+ }
+
+ $response->json(['access_token' => '123456']);
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/oauth2/user')
->desc('Mock an OAuth2 user route')
+ ->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
- ->param('token', '', function () { return new Text(100); }, 'OAuth2 Access Token.')
- ->action(
- function ($token) use ($response) {
- if ($token != '123456') {
- throw new Exception('Invalid token');
- }
+ ->param('token', '', new Text(100), 'OAuth2 Access Token.')
+ ->action(function ($token, $response) {
+ /** @var Appwrite\Utopia\Response $response */
- $response->json([
- 'id' => 1,
- 'name' => 'User Name',
- 'email' => 'user@localhost.test',
+ if ($token != '123456') {
+ throw new Exception('Invalid token');
+ }
+
+ $response->json([
+ 'id' => 1,
+ 'name' => 'User Name',
+ 'email' => 'user@localhost.test',
+ ]);
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/oauth2/success')
+ ->label('scope', 'public')
+ ->groups(['mock'])
+ ->label('docs', false)
+ ->action(function ($response) {
+ /** @var Appwrite\Utopia\Response $response */
+
+ $response->json([
+ 'result' => 'success',
+ ]);
+ }, ['response']);
+
+App::get('/v1/mock/tests/general/oauth2/failure')
+ ->groups(['mock'])
+ ->label('scope', 'public')
+ ->label('docs', false)
+ ->action(function ($response) {
+ /** @var Appwrite\Utopia\Response $response */
+
+ $response
+ ->setStatusCode(Response::STATUS_CODE_BAD_REQUEST)
+ ->json([
+ 'result' => 'failure',
]);
- }
- );
+ }, ['response']);
-$utopia->get('/v1/mock/tests/general/oauth2/success')
- ->label('scope', 'public')
- ->label('docs', false)
- ->action(
- function () use ($response) {
- $response->json([
- 'result' => 'success',
- ]);
- }
- );
+App::shutdown(function($utopia, $response, $request) {
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Swoole\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
-$utopia->get('/v1/mock/tests/general/oauth2/failure')
- ->label('scope', 'public')
- ->label('docs', false)
- ->action(
- function () use ($response) {
- $response
- ->setStatusCode(Response::STATUS_CODE_BAD_REQUEST)
- ->json([
- 'result' => 'failure',
- ]);
- }
- );
-
-$utopia->shutdown(function() use ($response, $request, &$result, $utopia) {
+ $result = [];
$route = $utopia->match($request);
$path = APP_STORAGE_CACHE.'/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
@@ -352,4 +373,4 @@ $utopia->shutdown(function() use ($response, $request, &$result, $utopia) {
}
$response->json(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']);
-}, 'mock');
\ No newline at end of file
+}, ['utopia', 'response', 'request'], 'mock');
\ No newline at end of file
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index e911c2c556..3fdca7cf17 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -1,12 +1,18 @@
init(function () use ($utopia, $request, $response, $register, $user, $project) {
$route = $utopia->match($request);
if (empty($project->getId()) && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope
@@ -22,9 +28,9 @@ $utopia->init(function () use ($utopia, $request, $response, $register, $user, $
$timeLimit->setNamespace('app_'.$project->getId());
$timeLimit
->setParam('{userId}', $user->getId())
- ->setParam('{userAgent}', $request->getServer('HTTP_USER_AGENT', ''))
+ ->setParam('{userAgent}', $request->getUserAgent(''))
->setParam('{ip}', $request->getIP())
- ->setParam('{url}', $request->getServer('HTTP_HOST', '').$route->getURL())
+ ->setParam('{url}', $request->getHostname().$route->getURL())
;
//TODO make sure we get array here
@@ -43,7 +49,7 @@ $utopia->init(function () use ($utopia, $request, $response, $register, $user, $
;
}
- if ($abuse->check() && $request->getServer('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
+ if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
throw new Exception('Too many requests', 429);
}
-}, 'api');
\ No newline at end of file
+}, ['utopia', 'request', 'response', 'project', 'user', 'register'], 'api');
\ No newline at end of file
diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php
index a2f85bcb0a..3452cd3535 100644
--- a/app/controllers/shared/web.php
+++ b/app/controllers/shared/web.php
@@ -1,22 +1,25 @@
init(function () use ($utopia, $response, $request, $layout) {
+App::init(function ($utopia, $request, $response, $layout) {
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Utopia\View $layout */
/* AJAX check */
if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__.'/../../views/layouts/empty.phtml');
}
+
$layout
->setParam('title', APP_NAME)
- ->setParam('protocol', Config::getParam('protocol'))
- ->setParam('domain', Config::getParam('domain'))
- ->setParam('home', $request->getServer('_APP_HOME'))
- ->setParam('setup', $request->getServer('_APP_SETUP'))
+ ->setParam('protocol', $request->getProtocol())
+ ->setParam('domain', $request->getHostname())
+ ->setParam('home', App::getEnv('_APP_HOME'))
+ ->setParam('setup', App::getEnv('_APP_SETUP'))
->setParam('class', 'unknown')
->setParam('icon', '/images/favicon.png')
->setParam('roles', [
@@ -24,22 +27,24 @@ $utopia->init(function () use ($utopia, $response, $request, $layout) {
['type' => 'developer', 'label' => 'Developer'],
['type' => 'admin', 'label' => 'Admin'],
])
- ->setParam('env', $utopia->getMode())
+ ->setParam('environments', Config::getParam('environments'))
+ ->setParam('mode', App::getMode())
;
$time = (60 * 60 * 24 * 45); // 45 days cache
- $isDev = (\Utopia\App::MODE_TYPE_DEVELOPMENT == Config::getParam('env'));
$response
->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
- ->addHeader('X-UA-Compatible', 'IE=Edge'); // Deny IE browsers from going into quirks mode
+ ->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
+ ->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
+ ;
$route = $utopia->match($request);
$scope = $route->getLabel('scope', '');
$layout
- ->setParam('version', Config::getParam('version'))
- ->setParam('isDev', $isDev)
+ ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
+ ->setParam('isDev', App::isDevelopment())
->setParam('class', $scope)
;
-}, 'web');
+}, ['utopia', 'request', 'response', 'layout'], 'web');
diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php
index 595d36d33a..6c2f7ba44c 100644
--- a/app/controllers/web/console.php
+++ b/app/controllers/web/console.php
@@ -1,7 +1,6 @@
init(function () use ($layout) {
+App::init(function ($layout) {
+ /** @var Utopia\View $layout */
+
$layout
->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.')
->setParam('analytics', 'UA-26264668-5')
;
-}, 'console');
+}, ['layout'], 'console');
+
+App::shutdown(function ($response, $layout) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\View $layout */
-$utopia->shutdown(function () use ($response, $request, $layout) {
$header = new View(__DIR__.'/../../views/console/comps/header.phtml');
$footer = new View(__DIR__.'/../../views/console/comps/footer.phtml');
$footer
- ->setParam('home', $request->getServer('_APP_HOME', ''))
- ->setParam('version', Config::getParam('version'))
+ ->setParam('home', App::getEnv('_APP_HOME', ''))
+ ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
@@ -31,15 +35,17 @@ $utopia->shutdown(function () use ($response, $request, $layout) {
->setParam('footer', [$footer])
;
- $response->send($layout->render());
-}, 'console');
+ $response->html($layout->render());
+}, ['response', 'layout'], 'console');
-$utopia->get('/error/:code')
+App::get('/error/:code')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
- ->action(function ($code) use ($layout) {
+ ->action(function ($code, $layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/error.phtml');
$page
@@ -49,29 +55,33 @@ $utopia->get('/error/:code')
$layout
->setParam('title', APP_NAME.' - Error')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console')
+App::get('/console')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout, $request) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/index.phtml');
$page
- ->setParam('home', $request->getServer('_APP_HOME', ''))
+ ->setParam('home', App::getEnv('_APP_HOME', ''))
;
$layout
->setParam('title', APP_NAME.' - Console')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/account')
+App::get('/console/account')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/account/index.phtml');
$cc = new View(__DIR__.'/../../views/console/forms/credit-card.phtml');
@@ -83,38 +93,44 @@ $utopia->get('/console/account')
$layout
->setParam('title', 'Account - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/notifications')
+App::get('/console/notifications')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/v1/console/notifications/index.phtml');
$layout
->setParam('title', APP_NAME.' - Notifications')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/home')
+App::get('/console/home')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/home/index.phtml');
$layout
->setParam('title', APP_NAME.' - Console')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/settings')
+App::get('/console/settings')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($request, $layout) {
- $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', ''));
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
+ $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', ''));
$page = new View(__DIR__.'/../../views/console/settings/index.phtml');
@@ -126,15 +142,17 @@ $utopia->get('/console/settings')
$layout
->setParam('title', APP_NAME.' - Settings')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/webhooks')
+App::get('/console/webhooks')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/webhooks/index.phtml');
-
+
$page
->setParam('events', Config::getParam('events', []))
;
@@ -142,14 +160,16 @@ $utopia->get('/console/webhooks')
$layout
->setParam('title', APP_NAME.' - Webhooks')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/keys')
+App::get('/console/keys')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
- $scopes = include __DIR__.'/../../../app/config/scopes.php';
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
+ $scopes = Config::getParam('scopes');
$page = new View(__DIR__.'/../../views/console/keys/index.phtml');
$page->setParam('scopes', $scopes);
@@ -157,38 +177,46 @@ $utopia->get('/console/keys')
$layout
->setParam('title', APP_NAME.' - API Keys')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/tasks')
+App::get('/console/tasks')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/tasks/index.phtml');
$layout
->setParam('title', APP_NAME.' - Tasks')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/database')
+App::get('/console/database')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/database/index.phtml');
$layout
->setParam('title', APP_NAME.' - Database')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/database/collection')
+App::get('/console/database/collection')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->param('id', '', function () { return new UID(); }, 'Collection unique ID.')
- ->action(function ($id) use ($response, $layout, $projectDB) {
+ ->param('id', '', new UID(), 'Collection unique ID.')
+ ->action(function ($id, $response, $layout, $projectDB) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\View $layout */
+ /** @var Appwrite\Database\Database $projectDB */
+
Authorization::disable();
$collection = $projectDB->getDocument($id, false);
Authorization::reset();
@@ -213,14 +241,17 @@ $utopia->get('/console/database/collection')
->addHeader('Expires', 0)
->addHeader('Pragma', 'no-cache')
;
- });
+ }, ['response', 'layout', 'projectDB']);
-$utopia->get('/console/database/document')
+App::get('/console/database/document')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->param('collection', '', function () { return new UID(); }, 'Collection unique ID.')
- ->action(function ($collection) use ($layout, $projectDB) {
+ ->param('collection', '', new UID(), 'Collection unique ID.')
+ ->action(function ($collection, $layout, $projectDB) {
+ /** @var Utopia\View $layout */
+ /** @var Appwrite\Database\Database $projectDB */
+
Authorization::disable();
$collection = $projectDB->getDocument($collection, false);
Authorization::reset();
@@ -243,31 +274,34 @@ $utopia->get('/console/database/document')
$layout
->setParam('title', APP_NAME.' - Database Document')
->setParam('body', $page);
- });
+ }, ['layout', 'projectDB']);
-$utopia->get('/console/storage')
+App::get('/console/storage')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($request, $layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/storage/index.phtml');
$page
- ->setParam('home', $request->getServer('_APP_HOME', 0))
- ->setParam('fileLimit', $request->getServer('_APP_STORAGE_LIMIT', 0))
- ->setParam('fileLimitHuman', Storage::human($request->getServer('_APP_STORAGE_LIMIT', 0)))
+ ->setParam('home', App::getEnv('_APP_HOME', 0))
+ ->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
+ ->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
;
$layout
->setParam('title', APP_NAME.' - Storage')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/users')
+App::get('/console/users')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/users/index.phtml');
$page->setParam('providers', Config::getParam('providers'));
@@ -275,28 +309,89 @@ $utopia->get('/console/users')
$layout
->setParam('title', APP_NAME.' - Users')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/users/user')
+App::get('/console/users/user')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/users/user.phtml');
$layout
->setParam('title', APP_NAME.' - User')
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/console/users/teams/team')
+App::get('/console/users/teams/team')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/console/users/team.phtml');
$layout
->setParam('title', APP_NAME.' - Team')
->setParam('body', $page);
- });
+ }, ['layout']);
+
+App::get('/console/functions')
+ ->groups(['web', 'console'])
+ ->desc('Platform console project functions')
+ ->label('permission', 'public')
+ ->label('scope', 'console')
+ ->action(function ($layout) {
+ $page = new View(__DIR__.'/../../views/console/functions/index.phtml');
+
+ $page
+ ->setParam('environments', Config::getParam('environments'))
+ ;
+
+ $layout
+ ->setParam('title', APP_NAME.' - Functions')
+ ->setParam('body', $page);
+ }, ['layout']);
+
+App::get('/console/functions/function')
+ ->groups(['web', 'console'])
+ ->desc('Platform console project function')
+ ->label('permission', 'public')
+ ->label('scope', 'console')
+ ->action(function ($layout) {
+ $page = new View(__DIR__.'/../../views/console/functions/function.phtml');
+
+ $page
+ ->setParam('events', Config::getParam('events', []))
+ ->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0))
+ ->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0)))
+ ->setParam('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
+ ;
+
+ $layout
+ ->setParam('title', APP_NAME.' - Function')
+ ->setParam('body', $page);
+ }, ['layout']);
+
+App::get('/console/version')
+ ->groups(['web', 'console'])
+ ->desc('Check for new version')
+ ->label('permission', 'public')
+ ->label('scope', 'console')
+ ->action(function ($response) {
+ try {
+ $version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost').'/v1/health/version'), true);
+
+ if($version && isset($version['version'])) {
+ return $response->json(['version' => $version['version']]);
+ }
+ else {
+ throw new Exception('Failed to check for a newer version', 500);
+ }
+ } catch (\Throwable $th) {
+ throw new Exception('Failed to check for a newer version', 500);
+ }
+ }, ['response']);
\ No newline at end of file
diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php
index 5d8a7aefc2..a6c2491c0f 100644
--- a/app/controllers/web/home.php
+++ b/app/controllers/web/home.php
@@ -1,18 +1,20 @@
init(function () use ($layout) {
+App::init(function ($layout) {
+ /** @var Utopia\View $layout */
+
$header = new View(__DIR__.'/../../views/home/comps/header.phtml');
$footer = new View(__DIR__.'/../../views/home/comps/footer.phtml');
$footer
- ->setParam('version', Config::getParam('version'))
+ ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN'))
;
$layout
@@ -23,100 +25,115 @@ $utopia->init(function () use ($layout) {
->setParam('header', [$header])
->setParam('footer', [$footer])
;
-}, 'home');
+}, ['layout'], 'home');
-$utopia->shutdown(function () use ($response, $layout) {
- $response->send($layout->render());
-}, 'home');
+App::shutdown(function ($response, $layout) {
+ /** @var Utopia\Response $response */
+ /** @var Utopia\View $layout */
-$utopia->get('/')
+ $response->html($layout->render());
+}, ['response', 'layout'], 'home');
+
+App::get('/')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(
- function () use ($response) {
- $response->redirect('/auth/signin');
- }
- );
+ ->action(function ($response) {
+ /** @var Utopia\Response $response */
-$utopia->get('/auth/signin')
+ $response->redirect('/auth/signin');
+ }, ['response']);
+
+App::get('/auth/signin')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/signin.phtml');
$layout
->setParam('title', 'Sign In - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/auth/signup')
+App::get('/auth/signup')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
$page = new View(__DIR__.'/../../views/home/auth/signup.phtml');
$layout
->setParam('title', 'Sign Up - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/auth/recovery')
+App::get('/auth/recovery')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($request, $layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/recovery.phtml');
$layout
->setParam('title', 'Password Recovery - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/auth/confirm')
+App::get('/auth/confirm')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/confirm.phtml');
$layout
->setParam('title', 'Account Confirmation - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/auth/join')
+App::get('/auth/join')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/join.phtml');
$layout
->setParam('title', 'Invitation - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/auth/recovery/reset')
+App::get('/auth/recovery/reset')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/recovery/reset.phtml');
$layout
->setParam('title', 'Password Reset - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-
-$utopia->get('/auth/oauth2/success')
+App::get('/auth/oauth2/success')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
$layout
@@ -125,13 +142,15 @@ $utopia->get('/auth/oauth2/success')
->setParam('header', [])
->setParam('footer', [])
;
- });
+ }, ['layout']);
-$utopia->get('/auth/oauth2/failure')
+App::get('/auth/oauth2/failure')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
- ->action(function () use ($layout) {
+ ->action(function ($layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
$layout
@@ -140,14 +159,16 @@ $utopia->get('/auth/oauth2/failure')
->setParam('header', [])
->setParam('footer', [])
;
- });
+ }, ['layout']);
-$utopia->get('/error/:code')
+App::get('/error/:code')
->groups(['web', 'home'])
->label('permission', 'public')
->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
- ->action(function ($code) use ($layout) {
+ ->action(function ($code, $layout) {
+ /** @var Utopia\View $layout */
+
$page = new View(__DIR__.'/../../views/error.phtml');
$page
@@ -157,410 +178,381 @@ $utopia->get('/error/:code')
$layout
->setParam('title', 'Error'.' - '.APP_NAME)
->setParam('body', $page);
- });
+ }, ['layout']);
-$utopia->get('/open-api-2.json')
+App::get('/open-api-2.json')
->groups(['web', 'home'])
->label('scope', 'public')
->label('docs', false)
- ->param('platform', APP_PLATFORM_CLIENT, function () {return new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE]);}, 'Choose target platform.', true)
- ->param('extensions', 0, function () {return new Range(0, 1);}, 'Show extra data.', true)
- ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true)
- ->action(
- function ($platform, $extensions, $tests) use ($response, $request, $utopia) {
- $services = Config::getParam('services', []);
+ ->param('platform', APP_PLATFORM_CLIENT, new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE], true), 'Choose target platform.', true)
+ ->param('extensions', 0, new Range(0, 1), 'Show extra data.', true)
+ ->param('tests', 0, new Range(0, 1), 'Include only test services.', true)
+ ->action(function ($platform, $extensions, $tests, $utopia, $request, $response) {
+ /** @var Utopia\App $utopia */
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+
+ $security = [
+ APP_PLATFORM_CLIENT => ['Project' => []],
+ APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []],
+ APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []],
+ ];
+
+ $platforms = [
+ 'client' => APP_PLATFORM_CLIENT,
+ 'server' => APP_PLATFORM_SERVER,
+ 'all' => APP_PLATFORM_CONSOLE,
+ ];
+
+ $keys = [
+ APP_PLATFORM_CLIENT => [
+ 'Project' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Project',
+ 'description' => 'Your project ID',
+ 'in' => 'header',
+ ],
+ 'Locale' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Locale',
+ 'description' => '',
+ 'in' => 'header',
+ ],
+ ],
+ APP_PLATFORM_SERVER => [
+ 'Project' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Project',
+ 'description' => 'Your project ID',
+ 'in' => 'header',
+ ],
+ 'Key' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Key',
+ 'description' => 'Your secret API key',
+ 'in' => 'header',
+ ],
+ 'Locale' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Locale',
+ 'description' => '',
+ 'in' => 'header',
+ ],
+ ],
+ APP_PLATFORM_CONSOLE => [
+ 'Project' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Project',
+ 'description' => 'Your project ID',
+ 'in' => 'header',
+ ],
+ 'Key' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Key',
+ 'description' => 'Your secret API key',
+ 'in' => 'header',
+ ],
+ 'Locale' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Locale',
+ 'description' => '',
+ 'in' => 'header',
+ ],
+ 'Mode' => [
+ 'type' => 'apiKey',
+ 'name' => 'X-Appwrite-Mode',
+ 'description' => '',
+ 'in' => 'header',
+ ],
+ ],
+ ];
+
+ /*
+ * Specifications (v3.0.0):
+ * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
+ */
+ $output = [
+ 'swagger' => '2.0',
+ 'info' => [
+ 'version' => APP_VERSION_STABLE,
+ 'title' => APP_NAME,
+ 'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)',
+ 'termsOfService' => 'https://appwrite.io/policy/terms',
+ 'contact' => [
+ 'name' => 'Appwrite Team',
+ 'url' => 'https://appwrite.io/support',
+ 'email' => App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
+ ],
+ 'license' => [
+ 'name' => 'BSD-3-Clause',
+ 'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE',
+ ],
+ ],
+ 'host' => \parse_url(App::getEnv('_APP_HOME', $request->getHostname()), PHP_URL_HOST),
+ 'basePath' => '/v1',
+ 'schemes' => ['https'],
+ 'consumes' => ['application/json', 'multipart/form-data'],
+ 'produces' => ['application/json'],
+ 'securityDefinitions' => $keys[$platform],
+ 'paths' => [],
+ 'definitions' => [
+ // 'Pet' => [
+ // 'required' => ['id', 'name'],
+ // 'properties' => [
+ // 'id' => [
+ // 'type' => 'integer',
+ // 'format' => 'int64',
+ // ],
+ // 'name' => [
+ // 'type' => 'string',
+ // ],
+ // 'tag' => [
+ // 'type' => 'string',
+ // ],
+ // ],
+ // ],
+ // 'Pets' => array(
+ // 'type' => 'array',
+ // 'items' => array(
+ // '$ref' => '#/definitions/Pet',
+ // ),
+ // ),
+ 'Error' => array(
+ 'required' => array(
+ 0 => 'code',
+ 1 => 'message',
+ ),
+ 'properties' => array(
+ 'code' => array(
+ 'type' => 'integer',
+ 'format' => 'int32',
+ ),
+ 'message' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ ],
+ 'externalDocs' => [
+ 'description' => 'Full API docs, specs and tutorials',
+ 'url' => $request->getProtocol().'://'.$request->getHostname().'/docs',
+ ],
+ ];
+
+ if ($extensions) {
+ if (isset($output['securityDefinitions']['Project'])) {
+ $output['securityDefinitions']['Project']['extensions'] = ['demo' => '5df5acd0d48c2'];
+ }
- function fromCamelCase($input)
- {
- \preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
- $ret = $matches[0];
- foreach ($ret as &$match) {
- $match = $match == \strtoupper($match) ? \strtolower($match) : \lcfirst($match);
- }
-
- return \implode('_', $ret);
+ if (isset($output['securityDefinitions']['Key'])) {
+ $output['securityDefinitions']['Key']['extensions'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2'];
+ }
+
+ if (isset($output['securityDefinitions']['Locale'])) {
+ $output['securityDefinitions']['Locale']['extensions'] = ['demo' => 'en'];
}
- function fromCamelCaseToDash($input)
- {
- return \str_replace([' ', '_'], '-', \strtolower(\preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $input)));
+ if (isset($output['securityDefinitions']['Mode'])) {
+ $output['securityDefinitions']['Mode']['extensions'] = ['demo' => ''];
}
+ }
- foreach ($services as $service) { /* @noinspection PhpIncludeInspection */
- if ($tests && !isset($service['tests'])) {
+ foreach ($utopia->getRoutes() as $key => $method) {
+ foreach ($method as $route) { /* @var $route \Utopia\Route */
+ if (!$route->getLabel('docs', true)) {
continue;
}
- if ($tests && !$service['tests']) {
+ if ($route->getLabel('sdk.mock', false)) {
continue;
}
-
- if (!$tests && !$service['sdk']) {
+
+ if (empty($route->getLabel('sdk.namespace', null))) {
continue;
}
-
- /** @noinspection PhpIncludeInspection */
- include_once \realpath(__DIR__.'/../../'.$service['controller']);
- }
- $security = [
- APP_PLATFORM_CLIENT => ['Project' => []],
- APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []],
- APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []],
- ];
+ if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
+ continue;
+ }
- $platforms = [
- 'client' => APP_PLATFORM_CLIENT,
- 'server' => APP_PLATFORM_SERVER,
- 'all' => APP_PLATFORM_CONSOLE,
- ];
+ $url = \str_replace('/v1', '', $route->getURL());
+ $scope = $route->getLabel('scope', '');
+ $hide = $route->getLabel('sdk.hide', false);
+ $consumes = ['application/json'];
- $keys = [
- APP_PLATFORM_CLIENT => [
- 'Project' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Project',
- 'description' => 'Your project ID',
- 'in' => 'header',
- ],
- 'Locale' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Locale',
- 'description' => '',
- 'in' => 'header',
- ],
- ],
- APP_PLATFORM_SERVER => [
- 'Project' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Project',
- 'description' => 'Your project ID',
- 'in' => 'header',
- ],
- 'Key' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Key',
- 'description' => 'Your secret API key',
- 'in' => 'header',
- ],
- 'Locale' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Locale',
- 'description' => '',
- 'in' => 'header',
- ],
- ],
- APP_PLATFORM_CONSOLE => [
- 'Project' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Project',
- 'description' => 'Your project ID',
- 'in' => 'header',
- ],
- 'Key' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Key',
- 'description' => 'Your secret API key',
- 'in' => 'header',
- ],
- 'Locale' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Locale',
- 'description' => '',
- 'in' => 'header',
- ],
- 'Mode' => [
- 'type' => 'apiKey',
- 'name' => 'X-Appwrite-Mode',
- 'description' => '',
- 'in' => 'header',
- ],
- ],
- ];
+ if ($hide) {
+ continue;
+ }
- /*
- * Specifications (v3.0.0):
- * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
- */
- $output = [
- 'swagger' => '2.0',
- 'info' => [
- 'version' => APP_VERSION_STABLE,
- 'title' => APP_NAME,
- 'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)',
- 'termsOfService' => 'https://appwrite.io/policy/terms',
- 'contact' => [
- 'name' => 'Appwrite Team',
- 'url' => 'https://appwrite.io/support',
- 'email' => $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
- ],
- 'license' => [
- 'name' => 'BSD-3-Clause',
- 'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE',
- ],
- ],
- 'host' => \parse_url($request->getServer('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST),
- 'basePath' => '/v1',
- 'schemes' => ['https'],
- 'consumes' => ['application/json', 'multipart/form-data'],
- 'produces' => ['application/json'],
- 'securityDefinitions' => $keys[$platform],
- 'paths' => [],
- 'definitions' => [
- // 'Pet' => [
- // 'required' => ['id', 'name'],
- // 'properties' => [
- // 'id' => [
- // 'type' => 'integer',
- // 'format' => 'int64',
- // ],
- // 'name' => [
- // 'type' => 'string',
- // ],
- // 'tag' => [
- // 'type' => 'string',
+ $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../'.$route->getLabel('sdk.description', '')) : null;
+
+ $temp = [
+ 'summary' => $route->getDesc(),
+ 'operationId' => $route->getLabel('sdk.method', \uniqid()),
+ 'consumes' => [],
+ 'tags' => [$route->getLabel('sdk.namespace', 'default')],
+ 'description' => ($desc) ? \file_get_contents($desc) : '',
+
+ // 'responses' => [
+ // 200 => [
+ // 'description' => 'An paged array of pets',
+ // 'schema' => [
+ // '$ref' => '#/definitions/Pet',
// ],
// ],
// ],
- // 'Pets' => array(
- // 'type' => 'array',
- // 'items' => array(
- // '$ref' => '#/definitions/Pet',
- // ),
- // ),
- 'Error' => array(
- 'required' => array(
- 0 => 'code',
- 1 => 'message',
- ),
- 'properties' => array(
- 'code' => array(
- 'type' => 'integer',
- 'format' => 'int32',
- ),
- 'message' => array(
- 'type' => 'string',
- ),
- ),
- ),
- ],
- 'externalDocs' => [
- 'description' => 'Full API docs, specs and tutorials',
- 'url' => Config::getParam('protocol').'://'.Config::getParam('domain').'/docs',
- ],
- ];
+ ];
- if ($extensions) {
- if (isset($output['securityDefinitions']['Project'])) {
- $output['securityDefinitions']['Project']['extensions'] = ['demo' => '5df5acd0d48c2'];
- }
-
- if (isset($output['securityDefinitions']['Key'])) {
- $output['securityDefinitions']['Key']['extensions'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2'];
- }
-
- if (isset($output['securityDefinitions']['Locale'])) {
- $output['securityDefinitions']['Locale']['extensions'] = ['demo' => 'en'];
- }
+ if ($extensions) {
+ $platformList = $route->getLabel('sdk.platform', []);
- if (isset($output['securityDefinitions']['Mode'])) {
- $output['securityDefinitions']['Mode']['extensions'] = ['demo' => ''];
- }
- }
-
- foreach ($utopia->getRoutes() as $key => $method) {
- foreach ($method as $route) { /* @var $route \Utopia\Route */
- if (!$route->getLabel('docs', true)) {
- continue;
- }
-
- if (empty($route->getLabel('sdk.namespace', null))) {
- continue;
- }
-
- if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) {
- continue;
- }
-
- $url = \str_replace('/v1', '', $route->getURL());
- $scope = $route->getLabel('scope', '');
- $hide = $route->getLabel('sdk.hide', false);
- $consumes = ['application/json'];
-
- if ($hide) {
- continue;
- }
-
- $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath('../'.$route->getLabel('sdk.description', '')) : null;
-
- $temp = [
- 'summary' => $route->getDesc(),
- 'operationId' => $route->getLabel('sdk.method', \uniqid()),
- 'consumes' => [],
- 'tags' => [$route->getLabel('sdk.namespace', 'default')],
- 'description' => ($desc) ? \file_get_contents($desc) : '',
-
- // 'responses' => [
- // 200 => [
- // 'description' => 'An paged array of pets',
- // 'schema' => [
- // '$ref' => '#/definitions/Pet',
- // ],
- // ],
- // ],
+ $temp['extensions'] = [
+ 'weight' => $route->getOrder(),
+ 'cookies' => $route->getLabel('sdk.cookies', false),
+ 'type' => $route->getLabel('sdk.methodType', ''),
+ 'demo' => 'docs/examples/'. Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.Template::fromCamelCaseToDash($temp['operationId']).'.md',
+ 'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
+ 'rate-limit' => $route->getLabel('abuse-limit', 0),
+ 'rate-time' => $route->getLabel('abuse-time', 3600),
+ 'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
+ 'scope' => $route->getLabel('scope', ''),
+ 'platforms' => $platformList,
];
+ }
- if ($extensions) {
- $platformList = $route->getLabel('sdk.platform', []);
+ if ((!empty($scope))) { // && 'public' != $scope
+ $temp['security'][] = $route->getLabel('sdk.security', $security[$platform]);
+ }
- $temp['extensions'] = [
- 'weight' => $route->getOrder(),
- 'cookies' => $route->getLabel('sdk.cookies', false),
- 'type' => $route->getLabel('sdk.methodType', ''),
- 'demo' => 'docs/examples/'.fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.fromCamelCaseToDash($temp['operationId']).'.md',
- 'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
- 'rate-limit' => $route->getLabel('abuse-limit', 0),
- 'rate-time' => $route->getLabel('abuse-time', 3600),
- 'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'),
- 'scope' => $route->getLabel('scope', ''),
- 'platforms' => $platformList,
- ];
- }
-
- if ((!empty($scope))) { // && 'public' != $scope
- $temp['security'][] = $route->getLabel('sdk.security', $security[$platform]);
- }
-
- $requestBody = [
- 'content' => [
- 'application/x-www-form-urlencoded' => [
- 'schema' => [
- 'type' => 'object',
- 'properties' => [],
- ],
- 'required' => [],
+ $requestBody = [
+ 'content' => [
+ 'application/x-www-form-urlencoded' => [
+ 'schema' => [
+ 'type' => 'object',
+ 'properties' => [],
],
+ 'required' => [],
],
+ ],
+ ];
+
+ foreach ($route->getParams() as $name => $param) {
+ $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $utopia->getResources($param['resources'])) : $param['validator']; /* @var $validator \Utopia\Validator */
+
+ $node = [
+ 'name' => $name,
+ 'description' => $param['description'],
+ 'required' => !$param['optional'],
];
- foreach ($route->getParams() as $name => $param) {
- $validator = (\is_callable($param['validator'])) ? $param['validator']() : $param['validator']; /* @var $validator \Utopia\Validator */
-
- $node = [
- 'name' => $name,
- 'description' => $param['description'],
- 'required' => !$param['optional'],
- ];
-
- switch ((!empty($validator)) ? \get_class($validator) : '') {
- case 'Utopia\Validator\Text':
- $node['type'] = 'string';
- $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']';
- break;
- case 'Utopia\Validator\Boolean':
- $node['type'] = 'boolean';
- $node['x-example'] = false;
- break;
- case 'Appwrite\Database\Validator\UID':
- $node['type'] = 'string';
- $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']';
- break;
- case 'Utopia\Validator\Email':
- $node['type'] = 'string';
- $node['format'] = 'email';
- $node['x-example'] = 'email@example.com';
- break;
- case 'Utopia\Validator\URL':
- $node['type'] = 'string';
- $node['format'] = 'url';
- $node['x-example'] = 'https://example.com';
- break;
- case 'Utopia\Validator\JSON':
- case 'Utopia\Validator\Mock':
- case 'Utopia\Validator\Assoc':
- $node['type'] = 'object';
- $node['type'] = 'object';
- $node['x-example'] = '{}';
- //$node['format'] = 'json';
- break;
- case 'Appwrite\Storage\Validator\File':
- $consumes = ['multipart/form-data'];
- $node['type'] = 'file';
- break;
- case 'Utopia\Validator\ArrayList':
- $node['type'] = 'array';
- $node['collectionFormat'] = 'multi';
- $node['items'] = [
- 'type' => 'string',
- ];
- break;
- case 'Appwrite\Auth\Validator\Password':
- $node['type'] = 'string';
- $node['format'] = 'format';
- $node['x-example'] = 'password';
- break;
- case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */
- $node['type'] = 'integer';
- $node['format'] = 'int32';
- $node['x-example'] = $validator->getMin();
- break;
- case 'Utopia\Validator\Numeric':
- $node['type'] = 'integer';
- $node['format'] = 'int32';
- break;
- case 'Utopia\Validator\Length':
- $node['type'] = 'string';
- break;
- case 'Utopia\Validator\Host':
- $node['type'] = 'string';
- $node['format'] = 'url';
- $node['x-example'] = 'https://example.com';
- break;
- case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */
- $node['type'] = 'string';
- $node['x-example'] = $validator->getList()[0];
- break;
- default:
- $node['type'] = 'string';
- break;
- }
-
- if ($param['optional'] && !\is_null($param['default'])) { // Param has default value
- $node['default'] = $param['default'];
- }
-
- if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
- $node['in'] = 'path';
- $temp['parameters'][] = $node;
- } elseif ($key == 'GET') { // Param is in query
- $node['in'] = 'query';
- $temp['parameters'][] = $node;
- } else { // Param is in payload
- $node['in'] = 'formData';
- $temp['parameters'][] = $node;
- $requestBody['content']['application/x-www-form-urlencoded']['schema']['properties'][] = $node;
-
- if (!$param['optional']) {
- $requestBody['content']['application/x-www-form-urlencoded']['required'][] = $name;
- }
- }
-
- $url = \str_replace(':'.$name, '{'.$name.'}', $url);
+ switch ((!empty($validator)) ? \get_class($validator) : '') {
+ case 'Utopia\Validator\Text':
+ $node['type'] = 'string';
+ $node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
+ break;
+ case 'Utopia\Validator\Boolean':
+ $node['type'] = 'boolean';
+ $node['x-example'] = false;
+ break;
+ case 'Appwrite\Database\Validator\UID':
+ $node['type'] = 'string';
+ $node['x-example'] = '['.\strtoupper(Template::fromCamelCaseToSnake($node['name'])).']';
+ break;
+ case 'Utopia\Validator\Email':
+ $node['type'] = 'string';
+ $node['format'] = 'email';
+ $node['x-example'] = 'email@example.com';
+ break;
+ case 'Utopia\Validator\URL':
+ $node['type'] = 'string';
+ $node['format'] = 'url';
+ $node['x-example'] = 'https://example.com';
+ break;
+ case 'Utopia\Validator\JSON':
+ case 'Utopia\Validator\Mock':
+ case 'Utopia\Validator\Assoc':
+ $node['type'] = 'object';
+ $node['type'] = 'object';
+ $node['x-example'] = '{}';
+ //$node['format'] = 'json';
+ break;
+ case 'Appwrite\Storage\Validator\File':
+ $consumes = ['multipart/form-data'];
+ $node['type'] = 'file';
+ break;
+ case 'Utopia\Validator\ArrayList':
+ $node['type'] = 'array';
+ $node['collectionFormat'] = 'multi';
+ $node['items'] = [
+ 'type' => 'string',
+ ];
+ break;
+ case 'Appwrite\Auth\Validator\Password':
+ $node['type'] = 'string';
+ $node['format'] = 'format';
+ $node['x-example'] = 'password';
+ break;
+ case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */
+ $node['type'] = 'integer';
+ $node['format'] = 'int32';
+ $node['x-example'] = $validator->getMin();
+ break;
+ case 'Utopia\Validator\Numeric':
+ $node['type'] = 'integer';
+ $node['format'] = 'int32';
+ break;
+ case 'Utopia\Validator\Length':
+ $node['type'] = 'string';
+ break;
+ case 'Utopia\Validator\Host':
+ $node['type'] = 'string';
+ $node['format'] = 'url';
+ $node['x-example'] = 'https://example.com';
+ break;
+ case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */
+ $node['type'] = 'string';
+ $node['x-example'] = $validator->getList()[0];
+ break;
+ default:
+ $node['type'] = 'string';
+ break;
}
- $temp['consumes'] = $consumes;
+ if ($param['optional'] && !\is_null($param['default'])) { // Param has default value
+ $node['default'] = $param['default'];
+ }
- $output['paths'][$url][\strtolower($route->getMethod())] = $temp;
+ if (false !== \strpos($url, ':'.$name)) { // Param is in URL path
+ $node['in'] = 'path';
+ $temp['parameters'][] = $node;
+ } elseif ($key == 'GET') { // Param is in query
+ $node['in'] = 'query';
+ $temp['parameters'][] = $node;
+ } else { // Param is in payload
+ $node['in'] = 'formData';
+ $temp['parameters'][] = $node;
+ $requestBody['content']['application/x-www-form-urlencoded']['schema']['properties'][] = $node;
+
+ if (!$param['optional']) {
+ $requestBody['content']['application/x-www-form-urlencoded']['required'][] = $name;
+ }
+ }
+
+ $url = \str_replace(':'.$name, '{'.$name.'}', $url);
}
+
+ $temp['consumes'] = $consumes;
+
+ $output['paths'][$url][\strtolower($route->getMethod())] = $temp;
}
-
- /*foreach ($consoleDB->getMocks() as $mock) {
- var_dump($mock['name']);
- }*/
-
- \ksort($output['paths']);
-
- $response
- ->json($output);
}
- );
\ No newline at end of file
+
+ /*foreach ($consoleDB->getMocks() as $mock) {
+ var_dump($mock['name']);
+ }*/
+
+ \ksort($output['paths']);
+
+ $response
+ ->json($output);
+ }, ['utopia', 'request', 'response']);
\ No newline at end of file
diff --git a/app/http.php b/app/http.php
new file mode 100644
index 0000000000..9540375430
--- /dev/null
+++ b/app/http.php
@@ -0,0 +1,114 @@
+set([
+ 'open_http2_protocol' => true,
+ // 'document_root' => __DIR__.'/../public',
+ // 'enable_static_handler' => true,
+ 'timeout' => 7,
+ 'http_compression' => true,
+ 'http_compression_level' => 6,
+ 'package_max_length' => $payloadSize,
+ ])
+;
+
+$http->on('WorkerStart', function($serv, $workerId) {
+ Console::success('Worker '.++$workerId.' started succefully');
+});
+
+$http->on('BeforeReload', function($serv, $workerId) {
+ Console::success('Starting reload...');
+});
+
+$http->on('AfterReload', function($serv, $workerId) {
+ Console::success('Reload completed...');
+});
+
+$http->on('start', function (Server $http) use ($payloadSize) {
+ Console::success('Server started succefully (max payload is '.$payloadSize.' bytes)');
+
+ Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
+
+ // listen ctrl + c
+ Process::signal(2, function () use ($http) {
+ Console::log('Stop by Ctrl+C');
+ $http->shutdown();
+ });
+});
+
+Files::load(__DIR__ . '/../public');
+
+include __DIR__ . '/controllers/general.php';
+
+$domain = App::getEnv('_APP_DOMAIN', '');
+
+Console::info('Issuing a TLS certificate for the master domain ('.$domain.') in 30 seconds.
+ Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script
+
+ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [
+ 'document' => [],
+ 'domain' => $domain,
+ 'validateTarget' => false,
+ 'validateCNAME' => false,
+]);
+
+$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
+ $request = new Request($swooleRequest);
+ $response = new Response($swooleResponse);
+
+ if(Files::isFileLoaded($request->getURI())) {
+ $time = (60 * 60 * 24 * 365 * 2); // 45 days cache
+
+ $response
+ ->setContentType(Files::getFileMimeType($request->getURI()))
+ ->addHeader('Cache-Control', 'public, max-age='.$time)
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache
+ ->send(Files::getFileContents($request->getURI()))
+ ;
+
+ return;
+ }
+
+ $app = new App('Asia/Tel_Aviv');
+
+ try {
+ $app->run($request, $response);
+ } catch (\Throwable $th) {
+ if(App::isDevelopment()) {
+ var_dump(get_class($th));
+ var_dump($th->getMessage());
+ var_dump($th->getFile());
+ var_dump($th->getLine());
+ $swooleResponse->end('error: '.$th->getMessage());
+ }
+
+ $swooleResponse->end('500: Server Error');
+ }
+});
+
+$http->start();
diff --git a/app/init.php b/app/init.php
index 7c4c03fa1e..9637c4381c 100644
--- a/app/init.php
+++ b/app/init.php
@@ -11,19 +11,23 @@ if (\file_exists(__DIR__.'/../vendor/autoload.php')) {
require_once __DIR__.'/../vendor/autoload.php';
}
+use Appwrite\Auth\Auth;
+use Appwrite\Database\Database;
+use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
+use Appwrite\Database\Adapter\Redis as RedisAdapter;
+use Appwrite\Database\Document;
+use Appwrite\Database\Validator\Authorization;
+use Appwrite\Event\Event;
+use Appwrite\Extend\PDO;
+use Appwrite\OpenSSL\OpenSSL;
use Utopia\App;
-use Utopia\Request;
-use Utopia\Response;
+use Utopia\View;
use Utopia\Config\Config;
use Utopia\Locale\Locale;
use Utopia\Registry\Registry;
-use Appwrite\Auth\Auth;
-use Appwrite\Database\Database;
-use Appwrite\Database\Document;
-use Appwrite\Database\Validator\Authorization;
-use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
-use Appwrite\Database\Adapter\Redis as RedisAdapter;
+use GeoIp2\Database\Reader;
use PHPMailer\PHPMailer\PHPMailer;
+use PDO as PDONative;
const APP_NAME = 'Appwrite';
const APP_DOMAIN = 'appwrite.io';
@@ -31,10 +35,11 @@ const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email address
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
const APP_MODE_ADMIN = 'admin';
-const APP_PAGING_LIMIT = 15;
-const APP_CACHE_BUSTER = 125;
-const APP_VERSION_STABLE = '0.6.2';
+const APP_PAGING_LIMIT = 12;
+const APP_CACHE_BUSTER = 127;
+const APP_VERSION_STABLE = '0.7.0';
const APP_STORAGE_UPLOADS = '/storage/uploads';
+const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_CACHE = '/storage/cache';
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
const APP_STORAGE_CONFIG = '/storage/config';
@@ -44,72 +49,103 @@ const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io';
const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite';
const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io';
const APP_SOCIAL_GITHUB = 'https://github.com/appwrite';
-const APP_SOCIAL_DISCORD = 'https://discord.gg/GSeTUeA';
+const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord';
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
+const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
$register = new Registry();
-$request = new Request();
-$response = new Response();
-$utopia = new App('Asia/Tel_Aviv');
-$utopia->setMode($utopia->getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
+App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
/*
* ENV vars
*/
-Config::load('events', __DIR__.'/../app/config/events.php');
-Config::load('providers', __DIR__.'/../app/config/providers.php');
-Config::load('platforms', __DIR__.'/../app/config/platforms.php');
-Config::load('locales', __DIR__.'/../app/config/locales.php');
-Config::load('collections', __DIR__.'/../app/config/collections.php');
-Config::load('roles', __DIR__.'/../app/config/roles.php'); // User roles and scopes
-Config::load('services', __DIR__.'/../app/config/services.php'); // List of services
+Config::load('events', __DIR__.'/config/events.php');
+Config::load('providers', __DIR__.'/config/providers.php');
+Config::load('platforms', __DIR__.'/config/platforms.php');
+Config::load('collections', __DIR__.'/config/collections.php');
+Config::load('environments', __DIR__.'/config/environments.php');
+Config::load('roles', __DIR__.'/config/roles.php'); // User roles and scopes
+Config::load('scopes', __DIR__.'/config/scopes.php'); // User roles and scopes
+Config::load('services', __DIR__.'/config/services.php'); // List of services
+Config::load('variables', __DIR__.'/config/variables.php'); // List of env variables
+Config::load('avatar-browsers', __DIR__.'/config/avatars/browsers.php');
+Config::load('avatar-credit-cards', __DIR__.'/config/avatars/credit-cards.php');
+Config::load('avatar-flags', __DIR__.'/config/avatars/flags.php');
+Config::load('locale-codes', __DIR__.'/config/locale/codes.php');
+Config::load('locale-currencies', __DIR__.'/config/locale/currencies.php');
+Config::load('locale-eu', __DIR__.'/config/locale/eu.php');
+Config::load('locale-languages', __DIR__.'/config/locale/languages.php');
+Config::load('locale-phones', __DIR__.'/config/locale/phones.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');
+Config::load('storage-outputs', __DIR__.'/config/storage/outputs.php');
-Config::setParam('env', $utopia->getMode());
-Config::setParam('domain', $request->getServer('HTTP_HOST', ''));
-Config::setParam('domainVerification', false);
-Config::setParam('version', $request->getServer('_APP_VERSION', 'UNKNOWN'));
-Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https')));
-Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT));
-Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST));
+Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '')
+ .':'.App::getEnv('_APP_REDIS_PORT', ''));
-Resque::setBackend($request->getServer('_APP_REDIS_HOST', '')
- .':'.$request->getServer('_APP_REDIS_PORT', ''));
+/**
+ * DB Filters
+ */
+Database::addFilter('json',
+ function($value) {
+ if(!is_array($value)) {
+ return $value;
+ }
+ return json_encode($value);
+ },
+ function($value) {
+ return json_decode($value, true);
+ }
+);
-\define('COOKIE_DOMAIN',
- (
- $request->getServer('HTTP_HOST', null) === 'localhost' ||
- $request->getServer('HTTP_HOST', null) === 'localhost:'.Config::getParam('port') ||
- (\filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false)
- )
- ? null
- : '.'.Config::getParam('hostname')
- );
-\define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE);
+Database::addFilter('encrypt',
+ function($value) {
+ $key = App::getEnv('_APP_OPENSSL_KEY_V1');
+ $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
+ $tag = null;
+
+ return json_encode([
+ 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
+ 'method' => OpenSSL::CIPHER_AES_128_GCM,
+ 'iv' => bin2hex($iv),
+ 'tag' => bin2hex($tag),
+ 'version' => '1',
+ ]);
+ },
+ function($value) {
+ $value = json_decode($value, true);
+ $key = App::getEnv('_APP_OPENSSL_KEY_V'.$value['version']);
+
+ return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
+ }
+);
/*
* Registry
*/
-$register->set('db', function () use ($request) { // Register DB connection
- $dbHost = $request->getServer('_APP_DB_HOST', '');
- $dbUser = $request->getServer('_APP_DB_USER', '');
- $dbPass = $request->getServer('_APP_DB_PASS', '');
- $dbScheme = $request->getServer('_APP_DB_SCHEMA', '');
+$register->set('db', function () { // Register DB connection
+ $dbHost = App::getEnv('_APP_DB_HOST', '');
+ $dbUser = App::getEnv('_APP_DB_USER', '');
+ $dbPass = App::getEnv('_APP_DB_PASS', '');
+ $dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array(
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
- PDO::ATTR_TIMEOUT => 5, // Seconds
+ PDONative::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
+ PDONative::ATTR_TIMEOUT => 3, // Seconds
+ PDONative::ATTR_PERSISTENT => true
));
// Connection settings
- $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays
- $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions
+ $pdo->setAttribute(PDONative::ATTR_DEFAULT_FETCH_MODE, PDONative::FETCH_ASSOC); // Return arrays
+ $pdo->setAttribute(PDONative::ATTR_ERRMODE, PDONative::ERRMODE_EXCEPTION); // Handle all errors with exceptions
return $pdo;
});
-$register->set('influxdb', function () use ($request) { // Register DB connection
- $host = $request->getServer('_APP_INFLUXDB_HOST', '');
- $port = $request->getServer('_APP_INFLUXDB_PORT', '');
+$register->set('influxdb', function () { // Register DB connection
+ $host = App::getEnv('_APP_INFLUXDB_HOST', '');
+ $port = App::getEnv('_APP_INFLUXDB_PORT', '');
if (empty($host) || empty($port)) {
return;
@@ -119,43 +155,42 @@ $register->set('influxdb', function () use ($request) { // Register DB connectio
return $client;
});
-$register->set('statsd', function () use ($request) { // Register DB connection
- $host = $request->getServer('_APP_STATSD_HOST', 'telegraf');
- $port = $request->getServer('_APP_STATSD_PORT', 8125);
+$register->set('statsd', function () { // Register DB connection
+ $host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
+ $port = App::getEnv('_APP_STATSD_PORT', 8125);
$connection = new \Domnikl\Statsd\Connection\UdpSocket($host, $port);
$statsd = new \Domnikl\Statsd\Client($connection);
return $statsd;
});
-$register->set('cache', function () use ($request) { // Register cache connection
+$register->set('cache', function () { // Register cache connection
$redis = new Redis();
-
- $redis->connect($request->getServer('_APP_REDIS_HOST', ''),
- $request->getServer('_APP_REDIS_PORT', ''));
+ $redis->pconnect(App::getEnv('_APP_REDIS_HOST', '', 2.5),
+ App::getEnv('_APP_REDIS_PORT', ''));
return $redis;
});
-$register->set('smtp', function () use ($request) {
+$register->set('smtp', function () {
$mail = new PHPMailer(true);
$mail->isSMTP();
- $username = $request->getServer('_APP_SMTP_USERNAME', null);
- $password = $request->getServer('_APP_SMTP_PASSWORD', null);
+ $username = App::getEnv('_APP_SMTP_USERNAME', null);
+ $password = App::getEnv('_APP_SMTP_PASSWORD', null);
$mail->XMailer = 'Appwrite Mailer';
- $mail->Host = $request->getServer('_APP_SMTP_HOST', 'smtp');
- $mail->Port = $request->getServer('_APP_SMTP_PORT', 25);
+ $mail->Host = App::getEnv('_APP_SMTP_HOST', 'smtp');
+ $mail->Port = App::getEnv('_APP_SMTP_PORT', 25);
$mail->SMTPAuth = (!empty($username) && !empty($password));
$mail->Username = $username;
$mail->Password = $password;
- $mail->SMTPSecure = $request->getServer('_APP_SMTP_SECURE', false);
+ $mail->SMTPSecure = App::getEnv('_APP_SMTP_SECURE', false);
$mail->SMTPAutoTLS = false;
$mail->CharSet = 'UTF-8';
- $from = \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'));
- $email = $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
+ $from = \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'));
+ $email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$mail->setFrom($email, $from);
$mail->addReplyTo($email, $from);
@@ -164,154 +199,271 @@ $register->set('smtp', function () use ($request) {
return $mail;
});
+$register->set('queue-webhooks', function () {
+ return new Event('v1-webhooks', 'WebhooksV1');
+});
+$register->set('queue-audits', function () {
+ return new Event('v1-audits', 'AuditsV1');
+});
+$register->set('queue-usage', function () {
+ return new Event('v1-usage', 'UsageV1');
+});
+$register->set('queue-mails', function () {
+ return new Event('v1-mails', 'MailsV1');
+});
+$register->set('queue-deletes', function () {
+ return new Event('v1-deletes', 'DeletesV1');
+});
+$register->set('queue-functions', function () {
+ return new Event('v1-functions', 'FunctionsV1');
+});
/*
* Localization
*/
-$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', ''));
-
Locale::$exceptions = false;
-
-Locale::setLanguage('af', include __DIR__.'/config/locales/af.php');
-Locale::setLanguage('ar', include __DIR__.'/config/locales/ar.php');
-Locale::setLanguage('bn', include __DIR__.'/config/locales/bn.php');
-Locale::setLanguage('cat', include __DIR__.'/config/locales/cat.php');
-Locale::setLanguage('cz', include __DIR__.'/config/locales/cz.php');
-Locale::setLanguage('de', include __DIR__.'/config/locales/de.php');
-Locale::setLanguage('en', include __DIR__.'/config/locales/en.php');
-Locale::setLanguage('es', include __DIR__.'/config/locales/es.php');
-Locale::setLanguage('fi', include __DIR__.'/config/locales/fi.php');
-Locale::setLanguage('fo', include __DIR__.'/config/locales/fo.php');
-Locale::setLanguage('fr', include __DIR__.'/config/locales/fr.php');
-Locale::setLanguage('gr', include __DIR__.'/config/locales/gr.php');
-Locale::setLanguage('he', include __DIR__.'/config/locales/he.php');
-Locale::setLanguage('hi', include __DIR__.'/config/locales/hi.php');
-Locale::setLanguage('hu', include __DIR__.'/config/locales/hu.php');
-Locale::setLanguage('hy', include __DIR__.'/config/locales/hy.php');
-Locale::setLanguage('id', include __DIR__.'/config/locales/id.php');
-Locale::setLanguage('is', include __DIR__.'/config/locales/is.php');
-Locale::setLanguage('it', include __DIR__.'/config/locales/it.php');
-Locale::setLanguage('ja', include __DIR__.'/config/locales/ja.php');
-Locale::setLanguage('jv', include __DIR__.'/config/locales/jv.php');
-Locale::setLanguage('km', include __DIR__.'/config/locales/km.php');
-Locale::setLanguage('ko', include __DIR__.'/config/locales/ko.php');
-Locale::setLanguage('lt', include __DIR__.'/config/locales/lt.php');
-Locale::setLanguage('ml', include __DIR__.'/config/locales/ml.php');
-Locale::setLanguage('ms', include __DIR__.'/config/locales/ms.php');
-Locale::setLanguage('nl', include __DIR__.'/config/locales/nl.php');
-Locale::setLanguage('no', include __DIR__.'/config/locales/no.php');
-Locale::setLanguage('ph', include __DIR__.'/config/locales/ph.php');
-Locale::setLanguage('pl', include __DIR__.'/config/locales/pl.php');
-Locale::setLanguage('pt-br', include __DIR__.'/config/locales/pt-br.php');
-Locale::setLanguage('pt-pt', include __DIR__.'/config/locales/pt-pt.php');
-Locale::setLanguage('ro', include __DIR__.'/config/locales/ro.php');
-Locale::setLanguage('ru', include __DIR__ . '/config/locales/ru.php');
-Locale::setLanguage('si', include __DIR__ . '/config/locales/si.php');
-Locale::setLanguage('sl', include __DIR__ . '/config/locales/sl.php');
-Locale::setLanguage('sq', include __DIR__ . '/config/locales/sq.php');
-Locale::setLanguage('sv', include __DIR__ . '/config/locales/sv.php');
-Locale::setLanguage('ta', include __DIR__ . '/config/locales/ta.php');
-Locale::setLanguage('th', include __DIR__.'/config/locales/th.php');
-Locale::setLanguage('tr', include __DIR__.'/config/locales/tr.php');
-Locale::setLanguage('ua', include __DIR__.'/config/locales/ua.php');
-Locale::setLanguage('vi', include __DIR__.'/config/locales/vi.php');
-Locale::setLanguage('zh-cn', include __DIR__.'/config/locales/zh-cn.php');
-Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php');
-
-Locale::setDefault('en');
-
-if (\in_array($locale, Config::getParam('locales'))) {
- Locale::setDefault($locale);
-}
+Locale::setLanguage('af', include __DIR__.'/config/locale/translations/af.php');
+Locale::setLanguage('ar', include __DIR__.'/config/locale/translations/ar.php');
+Locale::setLanguage('ba', include __DIR__.'/config/locale/translations/ba.php');
+Locale::setLanguage('be', include __DIR__.'/config/locale/translations/be.php');
+Locale::setLanguage('bg', include __DIR__.'/config/locale/translations/bg.php');
+Locale::setLanguage('bn', include __DIR__.'/config/locale/translations/bn.php');
+Locale::setLanguage('cat', include __DIR__.'/config/locale/translations/cat.php');
+Locale::setLanguage('cz', include __DIR__.'/config/locale/translations/cz.php');
+Locale::setLanguage('de', include __DIR__.'/config/locale/translations/de.php');
+Locale::setLanguage('en', include __DIR__.'/config/locale/translations/en.php');
+Locale::setLanguage('es', include __DIR__.'/config/locale/translations/es.php');
+Locale::setLanguage('fi', include __DIR__.'/config/locale/translations/fi.php');
+Locale::setLanguage('fo', include __DIR__.'/config/locale/translations/fo.php');
+Locale::setLanguage('fr', include __DIR__.'/config/locale/translations/fr.php');
+Locale::setLanguage('gr', include __DIR__.'/config/locale/translations/gr.php');
+Locale::setLanguage('gu', include __DIR__.'/config/locale/translations/gu.php');
+Locale::setLanguage('he', include __DIR__.'/config/locale/translations/he.php');
+Locale::setLanguage('hi', include __DIR__.'/config/locale/translations/hi.php');
+Locale::setLanguage('hu', include __DIR__.'/config/locale/translations/hu.php');
+Locale::setLanguage('hy', include __DIR__.'/config/locale/translations/hy.php');
+Locale::setLanguage('id', include __DIR__.'/config/locale/translations/id.php');
+Locale::setLanguage('is', include __DIR__.'/config/locale/translations/is.php');
+Locale::setLanguage('it', include __DIR__.'/config/locale/translations/it.php');
+Locale::setLanguage('ja', include __DIR__.'/config/locale/translations/ja.php');
+Locale::setLanguage('jv', include __DIR__.'/config/locale/translations/jv.php');
+Locale::setLanguage('ka', include __DIR__.'/config/locale/translations/ka.php');
+Locale::setLanguage('km', include __DIR__.'/config/locale/translations/km.php');
+Locale::setLanguage('ko', include __DIR__.'/config/locale/translations/ko.php');
+Locale::setLanguage('lt', include __DIR__.'/config/locale/translations/lt.php');
+Locale::setLanguage('ml', include __DIR__.'/config/locale/translations/ml.php');
+Locale::setLanguage('mr', include __DIR__.'/config/locale/translations/mr.php');
+Locale::setLanguage('ms', include __DIR__.'/config/locale/translations/ms.php');
+Locale::setLanguage('nl', include __DIR__.'/config/locale/translations/nl.php');
+Locale::setLanguage('no', include __DIR__.'/config/locale/translations/no.php');
+Locale::setLanguage('np', include __DIR__.'/config/locale/translations/np.php');
+Locale::setLanguage('od', include __DIR__.'/config/locale/translations/od.php');
+Locale::setLanguage('ph', include __DIR__.'/config/locale/translations/ph.php');
+Locale::setLanguage('pl', include __DIR__.'/config/locale/translations/pl.php');
+Locale::setLanguage('pt-br', include __DIR__.'/config/locale/translations/pt-br.php');
+Locale::setLanguage('pt-pt', include __DIR__.'/config/locale/translations/pt-pt.php');
+Locale::setLanguage('pa', include __DIR__.'/config/locale/translations/pa.php');
+Locale::setLanguage('ro', include __DIR__.'/config/locale/translations/ro.php');
+Locale::setLanguage('ru', include __DIR__ . '/config/locale/translations/ru.php');
+Locale::setLanguage('si', include __DIR__ . '/config/locale/translations/si.php');
+Locale::setLanguage('sl', include __DIR__ . '/config/locale/translations/sl.php');
+Locale::setLanguage('sq', include __DIR__ . '/config/locale/translations/sq.php');
+Locale::setLanguage('sv', include __DIR__ . '/config/locale/translations/sv.php');
+Locale::setLanguage('ta', include __DIR__ . '/config/locale/translations/ta.php');
+Locale::setLanguage('th', include __DIR__.'/config/locale/translations/th.php');
+Locale::setLanguage('tr', include __DIR__.'/config/locale/translations/tr.php');
+Locale::setLanguage('ua', include __DIR__.'/config/locale/translations/ua.php');
+Locale::setLanguage('ur', include __DIR__.'/config/locale/translations/ur.php');
+Locale::setLanguage('vi', include __DIR__.'/config/locale/translations/vi.php');
+Locale::setLanguage('zh-cn', include __DIR__.'/config/locale/translations/zh-cn.php');
+Locale::setLanguage('zh-tw', include __DIR__.'/config/locale/translations/zh-tw.php');
\stream_context_set_default([ // Set global user agent and http settings
'http' => [
'method' => 'GET',
'user_agent' => \sprintf(APP_USERAGENT,
- Config::getParam('version'),
- $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)),
+ App::getEnv('_APP_VERSION', 'UNKNOWN'),
+ App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)),
'timeout' => 2,
],
]);
-/*
- * Auth & Project Scope
- */
-$consoleDB = new Database();
-$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
-$consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects
+// Runtime Execution
-$consoleDB->setMocks(Config::getParam('collections', []));
-Authorization::disable();
+App::setResource('register', function() use ($register) {
+ return $register;
+});
-$project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', '')));
+App::setResource('layout', function($locale) {
+ $layout = new View(__DIR__.'/views/layouts/default.phtml');
+ $layout->setParam('locale', $locale);
-Authorization::enable();
+ return $layout;
+}, ['locale']);
-$console = $consoleDB->getDocument('console');
+App::setResource('locale', function() {
+ return new Locale('en');
+});
-$mode = $request->getParam('mode', $request->getHeader('X-Appwrite-Mode', 'default'));
+// Queues
+App::setResource('webhooks', function($register) {
+ return $register->get('queue-webhooks');
+}, ['register']);
-Auth::setCookieName('a_session_'.$project->getId());
+App::setResource('audits', function($register) {
+ return $register->get('queue-audits');
+}, ['register']);
-if (APP_MODE_ADMIN === $mode) {
- Auth::setCookieName('a_session_'.$console->getId());
-}
+App::setResource('usage', function($register) {
+ return $register->get('queue-usage');
+}, ['register']);
-$session = Auth::decodeSession(
- $request->getCookie(Auth::$cookieName, // Get sessions
- $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support)
- $request->getHeader('X-Appwrite-Key', '')))); // Get API Key
+App::setResource('mails', function($register) {
+ return $register->get('queue-mails');
+}, ['register']);
-// Get fallback session from clients who block 3rd-party cookies
-$response->addHeader('X-Debug-Fallback', 'false');
+App::setResource('deletes', function($register) {
+ return $register->get('queue-deletes');
+}, ['register']);
-if(empty($session['id']) && empty($session['secret'])) {
- $response->addHeader('X-Debug-Fallback', 'true');
- $fallback = $request->getHeader('X-Fallback-Cookies', '');
- $fallback = \json_decode($fallback, true);
- $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
-}
+App::setResource('functions', function($register) {
+ return $register->get('queue-functions');
+}, ['register']);
-Auth::$unique = $session['id'];
-Auth::$secret = $session['secret'];
+// Test Mock
+App::setResource('clients', function($console, $project) {
+ /**
+ * Get All verified client URLs for both console and current projects
+ * + Filter for duplicated entries
+ */
+ $clientsConsole = \array_map(function ($node) {
+ return $node['hostname'];
+ }, \array_filter($console->getAttribute('platforms', []), function ($node) {
+ if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
+ return true;
+ }
-$projectDB = new Database();
-$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
-$projectDB->setNamespace('app_'.$project->getId());
-$projectDB->setMocks(Config::getParam('collections', []));
+ return false;
+ }));
-if (APP_MODE_ADMIN !== $mode) {
- $user = $projectDB->getDocument(Auth::$unique);
-}
-else {
- $user = $consoleDB->getDocument(Auth::$unique);
+ $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) {
+ return $node['hostname'];
+ }, \array_filter($project->getAttribute('platforms', []), function ($node) {
+ if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) {
+ return true;
+ }
- $user
- ->setAttribute('$id', 'admin-'.$user->getAttribute('$id'))
- ;
-}
+ return false;
+ }))));
-if (empty($user->getId()) // Check a document has been found in the DB
- || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document
- || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token
- $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
-}
+ return $clients;
+}, ['console', 'project']);
-if (APP_MODE_ADMIN === $mode) {
- if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) {
- Authorization::disable();
- } else {
+App::setResource('user', function($mode, $project, $console, $request, $response, $projectDB, $consoleDB) {
+ /** @var Utopia\Request $request */
+ /** @var Utopia\Response $response */
+ /** @var Appwrite\Database\Document $project */
+ /** @var Appwrite\Database\Database $consoleDB */
+ /** @var Appwrite\Database\Database $projectDB */
+ /** @var bool $mode */
+
+ Authorization::setDefaultStatus(true);
+
+ Auth::setCookieName('a_session_'.$project->getId());
+
+ if (APP_MODE_ADMIN === $mode) {
+ Auth::setCookieName('a_session_'.$console->getId());
+ }
+
+ $session = Auth::decodeSession(
+ $request->getCookie(Auth::$cookieName, // Get sessions
+ $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support)
+ $request->getHeader('x-appwrite-key', '')))); // Get API Key
+
+ // Get fallback session from clients who block 3rd-party cookies
+ $response->addHeader('X-Debug-Fallback', 'false');
+
+ if(empty($session['id']) && empty($session['secret'])) {
+ $response->addHeader('X-Debug-Fallback', 'true');
+ $fallback = $request->getHeader('x-fallback-cookies', '');
+ $fallback = \json_decode($fallback, true);
+ $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
+ }
+
+ Auth::$unique = $session['id'];
+ Auth::$secret = $session['secret'];
+
+ if (APP_MODE_ADMIN !== $mode) {
+ $user = $projectDB->getDocument(Auth::$unique);
+ }
+ else {
+ $user = $consoleDB->getDocument(Auth::$unique);
+
+ $user
+ ->setAttribute('$id', 'admin-'.$user->getAttribute('$id'))
+ ;
+ }
+
+ if (empty($user->getId()) // Check a document has been found in the DB
+ || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document
+ || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token
$user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
}
-}
-// Set project mail
-$register->get('smtp')
- ->setFrom(
- $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
- ($project->getId() === 'console')
- ? \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server'))
- : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name')
- )
- );
+ if (APP_MODE_ADMIN === $mode) {
+ if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) {
+ Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
+ } else {
+ $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
+ }
+ }
+
+ return $user;
+}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']);
+
+App::setResource('project', function($consoleDB, $request) {
+ /** @var Appwrite\Swoole\Request $request */
+ /** @var Appwrite\Database\Database $consoleDB */
+
+ Authorization::disable();
+
+ $project = $consoleDB->getDocument($request->getParam('project',
+ $request->getHeader('x-appwrite-project', '')));
+
+ Authorization::reset();
+
+ return $project;
+}, ['consoleDB', 'request']);
+
+App::setResource('console', function($consoleDB) {
+ return $consoleDB->getDocument('console');
+}, ['consoleDB']);
+
+App::setResource('consoleDB', function($register) {
+ $consoleDB = new Database();
+ $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
+ $consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects
+
+ $consoleDB->setMocks(Config::getParam('collections', []));
+
+ return $consoleDB;
+}, ['register']);
+
+App::setResource('projectDB', function($register, $project) {
+ $projectDB = new Database();
+ $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register));
+ $projectDB->setNamespace('app_'.$project->getId());
+ $projectDB->setMocks(Config::getParam('collections', []));
+
+ return $projectDB;
+}, ['register', 'project']);
+
+App::setResource('mode', function($request) {
+ /** @var Utopia\Request $request */
+ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', 'default'));
+}, ['request']);
+
+App::setResource('geodb', function($request) {
+ /** @var Utopia\Request $request */
+ return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2020-01.mmdb');
+}, ['request']);
diff --git a/app/preload.php b/app/preload.php
new file mode 100644
index 0000000000..aded885c10
--- /dev/null
+++ b/app/preload.php
@@ -0,0 +1,36 @@
+paths(realpath(__DIR__ . '/../app/config'))
+ ->paths(realpath(__DIR__ . '/../app/controllers'))
+ ->paths(realpath(__DIR__ . '/../src'))
+ ->ignore(realpath(__DIR__ . '/../vendor/twig/twig'))
+ ->ignore(realpath(__DIR__ . '/../vendor/guzzlehttp/guzzle'))
+ ->ignore(realpath(__DIR__ . '/../vendor/geoip2'))
+ ->ignore(realpath(__DIR__ . '/../vendor/domnikl'))
+ ->ignore(realpath(__DIR__ . '/../vendor/maxmind'))
+ ->ignore(realpath(__DIR__ . '/../vendor/maxmind-db'))
+ ->ignore(realpath(__DIR__ . '/../vendor/psr/log'))
+ ->ignore(realpath(__DIR__ . '/../vendor/piwik'))
+ ->ignore(realpath(__DIR__ . '/../vendor/symfony'))
+ ->load();
\ No newline at end of file
diff --git a/app/sdks/client-flutter/CHANGELOG.md b/app/sdks/client-flutter/CHANGELOG.md
index 638d0882d2..a584ad8b9a 100644
--- a/app/sdks/client-flutter/CHANGELOG.md
+++ b/app/sdks/client-flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.0-dev.2
+
+- Fix for an error when using a self-signed certificate for Web
+
## 0.3.0-dev.1
- Updated package dependencies (@lohanidamodar)
diff --git a/app/sdks/client-flutter/README.md b/app/sdks/client-flutter/README.md
index 95cdeae642..d272426ee3 100644
--- a/app/sdks/client-flutter/README.md
+++ b/app/sdks/client-flutter/README.md
@@ -20,7 +20,7 @@ Add this to your package's `pubspec.yaml` file:
```yml
dependencies:
- appwrite: ^0.3.0-dev.1
+ appwrite: ^0.3.0-dev.2
```
You can install packages from the command line:
diff --git a/app/sdks/client-flutter/lib/client.dart b/app/sdks/client-flutter/lib/client.dart
index 1cf3ac8f0d..ee55d8ba18 100644
--- a/app/sdks/client-flutter/lib/client.dart
+++ b/app/sdks/client-flutter/lib/client.dart
@@ -35,7 +35,7 @@ class Client {
this.headers = {
'content-type': 'application/json',
- 'x-sdk-version': 'appwrite:flutter:0.3.0-dev.1',
+ 'x-sdk-version': 'appwrite:flutter:0.3.0-dev.2',
};
this.config = {};
@@ -101,7 +101,7 @@ class Client {
}
Future call(HttpMethod method, {String path = '', Map headers = const {}, Map params = const {}}) async {
- if(selfSigned) {
+ if(selfSigned && !kIsWeb) {
// Allow self signed requests
(http.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) {
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
diff --git a/app/sdks/client-flutter/lib/services/account.dart b/app/sdks/client-flutter/lib/services/account.dart
index 617ea83b98..890303a9d6 100644
--- a/app/sdks/client-flutter/lib/services/account.dart
+++ b/app/sdks/client-flutter/lib/services/account.dart
@@ -1,8 +1,10 @@
import 'dart:io';
+import 'package:universal_html/html.dart' as html;
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import "../client.dart";
@@ -337,19 +339,26 @@ class Account extends Service {
query: query.join('&')
);
- return FlutterWebAuth.authenticate(
- url: url.toString(),
- callbackUrlScheme: "appwrite-callback-" + client.config['project']
- ).then((value) async {
- Uri url = Uri.parse(value);
- Cookie cookie = new Cookie(url.queryParameters['key'], url.queryParameters['secret']);
- cookie.domain = Uri.parse(client.endPoint).host;
- cookie.httpOnly = true;
- cookie.path = '/';
- List cookies = [cookie];
- await client.init();
- client.cookieJar.saveFromResponse(Uri.parse(client.endPoint), cookies);
- });
+ if(kIsWeb) {
+ html.window.location.href = url.toString();
+ return null;
+ }else{
+
+ return FlutterWebAuth.authenticate(
+ url: url.toString(),
+ callbackUrlScheme: "appwrite-callback-" + client.config['project']
+ ).then((value) async {
+ Uri url = Uri.parse(value);
+ Cookie cookie = new Cookie(url.queryParameters['key'], url.queryParameters['secret']);
+ cookie.domain = Uri.parse(client.endPoint).host;
+ cookie.httpOnly = true;
+ cookie.path = '/';
+ List cookies = [cookie];
+ await client.init();
+ client.cookieJar.saveFromResponse(Uri.parse(client.endPoint), cookies);
+ });
+ }
+
}
/// Delete Account Session
@@ -376,16 +385,17 @@ class Account extends Service {
/// Use this endpoint to send a verification message to your user email address
/// to confirm they are the valid owners of that address. Both the **userId**
/// and **secret** arguments will be passed as query parameters to the URL you
- /// have provider to be attached to the verification email. The provided URL
- /// should redirect the user back for your app and allow you to complete the
+ /// have provided to be attached to the verification email. The provided URL
+ /// should redirect the user back to your app and allow you to complete the
/// verification process by verifying both the **userId** and **secret**
/// parameters. Learn more about how to [complete the verification
/// process](/docs/client/account#updateAccountVerification).
///
/// Please note that in order to avoid a [Redirect
- /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+ /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
/// the only valid redirect URLs are the ones from domains you have set when
/// adding your platforms in the console interface.
+ ///
///
Future createVerification({@required String url}) {
final String path = '/account/verification';
diff --git a/app/sdks/client-flutter/lib/services/avatars.dart b/app/sdks/client-flutter/lib/services/avatars.dart
index a57b3b564c..b7fa6393ef 100644
--- a/app/sdks/client-flutter/lib/services/avatars.dart
+++ b/app/sdks/client-flutter/lib/services/avatars.dart
@@ -190,6 +190,10 @@ class Avatars extends Service {
'project': client.config['project'],
};
+ params.keys.forEach((key) {if (params[key] is int || params[key] is double) {
+ params[key] = params[key].toString();
+ }});
+
Uri endpoint = Uri.parse(client.endPoint);
Uri location = new Uri(scheme: endpoint.scheme,
host: endpoint.host,
@@ -206,7 +210,7 @@ class Avatars extends Service {
/// Converts a given plain text to a QR code image. You can use the query
/// parameters to change the size and style of the resulting image.
///
- String getQR({@required String text, int size = 400, int margin = 1, int download = 0}) {
+ String getQR({@required String text, int size = 400, int margin = 1, bool download = false}) {
final String path = '/avatars/qr';
final Map params = {
diff --git a/app/sdks/client-flutter/lib/services/database.dart b/app/sdks/client-flutter/lib/services/database.dart
index 88082feef7..0fec1fb4d6 100644
--- a/app/sdks/client-flutter/lib/services/database.dart
+++ b/app/sdks/client-flutter/lib/services/database.dart
@@ -17,7 +17,7 @@ class Database extends Service {
/// of the project documents. [Learn more about different API
/// modes](/docs/admin).
///
- Future listDocuments({@required String collectionId, List filters = const [], int offset = 0, int limit = 50, String orderField = '\$id', OrderType orderType = OrderType.asc, String orderCast = 'string', String search = '', int first = 0, int last = 0}) {
+ Future listDocuments({@required String collectionId, List filters = const [], int offset = 0, int limit = 50, String orderField = '\$id', OrderType orderType = OrderType.asc, String orderCast = 'string', String search = ''}) {
final String path = '/database/collections/{collectionId}/documents'.replaceAll(RegExp('{collectionId}'), collectionId);
final Map params = {
@@ -28,8 +28,6 @@ class Database extends Service {
'orderType': orderType.name(),
'orderCast': orderCast,
'search': search,
- 'first': first,
- 'last': last,
};
final Map headers = {
diff --git a/app/sdks/client-flutter/pubspec.yaml b/app/sdks/client-flutter/pubspec.yaml
index 2d0c2fe412..cabc271c5e 100644
--- a/app/sdks/client-flutter/pubspec.yaml
+++ b/app/sdks/client-flutter/pubspec.yaml
@@ -1,5 +1,5 @@
name: appwrite
-version: 0.3.0-dev.1
+version: 0.3.0-dev.2
description: Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API
homepage: https://appwrite.io
repository: https://github.com/appwrite/sdk-for-flutter
@@ -15,6 +15,7 @@ dependencies:
cookie_jar: ^1.0.1
dio_cookie_manager: ^1.0.0
flutter_web_auth: ^0.2.4
+ universal_html: ^1.2.3
flutter:
sdk: flutter
diff --git a/app/sdks/client-swift/CHANGELOG.md b/app/sdks/client-swift/CHANGELOG.md
new file mode 100644
index 0000000000..fa4d35e687
--- /dev/null
+++ b/app/sdks/client-swift/CHANGELOG.md
@@ -0,0 +1 @@
+# Change Log
\ No newline at end of file
diff --git a/app/sdks/client-swift/LICENSE b/app/sdks/client-swift/LICENSE
new file mode 100644
index 0000000000..fc7c051a91
--- /dev/null
+++ b/app/sdks/client-swift/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2019 Appwrite (https://appwrite.io) and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name Appwrite nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/app/sdks/client-swift/Package.swift b/app/sdks/client-swift/Package.swift
new file mode 100644
index 0000000000..5bea7f1c50
--- /dev/null
+++ b/app/sdks/client-swift/Package.swift
@@ -0,0 +1,35 @@
+// swift-tools-version:5.1
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+//
+// Created by Armino
+// GitHub: https://github.com/armino-dev/sdk-generator
+//
+
+import PackageDescription
+
+let package = Package(
+ name: "Appwrite",
+ products: [
+ // Products define the executables and libraries produced by a package,
+ // and make them visible to other packages.
+ .library(
+ name: "Appwrite",
+ targets: ["Appwrite"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ // .package(url: /* package url */, from: "1.0.0"),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package.
+ // A target can define a module or a test suite.
+ // Targets can depend on other targets in this package,
+ // and on products in packages which this package depends on.
+ .target(
+ name: "Appwrite",
+ dependencies: []),
+ .testTarget(
+ name: "AppwriteTests",
+ dependencies: [Appwrite]),
+ ]
+)
diff --git a/app/sdks/client-swift/README.md b/app/sdks/client-swift/README.md
new file mode 100644
index 0000000000..d46757f068
--- /dev/null
+++ b/app/sdks/client-swift/README.md
@@ -0,0 +1,24 @@
+# Appwrite Swift SDK
+
+
+
+
+Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way.
+ Use the Swift SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools.
+ For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)
+
+
+
+
+
+## Installation
+
+```
+ git clone appwrite/sdk-for-swift
+ cd sdk-for-swift
+ swift run
+```
+
+## License
+
+Please see the [BSD-3-Clause license](https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE) file for more information.
diff --git a/app/sdks/client-swift/Sources/Appwrite/Client.swift b/app/sdks/client-swift/Sources/Appwrite/Client.swift
new file mode 100644
index 0000000000..8748fbc0f9
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Client.swift
@@ -0,0 +1,231 @@
+//
+// Client.swift
+//
+// Created by Armino
+// GitHub: https://github.com/armino-dev/sdk-generator
+//
+
+import Foundation
+
+open class Client {
+
+ // MARK: Properties
+
+ open var selfSigned = false
+
+ open var endpoint = "https://appwrite.io/v1"
+
+ open var headers: [String: String] = [
+ "content-type": "",
+ "x-sdk-version": "appwrite:swift:"
+ ]
+
+
+ // MARK: Methods
+
+ // default constructor
+ public init() {
+
+ }
+
+ ///
+ /// Set Project
+ ///
+ /// Your project ID
+ ///
+ /// @param String value
+ ///
+ /// @return Client
+ ///
+ open func setProject(value: String) -> Client {
+
+ self.addHeader(key: "X-Appwrite-Project", value: value)
+ return self
+ }
+
+ ///
+ /// Set Locale
+ ///
+ /// @param String value
+ ///
+ /// @return Client
+ ///
+ open func setLocale(value: String) -> Client {
+
+ self.addHeader(key: "X-Appwrite-Locale", value: value)
+ return self
+ }
+
+
+ ///
+ /// @param Bool status
+ /// @return Client
+ ///
+ open func setSelfSigned(status: Bool = true) -> Client {
+
+ self.selfSigned = status
+ return self
+ }
+
+ ///
+ /// @param String endpoint
+ /// @return Client
+ ///
+ open func setEndpoint(endpoint: String) -> Client {
+
+ self.endpoint = endpoint
+ return self
+ }
+
+ ///
+ /// @param String key
+ /// @param String value
+ ///
+ open func addHeader(key: String, value: String) -> Client {
+
+ self.headers[key.lowercased()] = value.lowercased()
+
+ return self
+ }
+
+ ///
+ open func httpBuildQuery(params: [String: Any], prefix: String = "") -> String {
+ var output: String = ""
+ for (key, value) in params {
+ let finalKey: String = prefix.isEmpty ? key : (prefix + "[" + key + "]")
+ if (value is AnyCollection) {
+ output += self.httpBuildQuery(params: value as! [String : Any], prefix: finalKey)
+ } else {
+ output += "\(value)"
+ }
+ output += "&"
+ }
+ return output
+ }
+
+ ///
+ /// Make an API call
+ ///
+ /// @param String method
+ /// @param String path
+ /// @param Array params
+ /// @param Array headers
+ /// @return Array|String
+ /// @throws Exception
+ ///
+ func call(method:String, path:String = "", headers:[String: String] = [:], params:[String: Any] = [:]) -> Any {
+
+ self.headers.merge(headers){(_, new) in new}
+ let targetURL:URL = URL(string: self.endpoint + path + (( method == HTTPMethod.get.rawValue && !params.isEmpty ) ? "?" + httpBuildQuery(params: params) : ""))!
+
+ var query: String = ""
+
+ var responseStatus: Int = HTTPStatus.unknown.rawValue
+ var responseType: String = ""
+ var responseBody: Any = ""
+
+ switch (self.headers["content-type"]) {
+ case "application/json":
+ do {
+ let json = try JSONSerialization.data(withJSONObject:params, options: [])
+ query = String( data: json, encoding: String.Encoding.utf8)!
+ } catch {
+ print("Failed to parse json: \(error.localizedDescription)")
+ }
+ break
+ default:
+ query = self.httpBuildQuery(params: params)
+ break
+ }
+
+ var request = URLRequest(url: targetURL)
+ let session = URLSession.shared
+
+ for (key, value) in self.headers {
+ request.setValue(value, forHTTPHeaderField: key)
+ }
+
+ request.httpMethod = method
+ if (method.uppercased() == "POST") {
+ request.httpBody = query.data(using: .utf8)
+ }
+
+ let semaphore = DispatchSemaphore(value: 0)
+
+ session.dataTask(with: request) { data, response, error in
+ if (error != nil) {
+ print(error!)
+ return
+ }
+ do {
+ let httpResponse = response as! HTTPURLResponse
+ responseStatus = httpResponse.statusCode
+
+ if (responseStatus == HTTPStatus.internalServerError.rawValue) {
+ print(responseStatus)
+ return
+ }
+
+ responseType = httpResponse.mimeType ?? ""
+
+ if (responseType == "application/json") {
+ let json = try JSONSerialization.jsonObject(with: data!, options: [])
+ responseBody = json
+ } else {
+ responseBody = String(data: data!, encoding: String.Encoding.utf8)!
+ }
+ } catch {
+ print(error)
+ }
+
+ semaphore.signal()
+ }.resume()
+
+ _ = semaphore.wait(wallTimeout: .distantFuture)
+
+ return responseBody
+ }
+
+}
+
+extension Client {
+
+ public enum HTTPStatus: Int {
+ case unknown = -1
+
+ case ok = 200
+ case created = 201
+ case accepted = 202
+
+ case movedPermanently = 301
+ case found = 302
+
+ case badRequest = 400
+ case notAuthorized = 401
+ case paymentRequired = 402
+ case forbidden = 403
+ case notFound = 404
+ case methodNotAllowed = 405
+ case notAcceptable = 406
+
+ case internalServerError = 500
+ case notImplemented = 501
+ }
+
+ public enum HTTPMethod: String {
+ case get
+
+ case post
+ case put
+ case patch
+
+ case delete
+
+ case head
+ case options
+ case connect
+ case trace
+ }
+
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Service.swift b/app/sdks/client-swift/Sources/Appwrite/Service.swift
new file mode 100644
index 0000000000..b11a067a5d
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Service.swift
@@ -0,0 +1,16 @@
+//
+// Service.swift
+//
+// Created by Armino
+// GitHub: https://github.com/armino-dev/sdk-generator
+//
+
+open class Service {
+
+ open var client: Client;
+
+ public init(client: Client)
+ {
+ self.client = client
+ }
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Account.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Account.swift
new file mode 100644
index 0000000000..0ffb74df52
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Account.swift
@@ -0,0 +1,491 @@
+
+
+class Account: Service
+{
+ /**
+ * Get Account
+ *
+ * Get currently logged in user data as JSON object.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func get() -> Array {
+ let path: String = "/account"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Account
+ *
+ * Use this endpoint to allow a new user to register a new account in your
+ * project. After the user registration completes successfully, you can use
+ * the [/account/verfication](/docs/client/account#createVerification) route
+ * to start verifying the user email address. To allow your new user to login
+ * to his new account, you need to create a new [account
+ * session](/docs/client/account#createSession).
+ *
+ * @param String _email
+ * @param String _password
+ * @param String _name
+ * @throws Exception
+ * @return array
+ */
+
+ func create(_email: String, _password: String, _name: String = "") -> Array {
+ let path: String = "/account"
+
+
+ var params: [String: Any] = [:]
+
+ params["email"] = _email
+ params["password"] = _password
+ params["name"] = _name
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete Account
+ *
+ * Delete a currently logged in user account. Behind the scene, the user
+ * record is not deleted but permanently blocked from any access. This is done
+ * to avoid deleted accounts being overtaken by new users with the same email
+ * address. Any user-related resources like documents or storage files should
+ * be deleted separately.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func delete() -> Array {
+ let path: String = "/account"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Account Email
+ *
+ * Update currently logged in user account email address. After changing user
+ * address, user confirmation status is being reset and a new confirmation
+ * mail is sent. For security measures, user password is required to complete
+ * this request.
+ *
+ * @param String _email
+ * @param String _password
+ * @throws Exception
+ * @return array
+ */
+
+ func updateEmail(_email: String, _password: String) -> Array {
+ let path: String = "/account/email"
+
+
+ var params: [String: Any] = [:]
+
+ params["email"] = _email
+ params["password"] = _password
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Account Logs
+ *
+ * Get currently logged in user list of latest security activity logs. Each
+ * log returns user IP address, location and date and time of log.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getLogs() -> Array {
+ let path: String = "/account/logs"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Account Name
+ *
+ * Update currently logged in user account name.
+ *
+ * @param String _name
+ * @throws Exception
+ * @return array
+ */
+
+ func updateName(_name: String) -> Array {
+ let path: String = "/account/name"
+
+
+ var params: [String: Any] = [:]
+
+ params["name"] = _name
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Account Password
+ *
+ * Update currently logged in user password. For validation, user is required
+ * to pass the password twice.
+ *
+ * @param String _password
+ * @param String _oldPassword
+ * @throws Exception
+ * @return array
+ */
+
+ func updatePassword(_password: String, _oldPassword: String) -> Array {
+ let path: String = "/account/password"
+
+
+ var params: [String: Any] = [:]
+
+ params["password"] = _password
+ params["oldPassword"] = _oldPassword
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Account Preferences
+ *
+ * Get currently logged in user preferences as a key-value object.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getPrefs() -> Array {
+ let path: String = "/account/prefs"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Account Preferences
+ *
+ * Update currently logged in user account preferences. You can pass only the
+ * specific settings you wish to update.
+ *
+ * @param object _prefs
+ * @throws Exception
+ * @return array
+ */
+
+ func updatePrefs(_prefs: object) -> Array {
+ let path: String = "/account/prefs"
+
+
+ var params: [String: Any] = [:]
+
+ params["prefs"] = _prefs
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Password Recovery
+ *
+ * Sends the user an email with a temporary secret key for password reset.
+ * When the user clicks the confirmation link he is redirected back to your
+ * app password reset URL with the secret key and email address values
+ * attached to the URL query string. Use the query string params to submit a
+ * request to the [PUT /account/recovery](/docs/client/account#updateRecovery)
+ * endpoint to complete the process.
+ *
+ * @param String _email
+ * @param String _url
+ * @throws Exception
+ * @return array
+ */
+
+ func createRecovery(_email: String, _url: String) -> Array {
+ let path: String = "/account/recovery"
+
+
+ var params: [String: Any] = [:]
+
+ params["email"] = _email
+ params["url"] = _url
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Complete Password Recovery
+ *
+ * Use this endpoint to complete the user account password reset. Both the
+ * **userId** and **secret** arguments will be passed as query parameters to
+ * the redirect URL you have provided when sending your request to the [POST
+ * /account/recovery](/docs/client/account#createRecovery) endpoint.
+ *
+ * Please note that in order to avoid a [Redirect
+ * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+ * the only valid redirect URLs are the ones from domains you have set when
+ * adding your platforms in the console interface.
+ *
+ * @param String _userId
+ * @param String _secret
+ * @param String _password
+ * @param String _passwordAgain
+ * @throws Exception
+ * @return array
+ */
+
+ func updateRecovery(_userId: String, _secret: String, _password: String, _passwordAgain: String) -> Array {
+ let path: String = "/account/recovery"
+
+
+ var params: [String: Any] = [:]
+
+ params["userId"] = _userId
+ params["secret"] = _secret
+ params["password"] = _password
+ params["passwordAgain"] = _passwordAgain
+
+ return [self.client.call(method: Client.HTTPMethod.put.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Account Sessions
+ *
+ * Get currently logged in user list of active sessions across different
+ * devices.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getSessions() -> Array {
+ let path: String = "/account/sessions"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Account Session
+ *
+ * Allow the user to login into his account by providing a valid email and
+ * password combination. This route will create a new session for the user.
+ *
+ * @param String _email
+ * @param String _password
+ * @throws Exception
+ * @return array
+ */
+
+ func createSession(_email: String, _password: String) -> Array {
+ let path: String = "/account/sessions"
+
+
+ var params: [String: Any] = [:]
+
+ params["email"] = _email
+ params["password"] = _password
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete All Account Sessions
+ *
+ * Delete all sessions from the user account and remove any sessions cookies
+ * from the end client.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func deleteSessions() -> Array {
+ let path: String = "/account/sessions"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Account Session with OAuth2
+ *
+ * Allow the user to login to his account using the OAuth2 provider of his
+ * choice. Each OAuth2 provider should be enabled from the Appwrite console
+ * first. Use the success and failure arguments to provide a redirect URL's
+ * back to your app when login is completed.
+ *
+ * @param String _provider
+ * @param String _success
+ * @param String _failure
+ * @param Array _scopes
+ * @throws Exception
+ * @return array
+ */
+
+ func createOAuth2Session(_provider: String, _success: String = "https://appwrite.io/auth/oauth2/success", _failure: String = "https://appwrite.io/auth/oauth2/failure", _scopes: Array = []) -> Array {
+ var path: String = "/account/sessions/oauth2/{provider}"
+
+ path = path.replacingOccurrences(
+ of: "{provider}",
+ with: _provider
+ )
+
+ var params: [String: Any] = [:]
+
+ params["success"] = _success
+ params["failure"] = _failure
+ params["scopes"] = _scopes
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete Account Session
+ *
+ * Use this endpoint to log out the currently logged in user from all his
+ * account sessions across all his different devices. When using the option id
+ * argument, only the session unique ID provider will be deleted.
+ *
+ * @param String _sessionId
+ * @throws Exception
+ * @return array
+ */
+
+ func deleteSession(_sessionId: String) -> Array {
+ var path: String = "/account/sessions/{sessionId}"
+
+ path = path.replacingOccurrences(
+ of: "{sessionId}",
+ with: _sessionId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Email Verification
+ *
+ * Use this endpoint to send a verification message to your user email address
+ * to confirm they are the valid owners of that address. Both the **userId**
+ * and **secret** arguments will be passed as query parameters to the URL you
+ * have provided to be attached to the verification email. The provided URL
+ * should redirect the user back to your app and allow you to complete the
+ * verification process by verifying both the **userId** and **secret**
+ * parameters. Learn more about how to [complete the verification
+ * process](/docs/client/account#updateAccountVerification).
+ *
+ * Please note that in order to avoid a [Redirect
+ * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
+ * the only valid redirect URLs are the ones from domains you have set when
+ * adding your platforms in the console interface.
+ *
+ *
+ * @param String _url
+ * @throws Exception
+ * @return array
+ */
+
+ func createVerification(_url: String) -> Array {
+ let path: String = "/account/verification"
+
+
+ var params: [String: Any] = [:]
+
+ params["url"] = _url
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Complete Email Verification
+ *
+ * Use this endpoint to complete the user email verification process. Use both
+ * the **userId** and **secret** parameters that were attached to your app URL
+ * to verify the user email ownership. If confirmed this route will return a
+ * 200 status code.
+ *
+ * @param String _userId
+ * @param String _secret
+ * @throws Exception
+ * @return array
+ */
+
+ func updateVerification(_userId: String, _secret: String) -> Array {
+ let path: String = "/account/verification"
+
+
+ var params: [String: Any] = [:]
+
+ params["userId"] = _userId
+ params["secret"] = _secret
+
+ return [self.client.call(method: Client.HTTPMethod.put.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Avatars.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Avatars.swift
new file mode 100644
index 0000000000..867841bafd
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Avatars.swift
@@ -0,0 +1,233 @@
+
+
+class Avatars: Service
+{
+ /**
+ * Get Browser Icon
+ *
+ * You can use this endpoint to show different browser icons to your users.
+ * The code argument receives the browser code as it appears in your user
+ * /account/sessions endpoint. Use width, height and quality arguments to
+ * change the output settings.
+ *
+ * @param String _code
+ * @param Int _width
+ * @param Int _height
+ * @param Int _quality
+ * @throws Exception
+ * @return array
+ */
+
+ func getBrowser(_code: String, _width: Int = 100, _height: Int = 100, _quality: Int = 100) -> Array {
+ var path: String = "/avatars/browsers/{code}"
+
+ path = path.replacingOccurrences(
+ of: "{code}",
+ with: _code
+ )
+
+ var params: [String: Any] = [:]
+
+ params["width"] = _width
+ params["height"] = _height
+ params["quality"] = _quality
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Credit Card Icon
+ *
+ * Need to display your users with your billing method or their payment
+ * methods? The credit card endpoint will return you the icon of the credit
+ * card provider you need. Use width, height and quality arguments to change
+ * the output settings.
+ *
+ * @param String _code
+ * @param Int _width
+ * @param Int _height
+ * @param Int _quality
+ * @throws Exception
+ * @return array
+ */
+
+ func getCreditCard(_code: String, _width: Int = 100, _height: Int = 100, _quality: Int = 100) -> Array {
+ var path: String = "/avatars/credit-cards/{code}"
+
+ path = path.replacingOccurrences(
+ of: "{code}",
+ with: _code
+ )
+
+ var params: [String: Any] = [:]
+
+ params["width"] = _width
+ params["height"] = _height
+ params["quality"] = _quality
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Favicon
+ *
+ * Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote
+ * website URL.
+ *
+ * @param String _url
+ * @throws Exception
+ * @return array
+ */
+
+ func getFavicon(_url: String) -> Array {
+ let path: String = "/avatars/favicon"
+
+
+ var params: [String: Any] = [:]
+
+ params["url"] = _url
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Country Flag
+ *
+ * You can use this endpoint to show different country flags icons to your
+ * users. The code argument receives the 2 letter country code. Use width,
+ * height and quality arguments to change the output settings.
+ *
+ * @param String _code
+ * @param Int _width
+ * @param Int _height
+ * @param Int _quality
+ * @throws Exception
+ * @return array
+ */
+
+ func getFlag(_code: String, _width: Int = 100, _height: Int = 100, _quality: Int = 100) -> Array {
+ var path: String = "/avatars/flags/{code}"
+
+ path = path.replacingOccurrences(
+ of: "{code}",
+ with: _code
+ )
+
+ var params: [String: Any] = [:]
+
+ params["width"] = _width
+ params["height"] = _height
+ params["quality"] = _quality
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Image from URL
+ *
+ * Use this endpoint to fetch a remote image URL and crop it to any image size
+ * you want. This endpoint is very useful if you need to crop and display
+ * remote images in your app or in case you want to make sure a 3rd party
+ * image is properly served using a TLS protocol.
+ *
+ * @param String _url
+ * @param Int _width
+ * @param Int _height
+ * @throws Exception
+ * @return array
+ */
+
+ func getImage(_url: String, _width: Int = 400, _height: Int = 400) -> Array {
+ let path: String = "/avatars/image"
+
+
+ var params: [String: Any] = [:]
+
+ params["url"] = _url
+ params["width"] = _width
+ params["height"] = _height
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get User Initials
+ *
+ * Use this endpoint to show your user initials avatar icon on your website or
+ * app. By default, this route will try to print your logged-in user name or
+ * email initials. You can also overwrite the user name if you pass the 'name'
+ * parameter. If no name is given and no user is logged, an empty avatar will
+ * be returned.
+ *
+ * You can use the color and background params to change the avatar colors. By
+ * default, a random theme will be selected. The random theme will persist for
+ * the user's initials when reloading the same theme will always return for
+ * the same initials.
+ *
+ * @param String _name
+ * @param Int _width
+ * @param Int _height
+ * @param String _color
+ * @param String _background
+ * @throws Exception
+ * @return array
+ */
+
+ func getInitials(_name: String = "", _width: Int = 500, _height: Int = 500, _color: String = "", _background: String = "") -> Array {
+ let path: String = "/avatars/initials"
+
+
+ var params: [String: Any] = [:]
+
+ params["name"] = _name
+ params["width"] = _width
+ params["height"] = _height
+ params["color"] = _color
+ params["background"] = _background
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get QR Code
+ *
+ * Converts a given plain text to a QR code image. You can use the query
+ * parameters to change the size and style of the resulting image.
+ *
+ * @param String _text
+ * @param Int _size
+ * @param Int _margin
+ * @param Bool _download
+ * @throws Exception
+ * @return array
+ */
+
+ func getQR(_text: String, _size: Int = 400, _margin: Int = 1, _download: Bool = false) -> Array {
+ let path: String = "/avatars/qr"
+
+
+ var params: [String: Any] = [:]
+
+ params["text"] = _text
+ params["size"] = _size
+ params["margin"] = _margin
+ params["download"] = _download
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Database.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Database.swift
new file mode 100644
index 0000000000..8436985582
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Database.swift
@@ -0,0 +1,183 @@
+
+
+class Database: Service
+{
+ /**
+ * List Documents
+ *
+ * Get a list of all the user documents. You can use the query params to
+ * filter your results. On admin mode, this endpoint will return a list of all
+ * of the project documents. [Learn more about different API
+ * modes](/docs/admin).
+ *
+ * @param String _collectionId
+ * @param Array _filters
+ * @param Int _limit
+ * @param Int _offset
+ * @param String _orderField
+ * @param String _orderType
+ * @param String _orderCast
+ * @param String _search
+ * @throws Exception
+ * @return array
+ */
+
+ func listDocuments(_collectionId: String, _filters: Array = [], _limit: Int = 25, _offset: Int = 0, _orderField: String = "$id", _orderType: String = "ASC", _orderCast: String = "string", _search: String = "") -> Array {
+ var path: String = "/database/collections/{collectionId}/documents"
+
+ path = path.replacingOccurrences(
+ of: "{collectionId}",
+ with: _collectionId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["filters"] = _filters
+ params["limit"] = _limit
+ params["offset"] = _offset
+ params["orderField"] = _orderField
+ params["orderType"] = _orderType
+ params["orderCast"] = _orderCast
+ params["search"] = _search
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Document
+ *
+ * Create a new Document. Before using this route, you should create a new
+ * collection resource using either a [server
+ * integration](/docs/server/database?sdk=nodejs#createCollection) API or
+ * directly from your database console.
+ *
+ * @param String _collectionId
+ * @param object _data
+ * @param Array _read
+ * @param Array _write
+ * @throws Exception
+ * @return array
+ */
+
+ func createDocument(_collectionId: String, _data: object, _read: Array, _write: Array) -> Array {
+ var path: String = "/database/collections/{collectionId}/documents"
+
+ path = path.replacingOccurrences(
+ of: "{collectionId}",
+ with: _collectionId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["data"] = _data
+ params["read"] = _read
+ params["write"] = _write
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Document
+ *
+ * Get document by its unique ID. This endpoint response returns a JSON object
+ * with the document data.
+ *
+ * @param String _collectionId
+ * @param String _documentId
+ * @throws Exception
+ * @return array
+ */
+
+ func getDocument(_collectionId: String, _documentId: String) -> Array {
+ var path: String = "/database/collections/{collectionId}/documents/{documentId}"
+
+ path = path.replacingOccurrences(
+ of: "{collectionId}",
+ with: _collectionId
+ )
+ path = path.replacingOccurrences(
+ of: "{documentId}",
+ with: _documentId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Document
+ *
+ * @param String _collectionId
+ * @param String _documentId
+ * @param object _data
+ * @param Array _read
+ * @param Array _write
+ * @throws Exception
+ * @return array
+ */
+
+ func updateDocument(_collectionId: String, _documentId: String, _data: object, _read: Array, _write: Array) -> Array {
+ var path: String = "/database/collections/{collectionId}/documents/{documentId}"
+
+ path = path.replacingOccurrences(
+ of: "{collectionId}",
+ with: _collectionId
+ )
+ path = path.replacingOccurrences(
+ of: "{documentId}",
+ with: _documentId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["data"] = _data
+ params["read"] = _read
+ params["write"] = _write
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete Document
+ *
+ * Delete document by its unique ID. This endpoint deletes only the parent
+ * documents, his attributes and relations to other documents. Child documents
+ * **will not** be deleted.
+ *
+ * @param String _collectionId
+ * @param String _documentId
+ * @throws Exception
+ * @return array
+ */
+
+ func deleteDocument(_collectionId: String, _documentId: String) -> Array {
+ var path: String = "/database/collections/{collectionId}/documents/{documentId}"
+
+ path = path.replacingOccurrences(
+ of: "{collectionId}",
+ with: _collectionId
+ )
+ path = path.replacingOccurrences(
+ of: "{documentId}",
+ with: _documentId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Locale.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Locale.swift
new file mode 100644
index 0000000000..ef21554cbc
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Locale.swift
@@ -0,0 +1,164 @@
+
+
+class Locale: Service
+{
+ /**
+ * Get User Locale
+ *
+ * Get the current user location based on IP. Returns an object with user
+ * country code, country name, continent name, continent code, ip address and
+ * suggested currency. You can use the locale header to get the data in a
+ * supported language.
+ *
+ * ([IP Geolocation by DB-IP](https://db-ip.com))
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func get() -> Array {
+ let path: String = "/locale"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List Continents
+ *
+ * List of all continents. You can use the locale header to get the data in a
+ * supported language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getContinents() -> Array {
+ let path: String = "/locale/continents"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List Countries
+ *
+ * List of all countries. You can use the locale header to get the data in a
+ * supported language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getCountries() -> Array {
+ let path: String = "/locale/countries"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List EU Countries
+ *
+ * List of all countries that are currently members of the EU. You can use the
+ * locale header to get the data in a supported language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getCountriesEU() -> Array {
+ let path: String = "/locale/countries/eu"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List Countries Phone Codes
+ *
+ * List of all countries phone codes. You can use the locale header to get the
+ * data in a supported language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getCountriesPhones() -> Array {
+ let path: String = "/locale/countries/phones"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List Currencies
+ *
+ * List of all currencies, including currency symbol, name, plural, and
+ * decimal digits for all major and minor currencies. You can use the locale
+ * header to get the data in a supported language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getCurrencies() -> Array {
+ let path: String = "/locale/currencies"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * List Languages
+ *
+ * List of all languages classified by ISO 639-1 including 2-letter code, name
+ * in English, and name in the respective language.
+ *
+ * @throws Exception
+ * @return array
+ */
+
+ func getLanguages() -> Array {
+ let path: String = "/locale/languages"
+
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Storage.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Storage.swift
new file mode 100644
index 0000000000..b6e98145f7
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Storage.swift
@@ -0,0 +1,246 @@
+
+
+class Storage: Service
+{
+ /**
+ * List Files
+ *
+ * Get a list of all the user files. You can use the query params to filter
+ * your results. On admin mode, this endpoint will return a list of all of the
+ * project files. [Learn more about different API modes](/docs/admin).
+ *
+ * @param String _search
+ * @param Int _limit
+ * @param Int _offset
+ * @param String _orderType
+ * @throws Exception
+ * @return array
+ */
+
+ func listFiles(_search: String = "", _limit: Int = 25, _offset: Int = 0, _orderType: String = "ASC") -> Array {
+ let path: String = "/storage/files"
+
+
+ var params: [String: Any] = [:]
+
+ params["search"] = _search
+ params["limit"] = _limit
+ params["offset"] = _offset
+ params["orderType"] = _orderType
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create File
+ *
+ * Create a new file. The user who creates the file will automatically be
+ * assigned to read and write access unless he has passed custom values for
+ * read and write arguments.
+ *
+ * @param Array _file
+ * @param Array _read
+ * @param Array _write
+ * @throws Exception
+ * @return array
+ */
+
+ func createFile(_file: Array, _read: Array, _write: Array) -> Array {
+ let path: String = "/storage/files"
+
+
+ var params: [String: Any] = [:]
+
+ params["file"] = _file
+ params["read"] = _read
+ params["write"] = _write
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "multipart/form-data",
+ ], params: params)];
+ }
+
+ /**
+ * Get File
+ *
+ * Get file by its unique ID. This endpoint response returns a JSON object
+ * with the file metadata.
+ *
+ * @param String _fileId
+ * @throws Exception
+ * @return array
+ */
+
+ func getFile(_fileId: String) -> Array {
+ var path: String = "/storage/files/{fileId}"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update File
+ *
+ * Update file by its unique ID. Only users with write permissions have access
+ * to update this resource.
+ *
+ * @param String _fileId
+ * @param Array _read
+ * @param Array _write
+ * @throws Exception
+ * @return array
+ */
+
+ func updateFile(_fileId: String, _read: Array, _write: Array) -> Array {
+ var path: String = "/storage/files/{fileId}"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["read"] = _read
+ params["write"] = _write
+
+ return [self.client.call(method: Client.HTTPMethod.put.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete File
+ *
+ * Delete a file by its unique ID. Only users with write permissions have
+ * access to delete this resource.
+ *
+ * @param String _fileId
+ * @throws Exception
+ * @return array
+ */
+
+ func deleteFile(_fileId: String) -> Array {
+ var path: String = "/storage/files/{fileId}"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get File for Download
+ *
+ * Get file content by its unique ID. The endpoint response return with a
+ * 'Content-Disposition: attachment' header that tells the browser to start
+ * downloading the file to user downloads directory.
+ *
+ * @param String _fileId
+ * @throws Exception
+ * @return array
+ */
+
+ func getFileDownload(_fileId: String) -> Array {
+ var path: String = "/storage/files/{fileId}/download"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get File Preview
+ *
+ * Get a file preview image. Currently, this method supports preview for image
+ * files (jpg, png, and gif), other supported formats, like pdf, docs, slides,
+ * and spreadsheets, will return the file icon image. You can also pass query
+ * string arguments for cutting and resizing your preview image.
+ *
+ * @param String _fileId
+ * @param Int _width
+ * @param Int _height
+ * @param Int _quality
+ * @param String _background
+ * @param String _output
+ * @throws Exception
+ * @return array
+ */
+
+ func getFilePreview(_fileId: String, _width: Int = 0, _height: Int = 0, _quality: Int = 100, _background: String = "", _output: String = "") -> Array {
+ var path: String = "/storage/files/{fileId}/preview"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["width"] = _width
+ params["height"] = _height
+ params["quality"] = _quality
+ params["background"] = _background
+ params["output"] = _output
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get File for View
+ *
+ * Get file content by its unique ID. This endpoint is similar to the download
+ * method but returns with no 'Content-Disposition: attachment' header.
+ *
+ * @param String _fileId
+ * @param String _as
+ * @throws Exception
+ * @return array
+ */
+
+ func getFileView(_fileId: String, _as: String = "") -> Array {
+ var path: String = "/storage/files/{fileId}/view"
+
+ path = path.replacingOccurrences(
+ of: "{fileId}",
+ with: _fileId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["as"] = _as
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/Sources/Appwrite/Services/Teams.swift b/app/sdks/client-swift/Sources/Appwrite/Services/Teams.swift
new file mode 100644
index 0000000000..50bee60273
--- /dev/null
+++ b/app/sdks/client-swift/Sources/Appwrite/Services/Teams.swift
@@ -0,0 +1,298 @@
+
+
+class Teams: Service
+{
+ /**
+ * List Teams
+ *
+ * Get a list of all the current user teams. You can use the query params to
+ * filter your results. On admin mode, this endpoint will return a list of all
+ * of the project teams. [Learn more about different API modes](/docs/admin).
+ *
+ * @param String _search
+ * @param Int _limit
+ * @param Int _offset
+ * @param String _orderType
+ * @throws Exception
+ * @return array
+ */
+
+ func list(_search: String = "", _limit: Int = 25, _offset: Int = 0, _orderType: String = "ASC") -> Array {
+ let path: String = "/teams"
+
+
+ var params: [String: Any] = [:]
+
+ params["search"] = _search
+ params["limit"] = _limit
+ params["offset"] = _offset
+ params["orderType"] = _orderType
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Team
+ *
+ * Create a new team. The user who creates the team will automatically be
+ * assigned as the owner of the team. The team owner can invite new members,
+ * who will be able add new owners and update or delete the team from your
+ * project.
+ *
+ * @param String _name
+ * @param Array _roles
+ * @throws Exception
+ * @return array
+ */
+
+ func create(_name: String, _roles: Array = ["owner"]) -> Array {
+ let path: String = "/teams"
+
+
+ var params: [String: Any] = [:]
+
+ params["name"] = _name
+ params["roles"] = _roles
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Team
+ *
+ * Get team by its unique ID. All team members have read access for this
+ * resource.
+ *
+ * @param String _teamId
+ * @throws Exception
+ * @return array
+ */
+
+ func get(_teamId: String) -> Array {
+ var path: String = "/teams/{teamId}"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Team
+ *
+ * Update team by its unique ID. Only team owners have write access for this
+ * resource.
+ *
+ * @param String _teamId
+ * @param String _name
+ * @throws Exception
+ * @return array
+ */
+
+ func update(_teamId: String, _name: String) -> Array {
+ var path: String = "/teams/{teamId}"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["name"] = _name
+
+ return [self.client.call(method: Client.HTTPMethod.put.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete Team
+ *
+ * Delete team by its unique ID. Only team owners have write access for this
+ * resource.
+ *
+ * @param String _teamId
+ * @throws Exception
+ * @return array
+ */
+
+ func delete(_teamId: String) -> Array {
+ var path: String = "/teams/{teamId}"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Get Team Memberships
+ *
+ * Get team members by the team unique ID. All team members have read access
+ * for this list of resources.
+ *
+ * @param String _teamId
+ * @param String _search
+ * @param Int _limit
+ * @param Int _offset
+ * @param String _orderType
+ * @throws Exception
+ * @return array
+ */
+
+ func getMemberships(_teamId: String, _search: String = "", _limit: Int = 25, _offset: Int = 0, _orderType: String = "ASC") -> Array {
+ var path: String = "/teams/{teamId}/memberships"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["search"] = _search
+ params["limit"] = _limit
+ params["offset"] = _offset
+ params["orderType"] = _orderType
+
+ return [self.client.call(method: Client.HTTPMethod.get.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Create Team Membership
+ *
+ * Use this endpoint to invite a new member to join your team. An email with a
+ * link to join the team will be sent to the new member email address if the
+ * member doesn't exist in the project it will be created automatically.
+ *
+ * Use the 'URL' parameter to redirect the user from the invitation email back
+ * to your app. When the user is redirected, use the [Update Team Membership
+ * Status](/docs/client/teams#updateMembershipStatus) endpoint to allow the
+ * user to accept the invitation to the team.
+ *
+ * Please note that in order to avoid a [Redirect
+ * Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+ * the only valid redirect URL's are the once from domains you have set when
+ * added your platforms in the console interface.
+ *
+ * @param String _teamId
+ * @param String _email
+ * @param Array _roles
+ * @param String _url
+ * @param String _name
+ * @throws Exception
+ * @return array
+ */
+
+ func createMembership(_teamId: String, _email: String, _roles: Array, _url: String, _name: String = "") -> Array {
+ var path: String = "/teams/{teamId}/memberships"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["email"] = _email
+ params["name"] = _name
+ params["roles"] = _roles
+ params["url"] = _url
+
+ return [self.client.call(method: Client.HTTPMethod.post.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Delete Team Membership
+ *
+ * This endpoint allows a user to leave a team or for a team owner to delete
+ * the membership of any other team member. You can also use this endpoint to
+ * delete a user membership even if he didn't accept it.
+ *
+ * @param String _teamId
+ * @param String _inviteId
+ * @throws Exception
+ * @return array
+ */
+
+ func deleteMembership(_teamId: String, _inviteId: String) -> Array {
+ var path: String = "/teams/{teamId}/memberships/{inviteId}"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+ path = path.replacingOccurrences(
+ of: "{inviteId}",
+ with: _inviteId
+ )
+
+ let params: [String: Any] = [:]
+
+
+ return [self.client.call(method: Client.HTTPMethod.delete.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+ /**
+ * Update Team Membership Status
+ *
+ * Use this endpoint to allow a user to accept an invitation to join a team
+ * after he is being redirected back to your app from the invitation email he
+ * was sent.
+ *
+ * @param String _teamId
+ * @param String _inviteId
+ * @param String _userId
+ * @param String _secret
+ * @throws Exception
+ * @return array
+ */
+
+ func updateMembershipStatus(_teamId: String, _inviteId: String, _userId: String, _secret: String) -> Array {
+ var path: String = "/teams/{teamId}/memberships/{inviteId}/status"
+
+ path = path.replacingOccurrences(
+ of: "{teamId}",
+ with: _teamId
+ )
+ path = path.replacingOccurrences(
+ of: "{inviteId}",
+ with: _inviteId
+ )
+
+ var params: [String: Any] = [:]
+
+ params["userId"] = _userId
+ params["secret"] = _secret
+
+ return [self.client.call(method: Client.HTTPMethod.patch.rawValue, path: path, headers: [
+ "content-type": "application/json",
+ ], params: params)];
+ }
+
+}
diff --git a/app/sdks/client-swift/docs/account.md b/app/sdks/client-swift/docs/account.md
new file mode 100644
index 0000000000..48bc91d213
--- /dev/null
+++ b/app/sdks/client-swift/docs/account.md
@@ -0,0 +1,240 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Account Service
+
+## Get Account
+
+```http request
+GET https://appwrite.io/v1/account
+```
+
+** Get currently logged in user data as JSON object. **
+
+## Create Account
+
+```http request
+POST https://appwrite.io/v1/account
+```
+
+** Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [/account/verfication](/docs/client/account#createVerification) route to start verifying the user email address. To allow your new user to login to his new account, you need to create a new [account session](/docs/client/account#createSession). **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| email | string | User email. | |
+| password | string | User password. Must be between 6 to 32 chars. | |
+| name | string | User name. | |
+
+## Delete Account
+
+```http request
+DELETE https://appwrite.io/v1/account
+```
+
+** Delete a currently logged in user account. Behind the scene, the user record is not deleted but permanently blocked from any access. This is done to avoid deleted accounts being overtaken by new users with the same email address. Any user-related resources like documents or storage files should be deleted separately. **
+
+## Update Account Email
+
+```http request
+PATCH https://appwrite.io/v1/account/email
+```
+
+** Update currently logged in user account email address. After changing user address, user confirmation status is being reset and a new confirmation mail is sent. For security measures, user password is required to complete this request. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| email | string | User email. | |
+| password | string | User password. Must be between 6 to 32 chars. | |
+
+## Get Account Logs
+
+```http request
+GET https://appwrite.io/v1/account/logs
+```
+
+** Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log. **
+
+## Update Account Name
+
+```http request
+PATCH https://appwrite.io/v1/account/name
+```
+
+** Update currently logged in user account name. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| name | string | User name. | |
+
+## Update Account Password
+
+```http request
+PATCH https://appwrite.io/v1/account/password
+```
+
+** Update currently logged in user password. For validation, user is required to pass the password twice. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| password | string | New user password. Must be between 6 to 32 chars. | |
+| oldPassword | string | Old user password. Must be between 6 to 32 chars. | |
+
+## Get Account Preferences
+
+```http request
+GET https://appwrite.io/v1/account/prefs
+```
+
+** Get currently logged in user preferences as a key-value object. **
+
+## Update Account Preferences
+
+```http request
+PATCH https://appwrite.io/v1/account/prefs
+```
+
+** Update currently logged in user account preferences. You can pass only the specific settings you wish to update. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| prefs | object | Prefs key-value JSON object. | |
+
+## Create Password Recovery
+
+```http request
+POST https://appwrite.io/v1/account/recovery
+```
+
+** Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT /account/recovery](/docs/client/account#updateRecovery) endpoint to complete the process. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| email | string | User email. | |
+| url | string | 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. | |
+
+## Complete Password Recovery
+
+```http request
+PUT https://appwrite.io/v1/account/recovery
+```
+
+** Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/recovery](/docs/client/account#createRecovery) endpoint.
+
+Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| userId | string | User account UID address. | |
+| secret | string | Valid reset token. | |
+| password | string | New password. Must be between 6 to 32 chars. | |
+| passwordAgain | string | New password again. Must be between 6 to 32 chars. | |
+
+## Get Account Sessions
+
+```http request
+GET https://appwrite.io/v1/account/sessions
+```
+
+** Get currently logged in user list of active sessions across different devices. **
+
+## Create Account Session
+
+```http request
+POST https://appwrite.io/v1/account/sessions
+```
+
+** Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| email | string | User email. | |
+| password | string | User password. Must be between 6 to 32 chars. | |
+
+## Delete All Account Sessions
+
+```http request
+DELETE https://appwrite.io/v1/account/sessions
+```
+
+** Delete all sessions from the user account and remove any sessions cookies from the end client. **
+
+## Create Account Session with OAuth2
+
+```http request
+GET https://appwrite.io/v1/account/sessions/oauth2/{provider}
+```
+
+** Allow the user to login to his account using the OAuth2 provider of his choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| provider | string | **Required** OAuth2 Provider. Currently, supported providers are: amazon, apple, bitbucket, bitly, box, discord, dropbox, facebook, github, gitlab, google, linkedin, microsoft, paypal, paypalSandbox, salesforce, slack, spotify, twitch, vk, yahoo, yandex. | |
+| success | string | URL to redirect back to your app after a successful login attempt. 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. | https://appwrite.io/auth/oauth2/success |
+| failure | string | URL to redirect back to your app after a failed login attempt. 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. | https://appwrite.io/auth/oauth2/failure |
+| scopes | array | A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. | [] |
+
+## Delete Account Session
+
+```http request
+DELETE https://appwrite.io/v1/account/sessions/{sessionId}
+```
+
+** Use this endpoint to log out the currently logged in user from all his account sessions across all his different devices. When using the option id argument, only the session unique ID provider will be deleted. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| sessionId | string | **Required** Session unique ID. Use the string 'current' to delete the current device session. | |
+
+## Create Email Verification
+
+```http request
+POST https://appwrite.io/v1/account/verification
+```
+
+** Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](/docs/client/account#updateAccountVerification).
+
+Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.
+ **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| url | string | URL to redirect the user back to your app from the verification 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. | |
+
+## Complete Email Verification
+
+```http request
+PUT https://appwrite.io/v1/account/verification
+```
+
+** Use this endpoint to complete the user email verification process. Use both the **userId** and **secret** parameters that were attached to your app URL to verify the user email ownership. If confirmed this route will return a 200 status code. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| userId | string | User unique ID. | |
+| secret | string | Valid verification token. | |
+
diff --git a/app/sdks/client-swift/docs/avatars.md b/app/sdks/client-swift/docs/avatars.md
new file mode 100644
index 0000000000..1e17a8e14a
--- /dev/null
+++ b/app/sdks/client-swift/docs/avatars.md
@@ -0,0 +1,124 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Avatars Service
+
+## Get Browser Icon
+
+```http request
+GET https://appwrite.io/v1/avatars/browsers/{code}
+```
+
+** You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user /account/sessions endpoint. Use width, height and quality arguments to change the output settings. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| code | string | **Required** Browser Code. | |
+| width | integer | Image width. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| height | integer | Image height. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| quality | integer | Image quality. Pass an integer between 0 to 100. Defaults to 100. | 100 |
+
+## Get Credit Card Icon
+
+```http request
+GET https://appwrite.io/v1/avatars/credit-cards/{code}
+```
+
+** Need to display your users with your billing method or their payment methods? The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| code | string | **Required** Credit Card Code. Possible values: amex, argencard, cabal, censosud, diners, discover, elo, hipercard, jcb, mastercard, naranja, targeta-shopping, union-china-pay, visa. | |
+| width | integer | Image width. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| height | integer | Image height. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| quality | integer | Image quality. Pass an integer between 0 to 100. Defaults to 100. | 100 |
+
+## Get Favicon
+
+```http request
+GET https://appwrite.io/v1/avatars/favicon
+```
+
+** Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| url | string | **Required** Website URL which you want to fetch the favicon from. | |
+
+## Get Country Flag
+
+```http request
+GET https://appwrite.io/v1/avatars/flags/{code}
+```
+
+** You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| code | string | **Required** Country Code. ISO Alpha-2 country code format. | |
+| width | integer | Image width. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| height | integer | Image height. Pass an integer between 0 to 2000. Defaults to 100. | 100 |
+| quality | integer | Image quality. Pass an integer between 0 to 100. Defaults to 100. | 100 |
+
+## Get Image from URL
+
+```http request
+GET https://appwrite.io/v1/avatars/image
+```
+
+** Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| url | string | **Required** Image URL which you want to crop. | |
+| width | integer | Resize preview image width, Pass an integer between 0 to 2000. | 400 |
+| height | integer | Resize preview image height, Pass an integer between 0 to 2000. | 400 |
+
+## Get User Initials
+
+```http request
+GET https://appwrite.io/v1/avatars/initials
+```
+
+** Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.
+
+You can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| name | string | Full Name. When empty, current user name or email will be used. | |
+| width | integer | Image width. Pass an integer between 0 to 2000. Defaults to 100. | 500 |
+| height | integer | Image height. Pass an integer between 0 to 2000. Defaults to 100. | 500 |
+| color | string | Changes text color. By default a random color will be picked and stay will persistent to the given name. | |
+| background | string | Changes background color. By default a random color will be picked and stay will persistent to the given name. | |
+
+## Get QR Code
+
+```http request
+GET https://appwrite.io/v1/avatars/qr
+```
+
+** Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| text | string | **Required** Plain text to be converted to QR code image. | |
+| size | integer | QR code size. Pass an integer between 0 to 1000. Defaults to 400. | 400 |
+| margin | integer | Margin from edge. Pass an integer between 0 to 10. Defaults to 1. | 1 |
+| download | boolean | Return resulting image with 'Content-Disposition: attachment ' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0. | |
+
diff --git a/app/sdks/client-swift/docs/database.md b/app/sdks/client-swift/docs/database.md
new file mode 100644
index 0000000000..d734cb3c1c
--- /dev/null
+++ b/app/sdks/client-swift/docs/database.md
@@ -0,0 +1,90 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Database Service
+
+## List Documents
+
+```http request
+GET https://appwrite.io/v1/database/collections/{collectionId}/documents
+```
+
+** Get a list of all the user documents. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project documents. [Learn more about different API modes](/docs/admin). **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| collectionId | string | **Required** Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection). | |
+| filters | array | Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: 'name=John Doe' or 'category.$id>=5bed2d152c362'. | [] |
+| limit | integer | Maximum number of documents to return in response. Use this value to manage pagination. | 25 |
+| offset | integer | Offset value. Use this value to manage pagination. | 0 |
+| orderField | string | Document field that results will be sorted by. | $id |
+| orderType | string | Order direction. Possible values are DESC for descending order, or ASC for ascending order. | ASC |
+| orderCast | string | Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string. | string |
+| search | string | Search query. Enter any free text search. The database will try to find a match against all document attributes and children. | |
+
+## Create Document
+
+```http request
+POST https://appwrite.io/v1/database/collections/{collectionId}/documents
+```
+
+** Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/database?sdk=nodejs#createCollection) API or directly from your database console. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| collectionId | string | **Required** Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection). | |
+| data | object | Document data as JSON object. | |
+| read | array | An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+| write | array | An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+
+## Get Document
+
+```http request
+GET https://appwrite.io/v1/database/collections/{collectionId}/documents/{documentId}
+```
+
+** Get document by its unique ID. This endpoint response returns a JSON object with the document data. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| collectionId | string | **Required** Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection). | |
+| documentId | string | **Required** Document unique ID. | |
+
+## Update Document
+
+```http request
+PATCH https://appwrite.io/v1/database/collections/{collectionId}/documents/{documentId}
+```
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| collectionId | string | **Required** Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection). | |
+| documentId | string | **Required** Document unique ID. | |
+| data | object | Document data as JSON object. | |
+| read | array | An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+| write | array | An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+
+## Delete Document
+
+```http request
+DELETE https://appwrite.io/v1/database/collections/{collectionId}/documents/{documentId}
+```
+
+** Delete document by its unique ID. This endpoint deletes only the parent documents, his attributes and relations to other documents. Child documents **will not** be deleted. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| collectionId | string | **Required** Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection). | |
+| documentId | string | **Required** Document unique ID. | |
+
diff --git a/app/sdks/client-swift/docs/examples/account/create-o-auth2session.md b/app/sdks/client-swift/docs/examples/account/create-o-auth2session.md
new file mode 100644
index 0000000000..0b67257890
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/create-o-auth2session.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.createOAuth2Session(_provider: "amazon");
diff --git a/app/sdks/client-swift/docs/examples/account/create-recovery.md b/app/sdks/client-swift/docs/examples/account/create-recovery.md
new file mode 100644
index 0000000000..04fdcae541
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/create-recovery.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.createRecovery(_email: "email@example.com", _url: "https://example.com");
diff --git a/app/sdks/client-swift/docs/examples/account/create-session.md b/app/sdks/client-swift/docs/examples/account/create-session.md
new file mode 100644
index 0000000000..477b20199c
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/create-session.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.createSession(_email: "email@example.com", _password: "password");
diff --git a/app/sdks/client-swift/docs/examples/account/create-verification.md b/app/sdks/client-swift/docs/examples/account/create-verification.md
new file mode 100644
index 0000000000..b0f8d6444e
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/create-verification.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.createVerification(_url: "https://example.com");
diff --git a/app/sdks/client-swift/docs/examples/account/create.md b/app/sdks/client-swift/docs/examples/account/create.md
new file mode 100644
index 0000000000..176b192c5e
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/create.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.create(_email: "email@example.com", _password: "password");
diff --git a/app/sdks/client-swift/docs/examples/account/delete-session.md b/app/sdks/client-swift/docs/examples/account/delete-session.md
new file mode 100644
index 0000000000..6f2373dc79
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/delete-session.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.deleteSession(_sessionId: "[SESSION_ID]");
diff --git a/app/sdks/client-swift/docs/examples/account/delete-sessions.md b/app/sdks/client-swift/docs/examples/account/delete-sessions.md
new file mode 100644
index 0000000000..6df71814c6
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/delete-sessions.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.deleteSessions();
diff --git a/app/sdks/client-swift/docs/examples/account/delete.md b/app/sdks/client-swift/docs/examples/account/delete.md
new file mode 100644
index 0000000000..f4123b0197
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/delete.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.delete();
diff --git a/app/sdks/client-swift/docs/examples/account/get-logs.md b/app/sdks/client-swift/docs/examples/account/get-logs.md
new file mode 100644
index 0000000000..8d3d965723
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/get-logs.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.getLogs();
diff --git a/app/sdks/client-swift/docs/examples/account/get-prefs.md b/app/sdks/client-swift/docs/examples/account/get-prefs.md
new file mode 100644
index 0000000000..e9746ab34b
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/get-prefs.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.getPrefs();
diff --git a/app/sdks/client-swift/docs/examples/account/get-sessions.md b/app/sdks/client-swift/docs/examples/account/get-sessions.md
new file mode 100644
index 0000000000..988e91c8e5
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/get-sessions.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.getSessions();
diff --git a/app/sdks/client-swift/docs/examples/account/get.md b/app/sdks/client-swift/docs/examples/account/get.md
new file mode 100644
index 0000000000..b84096bdb8
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/get.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.get();
diff --git a/app/sdks/client-swift/docs/examples/account/update-email.md b/app/sdks/client-swift/docs/examples/account/update-email.md
new file mode 100644
index 0000000000..cec700b330
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-email.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updateEmail(_email: "email@example.com", _password: "password");
diff --git a/app/sdks/client-swift/docs/examples/account/update-name.md b/app/sdks/client-swift/docs/examples/account/update-name.md
new file mode 100644
index 0000000000..dbc6f7d2b8
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-name.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updateName(_name: "[NAME]");
diff --git a/app/sdks/client-swift/docs/examples/account/update-password.md b/app/sdks/client-swift/docs/examples/account/update-password.md
new file mode 100644
index 0000000000..1346102a32
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-password.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updatePassword(_password: "password", _oldPassword: "password");
diff --git a/app/sdks/client-swift/docs/examples/account/update-prefs.md b/app/sdks/client-swift/docs/examples/account/update-prefs.md
new file mode 100644
index 0000000000..fea1a8d6ff
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-prefs.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updatePrefs(_prefs: );
diff --git a/app/sdks/client-swift/docs/examples/account/update-recovery.md b/app/sdks/client-swift/docs/examples/account/update-recovery.md
new file mode 100644
index 0000000000..6009d62e2c
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-recovery.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updateRecovery(_userId: "[USER_ID]", _secret: "[SECRET]", _password: "password", _passwordAgain: "password");
diff --git a/app/sdks/client-swift/docs/examples/account/update-verification.md b/app/sdks/client-swift/docs/examples/account/update-verification.md
new file mode 100644
index 0000000000..376a87a9c3
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/account/update-verification.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var account: Account = Account(client: client);
+
+var result = account.updateVerification(_userId: "[USER_ID]", _secret: "[SECRET]");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-browser.md b/app/sdks/client-swift/docs/examples/avatars/get-browser.md
new file mode 100644
index 0000000000..874ef96d31
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-browser.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getBrowser(_code: "aa");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-credit-card.md b/app/sdks/client-swift/docs/examples/avatars/get-credit-card.md
new file mode 100644
index 0000000000..797431f3dc
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-credit-card.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getCreditCard(_code: "amex");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-favicon.md b/app/sdks/client-swift/docs/examples/avatars/get-favicon.md
new file mode 100644
index 0000000000..5623468481
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-favicon.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getFavicon(_url: "https://example.com");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-flag.md b/app/sdks/client-swift/docs/examples/avatars/get-flag.md
new file mode 100644
index 0000000000..fba93ae4d4
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-flag.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getFlag(_code: "af");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-image.md b/app/sdks/client-swift/docs/examples/avatars/get-image.md
new file mode 100644
index 0000000000..4ebe561b3b
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-image.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getImage(_url: "https://example.com");
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-initials.md b/app/sdks/client-swift/docs/examples/avatars/get-initials.md
new file mode 100644
index 0000000000..d317cfc5a8
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-initials.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getInitials();
diff --git a/app/sdks/client-swift/docs/examples/avatars/get-q-r.md b/app/sdks/client-swift/docs/examples/avatars/get-q-r.md
new file mode 100644
index 0000000000..737acfc258
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/avatars/get-q-r.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var avatars: Avatars = Avatars(client: client);
+
+var result = avatars.getQR(_text: "[TEXT]");
diff --git a/app/sdks/client-swift/docs/examples/database/create-document.md b/app/sdks/client-swift/docs/examples/database/create-document.md
new file mode 100644
index 0000000000..ebd59adc47
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/database/create-document.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var database: Database = Database(client: client);
+
+var result = database.createDocument(_collectionId: "[COLLECTION_ID]", _data: , _read: [], _write: []);
diff --git a/app/sdks/client-swift/docs/examples/database/delete-document.md b/app/sdks/client-swift/docs/examples/database/delete-document.md
new file mode 100644
index 0000000000..faec2ceee9
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/database/delete-document.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var database: Database = Database(client: client);
+
+var result = database.deleteDocument(_collectionId: "[COLLECTION_ID]", _documentId: "[DOCUMENT_ID]");
diff --git a/app/sdks/client-swift/docs/examples/database/get-document.md b/app/sdks/client-swift/docs/examples/database/get-document.md
new file mode 100644
index 0000000000..0f3a05bfa5
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/database/get-document.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var database: Database = Database(client: client);
+
+var result = database.getDocument(_collectionId: "[COLLECTION_ID]", _documentId: "[DOCUMENT_ID]");
diff --git a/app/sdks/client-swift/docs/examples/database/list-documents.md b/app/sdks/client-swift/docs/examples/database/list-documents.md
new file mode 100644
index 0000000000..2c9f4a2efa
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/database/list-documents.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var database: Database = Database(client: client);
+
+var result = database.listDocuments(_collectionId: "[COLLECTION_ID]");
diff --git a/app/sdks/client-swift/docs/examples/database/update-document.md b/app/sdks/client-swift/docs/examples/database/update-document.md
new file mode 100644
index 0000000000..bddaf61111
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/database/update-document.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var database: Database = Database(client: client);
+
+var result = database.updateDocument(_collectionId: "[COLLECTION_ID]", _documentId: "[DOCUMENT_ID]", _data: , _read: [], _write: []);
diff --git a/app/sdks/client-swift/docs/examples/locale/get-continents.md b/app/sdks/client-swift/docs/examples/locale/get-continents.md
new file mode 100644
index 0000000000..3f57d5eb0d
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-continents.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getContinents();
diff --git a/app/sdks/client-swift/docs/examples/locale/get-countries-e-u.md b/app/sdks/client-swift/docs/examples/locale/get-countries-e-u.md
new file mode 100644
index 0000000000..9f2a26213c
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-countries-e-u.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getCountriesEU();
diff --git a/app/sdks/client-swift/docs/examples/locale/get-countries-phones.md b/app/sdks/client-swift/docs/examples/locale/get-countries-phones.md
new file mode 100644
index 0000000000..2cc9c4a9a0
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-countries-phones.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getCountriesPhones();
diff --git a/app/sdks/client-swift/docs/examples/locale/get-countries.md b/app/sdks/client-swift/docs/examples/locale/get-countries.md
new file mode 100644
index 0000000000..df52194af1
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-countries.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getCountries();
diff --git a/app/sdks/client-swift/docs/examples/locale/get-currencies.md b/app/sdks/client-swift/docs/examples/locale/get-currencies.md
new file mode 100644
index 0000000000..cf5f403eba
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-currencies.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getCurrencies();
diff --git a/app/sdks/client-swift/docs/examples/locale/get-languages.md b/app/sdks/client-swift/docs/examples/locale/get-languages.md
new file mode 100644
index 0000000000..e8296dd7ca
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get-languages.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.getLanguages();
diff --git a/app/sdks/client-swift/docs/examples/locale/get.md b/app/sdks/client-swift/docs/examples/locale/get.md
new file mode 100644
index 0000000000..5685e89440
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/locale/get.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var locale: Locale = Locale(client: client);
+
+var result = locale.get();
diff --git a/app/sdks/client-swift/docs/examples/storage/create-file.md b/app/sdks/client-swift/docs/examples/storage/create-file.md
new file mode 100644
index 0000000000..b4e15a4ec1
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/create-file.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.createFile(_file: nil, _read: [], _write: []);
diff --git a/app/sdks/client-swift/docs/examples/storage/delete-file.md b/app/sdks/client-swift/docs/examples/storage/delete-file.md
new file mode 100644
index 0000000000..83152d9194
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/delete-file.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.deleteFile(_fileId: "[FILE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/storage/get-file-download.md b/app/sdks/client-swift/docs/examples/storage/get-file-download.md
new file mode 100644
index 0000000000..023f1a2243
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/get-file-download.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.getFileDownload(_fileId: "[FILE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/storage/get-file-preview.md b/app/sdks/client-swift/docs/examples/storage/get-file-preview.md
new file mode 100644
index 0000000000..faaacd8e8c
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/get-file-preview.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.getFilePreview(_fileId: "[FILE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/storage/get-file-view.md b/app/sdks/client-swift/docs/examples/storage/get-file-view.md
new file mode 100644
index 0000000000..d480377b06
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/get-file-view.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.getFileView(_fileId: "[FILE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/storage/get-file.md b/app/sdks/client-swift/docs/examples/storage/get-file.md
new file mode 100644
index 0000000000..15bf5f7241
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/get-file.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.getFile(_fileId: "[FILE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/storage/list-files.md b/app/sdks/client-swift/docs/examples/storage/list-files.md
new file mode 100644
index 0000000000..bfce4293e5
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/list-files.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.listFiles();
diff --git a/app/sdks/client-swift/docs/examples/storage/update-file.md b/app/sdks/client-swift/docs/examples/storage/update-file.md
new file mode 100644
index 0000000000..6a14e47773
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/storage/update-file.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var storage: Storage = Storage(client: client);
+
+var result = storage.updateFile(_fileId: "[FILE_ID]", _read: [], _write: []);
diff --git a/app/sdks/client-swift/docs/examples/teams/create-membership.md b/app/sdks/client-swift/docs/examples/teams/create-membership.md
new file mode 100644
index 0000000000..4af13ae6ee
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/create-membership.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.createMembership(_teamId: "[TEAM_ID]", _email: "email@example.com", _roles: [], _url: "https://example.com");
diff --git a/app/sdks/client-swift/docs/examples/teams/create.md b/app/sdks/client-swift/docs/examples/teams/create.md
new file mode 100644
index 0000000000..a5cf829019
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/create.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.create(_name: "[NAME]");
diff --git a/app/sdks/client-swift/docs/examples/teams/delete-membership.md b/app/sdks/client-swift/docs/examples/teams/delete-membership.md
new file mode 100644
index 0000000000..19bf8b2f7d
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/delete-membership.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.deleteMembership(_teamId: "[TEAM_ID]", _inviteId: "[INVITE_ID]");
diff --git a/app/sdks/client-swift/docs/examples/teams/delete.md b/app/sdks/client-swift/docs/examples/teams/delete.md
new file mode 100644
index 0000000000..50cc25892b
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/delete.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.delete(_teamId: "[TEAM_ID]");
diff --git a/app/sdks/client-swift/docs/examples/teams/get-memberships.md b/app/sdks/client-swift/docs/examples/teams/get-memberships.md
new file mode 100644
index 0000000000..089ea8f717
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/get-memberships.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.getMemberships(_teamId: "[TEAM_ID]");
diff --git a/app/sdks/client-swift/docs/examples/teams/get.md b/app/sdks/client-swift/docs/examples/teams/get.md
new file mode 100644
index 0000000000..25bf212a45
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/get.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.get(_teamId: "[TEAM_ID]");
diff --git a/app/sdks/client-swift/docs/examples/teams/list.md b/app/sdks/client-swift/docs/examples/teams/list.md
new file mode 100644
index 0000000000..8333d1fe43
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/list.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.list();
diff --git a/app/sdks/client-swift/docs/examples/teams/update-membership-status.md b/app/sdks/client-swift/docs/examples/teams/update-membership-status.md
new file mode 100644
index 0000000000..ae72e4c799
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/update-membership-status.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.updateMembershipStatus(_teamId: "[TEAM_ID]", _inviteId: "[INVITE_ID]", _userId: "[USER_ID]", _secret: "[SECRET]");
diff --git a/app/sdks/client-swift/docs/examples/teams/update.md b/app/sdks/client-swift/docs/examples/teams/update.md
new file mode 100644
index 0000000000..fafd98b881
--- /dev/null
+++ b/app/sdks/client-swift/docs/examples/teams/update.md
@@ -0,0 +1,14 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+
+var client: Client = Client()
+
+client
+ .setEndpoint(endpoint: "https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
+ .setProject(value: "5df5acd0d48c2") // Your project ID
+
+var teams: Teams = Teams(client: client);
+
+var result = teams.update(_teamId: "[TEAM_ID]", _name: "[NAME]");
diff --git a/app/sdks/client-swift/docs/locale.md b/app/sdks/client-swift/docs/locale.md
new file mode 100644
index 0000000000..efe5a690e2
--- /dev/null
+++ b/app/sdks/client-swift/docs/locale.md
@@ -0,0 +1,64 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Locale Service
+
+## Get User Locale
+
+```http request
+GET https://appwrite.io/v1/locale
+```
+
+** Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.
+
+([IP Geolocation by DB-IP](https://db-ip.com)) **
+
+## List Continents
+
+```http request
+GET https://appwrite.io/v1/locale/continents
+```
+
+** List of all continents. You can use the locale header to get the data in a supported language. **
+
+## List Countries
+
+```http request
+GET https://appwrite.io/v1/locale/countries
+```
+
+** List of all countries. You can use the locale header to get the data in a supported language. **
+
+## List EU Countries
+
+```http request
+GET https://appwrite.io/v1/locale/countries/eu
+```
+
+** List of all countries that are currently members of the EU. You can use the locale header to get the data in a supported language. **
+
+## List Countries Phone Codes
+
+```http request
+GET https://appwrite.io/v1/locale/countries/phones
+```
+
+** List of all countries phone codes. You can use the locale header to get the data in a supported language. **
+
+## List Currencies
+
+```http request
+GET https://appwrite.io/v1/locale/currencies
+```
+
+** List of all currencies, including currency symbol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in a supported language. **
+
+## List Languages
+
+```http request
+GET https://appwrite.io/v1/locale/languages
+```
+
+** List of all languages classified by ISO 639-1 including 2-letter code, name in English, and name in the respective language. **
+
diff --git a/app/sdks/client-swift/docs/storage.md b/app/sdks/client-swift/docs/storage.md
new file mode 100644
index 0000000000..f902ebd2e9
--- /dev/null
+++ b/app/sdks/client-swift/docs/storage.md
@@ -0,0 +1,131 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Storage Service
+
+## List Files
+
+```http request
+GET https://appwrite.io/v1/storage/files
+```
+
+** Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](/docs/admin). **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| search | string | Search term to filter your list results. | |
+| limit | integer | Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request. | 25 |
+| offset | integer | Results offset. The default value is 0. Use this param to manage pagination. | 0 |
+| orderType | string | Order result by ASC or DESC order. | ASC |
+
+## Create File
+
+```http request
+POST https://appwrite.io/v1/storage/files
+```
+
+** Create a new file. The user who creates the file will automatically be assigned to read and write access unless he has passed custom values for read and write arguments. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| file | file | Binary file. | |
+| read | array | An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+| write | array | An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+
+## Get File
+
+```http request
+GET https://appwrite.io/v1/storage/files/{fileId}
+```
+
+** Get file by its unique ID. This endpoint response returns a JSON object with the file metadata. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID. | |
+
+## Update File
+
+```http request
+PUT https://appwrite.io/v1/storage/files/{fileId}
+```
+
+** Update file by its unique ID. Only users with write permissions have access to update this resource. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID. | |
+| read | array | An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+| write | array | An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions. | |
+
+## Delete File
+
+```http request
+DELETE https://appwrite.io/v1/storage/files/{fileId}
+```
+
+** Delete a file by its unique ID. Only users with write permissions have access to delete this resource. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID. | |
+
+## Get File for Download
+
+```http request
+GET https://appwrite.io/v1/storage/files/{fileId}/download
+```
+
+** Get file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID. | |
+
+## Get File Preview
+
+```http request
+GET https://appwrite.io/v1/storage/files/{fileId}/preview
+```
+
+** Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID | |
+| width | integer | Resize preview image width, Pass an integer between 0 to 4000. | 0 |
+| height | integer | Resize preview image height, Pass an integer between 0 to 4000. | 0 |
+| quality | integer | Preview image quality. Pass an integer between 0 to 100. Defaults to 100. | 100 |
+| background | string | Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix. | |
+| output | string | Output format type (jpeg, jpg, png, gif and webp). | |
+
+## Get File for View
+
+```http request
+GET https://appwrite.io/v1/storage/files/{fileId}/view
+```
+
+** Get file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| fileId | string | **Required** File unique ID. | |
+| as | string | Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk. | |
+
diff --git a/app/sdks/client-swift/docs/teams.md b/app/sdks/client-swift/docs/teams.md
new file mode 100644
index 0000000000..7f8b4238a7
--- /dev/null
+++ b/app/sdks/client-swift/docs/teams.md
@@ -0,0 +1,153 @@
+/// Swift Appwrite SDK
+/// Produced by Appwrite SDK Generator
+///
+
+# Teams Service
+
+## List Teams
+
+```http request
+GET https://appwrite.io/v1/teams
+```
+
+** Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](/docs/admin). **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| search | string | Search term to filter your list results. | |
+| limit | integer | Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request. | 25 |
+| offset | integer | Results offset. The default value is 0. Use this param to manage pagination. | 0 |
+| orderType | string | Order result by ASC or DESC order. | ASC |
+
+## Create Team
+
+```http request
+POST https://appwrite.io/v1/teams
+```
+
+** Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| name | string | Team name. | |
+| roles | array | Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). | ["owner"] |
+
+## Get Team
+
+```http request
+GET https://appwrite.io/v1/teams/{teamId}
+```
+
+** Get team by its unique ID. All team members have read access for this resource. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+
+## Update Team
+
+```http request
+PUT https://appwrite.io/v1/teams/{teamId}
+```
+
+** Update team by its unique ID. Only team owners have write access for this resource. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+| name | string | Team name. | |
+
+## Delete Team
+
+```http request
+DELETE https://appwrite.io/v1/teams/{teamId}
+```
+
+** Delete team by its unique ID. Only team owners have write access for this resource. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+
+## Get Team Memberships
+
+```http request
+GET https://appwrite.io/v1/teams/{teamId}/memberships
+```
+
+** Get team members by the team unique ID. All team members have read access for this list of resources. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+| search | string | Search term to filter your list results. | |
+| limit | integer | Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request. | 25 |
+| offset | integer | Results offset. The default value is 0. Use this param to manage pagination. | 0 |
+| orderType | string | Order result by ASC or DESC order. | ASC |
+
+## Create Team Membership
+
+```http request
+POST https://appwrite.io/v1/teams/{teamId}/memberships
+```
+
+** Use this endpoint to invite a new member to join your team. An email with a link to join the team will be sent to the new member email address if the member doesn't exist in the project it will be created automatically.
+
+Use the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](/docs/client/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team.
+
+Please note that in order to avoid a [Redirect Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+| email | string | New team member email. | |
+| name | string | New team member name. | |
+| roles | array | Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). | |
+| url | string | URL to redirect the user back to your app from the invitation 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. | |
+
+## Delete Team Membership
+
+```http request
+DELETE https://appwrite.io/v1/teams/{teamId}/memberships/{inviteId}
+```
+
+** This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member. You can also use this endpoint to delete a user membership even if he didn't accept it. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+| inviteId | string | **Required** Invite unique ID. | |
+
+## Update Team Membership Status
+
+```http request
+PATCH https://appwrite.io/v1/teams/{teamId}/memberships/{inviteId}/status
+```
+
+** Use this endpoint to allow a user to accept an invitation to join a team after he is being redirected back to your app from the invitation email he was sent. **
+
+### Parameters
+
+| Field Name | Type | Description | Default |
+| --- | --- | --- | --- |
+| teamId | string | **Required** Team unique ID. | |
+| inviteId | string | **Required** Invite unique ID. | |
+| userId | string | User unique ID. | |
+| secret | string | Secret key. | |
+
diff --git a/app/sdks/client-web/README.md b/app/sdks/client-web/README.md
index 093783ab41..41e939b017 100644
--- a/app/sdks/client-web/README.md
+++ b/app/sdks/client-web/README.md
@@ -1,7 +1,7 @@
# Appwrite Web SDK

-
+
Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way.
Use the Web SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools.
diff --git a/app/sdks/client-web/src/sdk.js b/app/sdks/client-web/src/sdk.js
index 5c98ea89b3..90c9b8021b 100644
--- a/app/sdks/client-web/src/sdk.js
+++ b/app/sdks/client-web/src/sdk.js
@@ -787,16 +787,17 @@
* Use this endpoint to send a verification message to your user email address
* to confirm they are the valid owners of that address. Both the **userId**
* and **secret** arguments will be passed as query parameters to the URL you
- * have provider to be attached to the verification email. The provided URL
- * should redirect the user back for your app and allow you to complete the
+ * have provided to be attached to the verification email. The provided URL
+ * should redirect the user back to your app and allow you to complete the
* verification process by verifying both the **userId** and **secret**
* parameters. Learn more about how to [complete the verification
* process](/docs/client/account#updateAccountVerification).
*
* Please note that in order to avoid a [Redirect
- * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+ * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
* the only valid redirect URLs are the ones from domains you have set when
* adding your platforms in the console interface.
+ *
*
* @param {string} url
* @throws {Error}
@@ -1217,11 +1218,11 @@
* @param {string} text
* @param {number} size
* @param {number} margin
- * @param {number} download
+ * @param {boolean} download
* @throws {Error}
* @return {string}
*/
- getQR: function(text, size = 400, margin = 1, download = 0) {
+ getQR: function(text, size = 400, margin = 1, download = false) {
if(text === undefined) {
throw new Error('Missing required parameter: "text"');
}
@@ -1281,18 +1282,16 @@
*
* @param {string} collectionId
* @param {string[]} filters
- * @param {number} offset
* @param {number} limit
+ * @param {number} offset
* @param {string} orderField
* @param {string} orderType
* @param {string} orderCast
* @param {string} search
- * @param {number} first
- * @param {number} last
* @throws {Error}
* @return {Promise}
*/
- listDocuments: function(collectionId, filters = [], offset = 0, limit = 50, orderField = '$id', orderType = 'ASC', orderCast = 'string', search = '', first = 0, last = 0) {
+ listDocuments: function(collectionId, filters = [], limit = 25, offset = 0, orderField = '$id', orderType = 'ASC', orderCast = 'string', search = '') {
if(collectionId === undefined) {
throw new Error('Missing required parameter: "collectionId"');
}
@@ -1305,14 +1304,14 @@
payload['filters'] = filters;
}
- if(offset) {
- payload['offset'] = offset;
- }
-
if(limit) {
payload['limit'] = limit;
}
+ if(offset) {
+ payload['offset'] = offset;
+ }
+
if(orderField) {
payload['orderField'] = orderField;
}
@@ -1329,14 +1328,6 @@
payload['search'] = search;
}
- if(first) {
- payload['first'] = first;
- }
-
- if(last) {
- payload['last'] = last;
- }
-
return http
.get(path, {
'content-type': 'application/json',
diff --git a/app/sdks/client-web/src/sdk.min.js b/app/sdks/client-web/src/sdk.min.js
index 7b470dc380..2bc06cc935 100644
--- a/app/sdks/client-web/src/sdk.min.js
+++ b/app/sdks/client-web/src/sdk.min.js
@@ -86,22 +86,20 @@ if(height){payload.height=height}
if(color){payload.color=color}
if(background){payload.background=background}
payload.project=config.project;let query=[];for(let p in payload){if(Array.isArray(payload[p])){for(let index=0;index;
+ listDocuments(collectionId: string, filters: string[], limit: number, offset: number, orderField: string, orderType: string, orderCast: string, search: string): Promise