44
55use Appwrite \AppwriteException ;
66use Appwrite \Client ;
7+ use Appwrite \Enums \Adapter ;
8+ use Appwrite \Enums \BuildRuntime ;
79use Appwrite \Enums \Compression ;
10+ use Appwrite \Enums \Framework ;
811use Appwrite \Enums \PasswordHash ;
912use Appwrite \Enums \Runtime ;
1013use Appwrite \InputFile ;
1114use Appwrite \Services \Functions ;
15+ use Appwrite \Services \Sites ;
1216use Appwrite \Services \Storage ;
1317use Appwrite \Services \Teams ;
1418use Appwrite \Services \Users ;
4347use Utopia \Migration \Resources \Functions \Deployment ;
4448use Utopia \Migration \Resources \Functions \EnvVar ;
4549use Utopia \Migration \Resources \Functions \Func ;
50+ use Utopia \Migration \Resources \Sites \Deployment as SiteDeployment ;
51+ use Utopia \Migration \Resources \Sites \EnvVar as SiteEnvVar ;
52+ use Utopia \Migration \Resources \Sites \Site ;
4653use Utopia \Migration \Resources \Storage \Bucket ;
4754use Utopia \Migration \Resources \Storage \File ;
4855use Utopia \Migration \Transfer ;
@@ -55,6 +62,7 @@ class Appwrite extends Destination
5562 protected string $ key ;
5663
5764 private Functions $ functions ;
65+ private Sites $ sites ;
5866 private Storage $ storage ;
5967 private Teams $ teams ;
6068 private Users $ users ;
@@ -102,6 +110,7 @@ public function __construct(
102110 ->setKey ($ key );
103111
104112 $ this ->functions = new Functions ($ this ->client );
113+ $ this ->sites = new Sites ($ this ->client );
105114 $ this ->storage = new Storage ($ this ->client );
106115 $ this ->teams = new Teams ($ this ->client );
107116 $ this ->users = new Users ($ this ->client );
@@ -148,6 +157,11 @@ public static function getSupportedResources(): array
148157 Resource::TYPE_FUNCTION ,
149158 Resource::TYPE_DEPLOYMENT ,
150159 Resource::TYPE_ENVIRONMENT_VARIABLE ,
160+
161+ // Sites
162+ Resource::TYPE_SITE ,
163+ Resource::TYPE_SITE_DEPLOYMENT ,
164+ Resource::TYPE_SITE_VARIABLE ,
151165 ];
152166 }
153167
@@ -219,6 +233,15 @@ public function report(array $resources = [], array $resourceIds = []): array
219233 $ this ->functions ->create ('' , '' , Runtime::NODE180 ());
220234 }
221235
236+ // Sites
237+ if (\in_array (Resource::TYPE_SITE , $ resources )) {
238+ $ scope = 'sites.read ' ;
239+ $ this ->sites ->list ();
240+
241+ $ scope = 'sites.write ' ;
242+ $ this ->sites ->create ('' , '' , Framework::OTHER (), BuildRuntime::STATIC1 ());
243+ }
244+
222245 } catch (AppwriteException $ e ) {
223246 if ($ e ->getCode () === 403 ) {
224247 throw new \Exception ('Missing scope: ' . $ scope , previous: $ e );
@@ -256,6 +279,7 @@ protected function import(array $resources, callable $callback): void
256279 Transfer::GROUP_STORAGE => $ this ->importFileResource ($ resource ),
257280 Transfer::GROUP_AUTH => $ this ->importAuthResource ($ resource ),
258281 Transfer::GROUP_FUNCTIONS => $ this ->importFunctionResource ($ resource ),
282+ Transfer::GROUP_SITES => $ this ->importSiteResource ($ resource ),
259283 default => throw new \Exception ('Invalid resource group ' ),
260284 };
261285 } catch (\Throwable $ e ) {
@@ -1465,6 +1489,207 @@ private function importDeployment(Deployment $deployment): Resource
14651489 return $ deployment ;
14661490 }
14671491
1492+ /**
1493+ * @throws AppwriteException
1494+ */
1495+ public function importSiteResource (Resource $ resource ): Resource
1496+ {
1497+ switch ($ resource ->getName ()) {
1498+ case Resource::TYPE_SITE :
1499+ /** @var Site $resource */
1500+
1501+ $ buildRuntime = match ($ resource ->getBuildRuntime ()) {
1502+ 'node-14.5 ' => BuildRuntime::NODE145 (),
1503+ 'node-16.0 ' => BuildRuntime::NODE160 (),
1504+ 'node-18.0 ' => BuildRuntime::NODE180 (),
1505+ 'node-19.0 ' => BuildRuntime::NODE190 (),
1506+ 'node-20.0 ' => BuildRuntime::NODE200 (),
1507+ 'node-21.0 ' => BuildRuntime::NODE210 (),
1508+ 'node-22 ' => BuildRuntime::NODE22 (),
1509+ 'php-8.0 ' => BuildRuntime::PHP80 (),
1510+ 'php-8.1 ' => BuildRuntime::PHP81 (),
1511+ 'php-8.2 ' => BuildRuntime::PHP82 (),
1512+ 'php-8.3 ' => BuildRuntime::PHP83 (),
1513+ 'ruby-3.0 ' => BuildRuntime::RUBY30 (),
1514+ 'ruby-3.1 ' => BuildRuntime::RUBY31 (),
1515+ 'ruby-3.2 ' => BuildRuntime::RUBY32 (),
1516+ 'ruby-3.3 ' => BuildRuntime::RUBY33 (),
1517+ 'python-3.8 ' => BuildRuntime::PYTHON38 (),
1518+ 'python-3.9 ' => BuildRuntime::PYTHON39 (),
1519+ 'python-3.10 ' => BuildRuntime::PYTHON310 (),
1520+ 'python-3.11 ' => BuildRuntime::PYTHON311 (),
1521+ 'python-3.12 ' => BuildRuntime::PYTHON312 (),
1522+ 'python-ml-3.11 ' => BuildRuntime::PYTHONML311 (),
1523+ 'python-ml-3.12 ' => BuildRuntime::PYTHONML312 (),
1524+ 'dart-3.0 ' => BuildRuntime::DART30 (),
1525+ 'dart-3.1 ' => BuildRuntime::DART31 (),
1526+ 'dart-3.3 ' => BuildRuntime::DART33 (),
1527+ 'dart-3.5 ' => BuildRuntime::DART35 (),
1528+ 'dart-3.8 ' => BuildRuntime::DART38 (),
1529+ 'dart-3.9 ' => BuildRuntime::DART39 (),
1530+ 'dart-2.15 ' => BuildRuntime::DART215 (),
1531+ 'dart-2.16 ' => BuildRuntime::DART216 (),
1532+ 'dart-2.17 ' => BuildRuntime::DART217 (),
1533+ 'dart-2.18 ' => BuildRuntime::DART218 (),
1534+ 'dart-2.19 ' => BuildRuntime::DART219 (),
1535+ 'deno-1.21 ' => BuildRuntime::DENO121 (),
1536+ 'deno-1.24 ' => BuildRuntime::DENO124 (),
1537+ 'deno-1.35 ' => BuildRuntime::DENO135 (),
1538+ 'deno-1.40 ' => BuildRuntime::DENO140 (),
1539+ 'deno-1.46 ' => BuildRuntime::DENO146 (),
1540+ 'deno-2.0 ' => BuildRuntime::DENO20 (),
1541+ 'dotnet-6.0 ' => BuildRuntime::DOTNET60 (),
1542+ 'dotnet-7.0 ' => BuildRuntime::DOTNET70 (),
1543+ 'dotnet-8.0 ' => BuildRuntime::DOTNET80 (),
1544+ 'java-8.0 ' => BuildRuntime::JAVA80 (),
1545+ 'java-11.0 ' => BuildRuntime::JAVA110 (),
1546+ 'java-17.0 ' => BuildRuntime::JAVA170 (),
1547+ 'java-18.0 ' => BuildRuntime::JAVA180 (),
1548+ 'java-21.0 ' => BuildRuntime::JAVA210 (),
1549+ 'java-22 ' => BuildRuntime::JAVA22 (),
1550+ 'swift-5.5 ' => BuildRuntime::SWIFT55 (),
1551+ 'swift-5.8 ' => BuildRuntime::SWIFT58 (),
1552+ 'swift-5.9 ' => BuildRuntime::SWIFT59 (),
1553+ 'swift-5.10 ' => BuildRuntime::SWIFT510 (),
1554+ 'kotlin-1.6 ' => BuildRuntime::KOTLIN16 (),
1555+ 'kotlin-1.8 ' => BuildRuntime::KOTLIN18 (),
1556+ 'kotlin-1.9 ' => BuildRuntime::KOTLIN19 (),
1557+ 'kotlin-2.0 ' => BuildRuntime::KOTLIN20 (),
1558+ 'cpp-17 ' => BuildRuntime::CPP17 (),
1559+ 'cpp-20 ' => BuildRuntime::CPP20 (),
1560+ 'bun-1.0 ' => BuildRuntime::BUN10 (),
1561+ 'bun-1.1 ' => BuildRuntime::BUN11 (),
1562+ 'go-1.23 ' => BuildRuntime::GO123 (),
1563+ 'static-1 ' => BuildRuntime::STATIC1 (),
1564+ 'flutter-3.24 ' => BuildRuntime::FLUTTER324 (),
1565+ 'flutter-3.27 ' => BuildRuntime::FLUTTER327 (),
1566+ 'flutter-3.29 ' => BuildRuntime::FLUTTER329 (),
1567+ 'flutter-3.32 ' => BuildRuntime::FLUTTER332 (),
1568+ 'flutter-3.35 ' => BuildRuntime::FLUTTER335 (),
1569+ default => throw new \Exception ('Invalid Build Runtime: ' . $ resource ->getBuildRuntime ()),
1570+ };
1571+
1572+ $ framework = match ($ resource ->getFramework ()) {
1573+ 'analog ' => Framework::ANALOG (),
1574+ 'angular ' => Framework::ANGULAR (),
1575+ 'astro ' => Framework::ASTRO (),
1576+ 'flutter ' , 'flutter-web ' => Framework::FLUTTER (),
1577+ 'lynx ' => Framework::LYNX (),
1578+ 'nextjs ' => Framework::NEXTJS (),
1579+ 'nuxt ' => Framework::NUXT (),
1580+ 'react ' => Framework::REACT (),
1581+ 'react-native ' => Framework::REACTNATIVE (),
1582+ 'remix ' => Framework::REMIX (),
1583+ 'svelte-kit ' => Framework::SVELTEKIT (),
1584+ 'tanstack-start ' => Framework::TANSTACKSTART (),
1585+ 'vite ' => Framework::VITE (),
1586+ 'vue ' => Framework::VUE (),
1587+ default => Framework::OTHER (),
1588+ };
1589+
1590+ $ adapter = match ($ resource ->getAdapter ()) {
1591+ 'static ' => Adapter::STATIC (),
1592+ 'ssr ' => Adapter::SSR (),
1593+ default => null ,
1594+ };
1595+
1596+ $ this ->sites ->create (
1597+ $ resource ->getId (),
1598+ $ resource ->getSiteName (),
1599+ $ framework ,
1600+ $ buildRuntime ,
1601+ $ resource ->getEnabled (),
1602+ $ resource ->getLogging (),
1603+ $ resource ->getTimeout (),
1604+ $ resource ->getInstallCommand (),
1605+ $ resource ->getBuildCommand (),
1606+ $ resource ->getOutputDirectory (),
1607+ $ adapter ,
1608+ fallbackFile: $ resource ->getFallbackFile (),
1609+ specification: $ resource ->getSpecification (),
1610+ );
1611+ break ;
1612+ case Resource::TYPE_SITE_VARIABLE :
1613+ /** @var SiteEnvVar $resource */
1614+ $ this ->sites ->createVariable (
1615+ $ resource ->getSite ()->getId (),
1616+ $ resource ->getKey (),
1617+ $ resource ->getValue ()
1618+ );
1619+ break ;
1620+ case Resource::TYPE_SITE_DEPLOYMENT :
1621+ /** @var SiteDeployment $resource */
1622+ return $ this ->importSiteDeployment ($ resource );
1623+ }
1624+
1625+ $ resource ->setStatus (Resource::STATUS_SUCCESS );
1626+
1627+ return $ resource ;
1628+ }
1629+
1630+ /**
1631+ * @throws AppwriteException
1632+ * @throws \Exception
1633+ */
1634+ private function importSiteDeployment (SiteDeployment $ deployment ): Resource
1635+ {
1636+ $ siteId = $ deployment ->getSite ()->getId ();
1637+
1638+ if ($ deployment ->getSize () <= Transfer::STORAGE_MAX_CHUNK_SIZE ) {
1639+ $ response = $ this ->client ->call (
1640+ 'POST ' ,
1641+ "/sites/ {$ siteId }/deployments " ,
1642+ [
1643+ 'content-type ' => 'multipart/form-data ' ,
1644+ ],
1645+ [
1646+ 'siteId ' => $ siteId ,
1647+ 'code ' => new \CURLFile ('data://application/gzip;base64, ' . base64_encode ($ deployment ->getData ()), 'application/gzip ' , 'deployment.tar.gz ' ),
1648+ 'activate ' => $ deployment ->getActivated (),
1649+ ]
1650+ );
1651+
1652+ if (!\is_array ($ response ) || !isset ($ response ['$id ' ])) {
1653+ throw new \Exception ('Site deployment creation failed ' );
1654+ }
1655+
1656+ $ deployment ->setStatus (Resource::STATUS_SUCCESS );
1657+
1658+ return $ deployment ;
1659+ }
1660+
1661+ $ response = $ this ->client ->call (
1662+ 'POST ' ,
1663+ "/sites/ {$ siteId }/deployments " ,
1664+ [
1665+ 'content-type ' => 'multipart/form-data ' ,
1666+ 'content-range ' => 'bytes ' . ($ deployment ->getStart ()) . '- ' . ($ deployment ->getEnd () == ($ deployment ->getSize () - 1 ) ? $ deployment ->getSize () : $ deployment ->getEnd ()) . '/ ' . $ deployment ->getSize (),
1667+ 'x-appwrite-id ' => $ deployment ->getId (),
1668+ ],
1669+ [
1670+ 'siteId ' => $ siteId ,
1671+ 'code ' => new \CURLFile ('data://application/gzip;base64, ' . base64_encode ($ deployment ->getData ()), 'application/gzip ' , 'deployment.tar.gz ' ),
1672+ 'activate ' => $ deployment ->getActivated (),
1673+ ]
1674+ );
1675+
1676+ if (!\is_array ($ response ) || !isset ($ response ['$id ' ])) {
1677+ throw new \Exception ('Site deployment creation failed ' );
1678+ }
1679+
1680+ if ($ deployment ->getStart () === 0 ) {
1681+ $ deployment ->setId ($ response ['$id ' ]);
1682+ }
1683+
1684+ if ($ deployment ->getEnd () == ($ deployment ->getSize () - 1 )) {
1685+ $ deployment ->setStatus (Resource::STATUS_SUCCESS );
1686+ } else {
1687+ $ deployment ->setStatus (Resource::STATUS_PENDING );
1688+ }
1689+
1690+ return $ deployment ;
1691+ }
1692+
14681693 private function validateFieldsForIndexes (Index $ resource , UtopiaDocument $ table , array &$ lengths )
14691694 {
14701695 /**
0 commit comments