From e032bd8a7880eb5e8292be55ff2fffdb391705f4 Mon Sep 17 00:00:00 2001
From: NaiJi <NaiJi@udongein.xyz>
Date: Thu, 13 Oct 2022 23:13:56 +0000
Subject: [PATCH] feat(region): Implement endpoints for listing available
 provider regions

---
 .../digital_ocean/digital_ocean.dart          | 64 +++++++++++++++++++
 .../server_providers/hetzner/hetzner.dart     | 53 ++++++++++++++-
 .../server_providers/server_provider.dart     |  2 +
 .../hetzner_metrics_repository.dart           |  5 +-
 .../provider_volume_cubit.dart                | 53 ++++++++++++---
 .../server_installation_cubit.dart            |  9 ++-
 .../server_installation_repository.dart       | 42 ++++++++++--
 .../models/server_provider_location.dart      | 13 ++++
 8 files changed, 220 insertions(+), 21 deletions(-)
 create mode 100644 lib/logic/models/server_provider_location.dart

diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart
index 2354e5ab..798fc54b 100644
--- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart
+++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart
@@ -11,6 +11,7 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart';
 import 'package:selfprivacy/logic/models/hive/user.dart';
 import 'package:selfprivacy/logic/models/price.dart';
 import 'package:selfprivacy/logic/models/server_basic_info.dart';
+import 'package:selfprivacy/logic/models/server_provider_location.dart';
 import 'package:selfprivacy/utils/password_generator.dart';
 
 class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
@@ -538,6 +539,69 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
     return servers;
   }
 
+  String? getEmojiFlag(final String query) {
+    String? emoji;
+
+    switch (query.toLowerCase().substring(0, 2)) {
+      case 'fra':
+        emoji = '🇩🇪';
+        break;
+
+      case 'ams':
+        emoji = '🇳🇱';
+        break;
+
+      case 'sgp':
+        emoji = '🇸🇬';
+        break;
+
+      case 'lon':
+        emoji = '🇬🇧';
+        break;
+
+      case 'tor':
+        emoji = '🇨🇦';
+        break;
+
+      case 'blr':
+        emoji = '🇮🇳';
+        break;
+
+      case 'nyc':
+      case 'sfo':
+        emoji = '🇺🇸';
+        break;
+    }
+
+    return emoji;
+  }
+
+  @override
+  Future<List<ServerProviderLocation>> getAvailableLocations() async {
+    List<ServerProviderLocation> locations = [];
+
+    final Dio client = await getClient();
+    try {
+      final Response response = await client.post(
+        '/locations',
+      );
+
+      locations = response.data!['locations'].map<ServerProviderLocation>(
+        (final location) => ServerProviderLocation(
+          title: location['slug'],
+          description: location['name'],
+          flag: getEmojiFlag(location['slug']),
+        ),
+      );
+    } catch (e) {
+      print(e);
+    } finally {
+      close(client);
+    }
+
+    return locations;
+  }
+
   @override
   Future<void> createReverseDns({
     required final ServerHostingDetails serverDetails,
diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart
index fe577c03..2c0b70db 100644
--- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart
+++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart
@@ -11,10 +11,15 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart';
 import 'package:selfprivacy/logic/models/hive/user.dart';
 import 'package:selfprivacy/logic/models/price.dart';
 import 'package:selfprivacy/logic/models/server_basic_info.dart';
+import 'package:selfprivacy/logic/models/server_provider_location.dart';
 import 'package:selfprivacy/utils/password_generator.dart';
 
 class HetznerApi extends ServerProviderApi with VolumeProviderApi {
-  HetznerApi({required this.region, this.hasLogger = false, this.isWithToken = true,});
+  HetznerApi({
+    required this.region,
+    this.hasLogger = false,
+    this.isWithToken = true,
+  });
   @override
   bool hasLogger;
   @override
@@ -536,6 +541,52 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
     return servers;
   }
 
+  String? getEmojiFlag(final String query) {
+    String? emoji;
+
+    switch (query.toLowerCase()) {
+      case 'de':
+        emoji = '🇩🇪';
+        break;
+
+      case 'fi':
+        emoji = '🇫🇮';
+        break;
+
+      case 'us':
+        emoji = '🇺🇸';
+        break;
+    }
+
+    return emoji;
+  }
+
+  @override
+  Future<List<ServerProviderLocation>> getAvailableLocations() async {
+    List<ServerProviderLocation> locations = [];
+
+    final Dio client = await getClient();
+    try {
+      final Response response = await client.post(
+        '/locations',
+      );
+
+      locations = response.data!['locations'].map<ServerProviderLocation>(
+        (final location) => ServerProviderLocation(
+          title: location['city'],
+          description: location['description'],
+          flag: getEmojiFlag(location['country']),
+        ),
+      );
+    } catch (e) {
+      print(e);
+    } finally {
+      close(client);
+    }
+
+    return locations;
+  }
+
   @override
   Future<void> createReverseDns({
     required final ServerHostingDetails serverDetails,
diff --git a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart
index c2bfd533..d4b61721 100644
--- a/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart
+++ b/lib/logic/api_maps/rest_maps/server_providers/server_provider.dart
@@ -3,9 +3,11 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart';
 import 'package:selfprivacy/logic/models/hive/server_domain.dart';
 import 'package:selfprivacy/logic/models/hive/user.dart';
 import 'package:selfprivacy/logic/models/server_basic_info.dart';
+import 'package:selfprivacy/logic/models/server_provider_location.dart';
 
 abstract class ServerProviderApi extends ApiMap {
   Future<List<ServerBasicInfo>> getServers();
+  Future<List<ServerProviderLocation>> getAvailableLocations();
 
   Future<ServerHostingDetails> restart();
   Future<ServerHostingDetails> powerOn();
diff --git a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart
index 5b363406..18137fdc 100644
--- a/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart
+++ b/lib/logic/cubit/hetzner_metrics/hetzner_metrics_repository.dart
@@ -26,7 +26,10 @@ class HetznerMetricsRepository {
         break;
     }
 
-    final HetznerApi api = HetznerApi(hasLogger: false, region: 'fra1',);
+    final HetznerApi api = HetznerApi(
+      hasLogger: false,
+      region: 'fra1',
+    );
 
     final List<Map<String, dynamic>> results = await Future.wait([
       api.getMetrics(start, end, 'cpu'),
diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart
index 1eb47bf3..95b0d362 100644
--- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart
+++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart
@@ -34,8 +34,13 @@ class ApiProviderVolumeCubit
     }
   }
 
-  Future<Price?> getPricePerGb() async =>
-      providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getPricePerGb();
+  Future<Price?> getPricePerGb() async => providerApi!
+      .getVolumeProvider(
+        settings: const ServerProviderApiSettings(
+          region: 'fra1',
+        ),
+      )
+      .getPricePerGb();
 
   Future<void> refresh() async {
     emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false));
@@ -47,8 +52,13 @@ class ApiProviderVolumeCubit
       return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
     }
 
-    final List<ServerVolume> volumes =
-        await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).getVolumes();
+    final List<ServerVolume> volumes = await providerApi!
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
+        .getVolumes();
 
     if (volumes.isEmpty) {
       return emit(const ApiProviderVolumeState([], LoadingStatus.error, false));
@@ -60,14 +70,22 @@ class ApiProviderVolumeCubit
   Future<void> attachVolume(final DiskVolume volume) async {
     final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
     await providerApi!
-        .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),)
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
         .attachVolume(volume.providerVolume!.id.toString(), server.id);
     refresh();
   }
 
   Future<void> detachVolume(final DiskVolume volume) async {
     await providerApi!
-        .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),)
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
         .detachVolume(volume.providerVolume!.id.toString());
     refresh();
   }
@@ -81,7 +99,13 @@ class ApiProviderVolumeCubit
       'Starting resize',
     );
     emit(state.copyWith(isResizing: true));
-    final bool resized = await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).resizeVolume(
+    final bool resized = await providerApi!
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
+        .resizeVolume(
           volume.providerVolume!.id.toString(),
           newSizeGb,
         );
@@ -117,8 +141,13 @@ class ApiProviderVolumeCubit
   }
 
   Future<void> createVolume() async {
-    final ServerVolume? volume =
-        await providerApi!.getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),).createVolume();
+    final ServerVolume? volume = await providerApi!
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
+        .createVolume();
 
     final diskVolume = DiskVolume(providerVolume: volume);
     await attachVolume(diskVolume);
@@ -131,7 +160,11 @@ class ApiProviderVolumeCubit
 
   Future<void> deleteVolume(final DiskVolume volume) async {
     await providerApi!
-        .getVolumeProvider(settings: const ServerProviderApiSettings(region: 'fra1',),)
+        .getVolumeProvider(
+          settings: const ServerProviderApiSettings(
+            region: 'fra1',
+          ),
+        )
         .deleteVolume(volume.providerVolume!.id.toString());
     refresh();
   }
diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart
index 8e121044..99dd1d82 100644
--- a/lib/logic/cubit/server_installation/server_installation_cubit.dart
+++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart
@@ -63,7 +63,11 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
 
   RegExp getServerProviderApiTokenValidation() =>
       repository.serverProviderApiFactory!
-          .getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),)
+          .getServerProvider(
+            settings: const ServerProviderApiSettings(
+              region: 'fra1',
+            ),
+          )
           .getApiTokenValidation();
 
   RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory!
@@ -75,7 +79,8 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
   ) async =>
       repository.serverProviderApiFactory!
           .getServerProvider(
-            settings: const ServerProviderApiSettings(region: 'fra1', isWithToken: false),
+            settings: const ServerProviderApiSettings(
+                region: 'fra1', isWithToken: false),
           )
           .isApiTokenValid(providerToken);
 
diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart
index 10306c61..4fe10b1f 100644
--- a/lib/logic/cubit/server_installation/server_installation_repository.dart
+++ b/lib/logic/cubit/server_installation/server_installation_repository.dart
@@ -153,7 +153,11 @@ class ServerInstallationRepository {
   ) async {
     ServerHostingDetails serverDetails;
 
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     serverDetails = await api.powerOn();
 
     return serverDetails;
@@ -229,7 +233,11 @@ class ServerInstallationRepository {
     required final Future<void> Function(ServerHostingDetails serverDetails)
         onSuccess,
   }) async {
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     try {
       final ServerHostingDetails? serverDetails = await api.createServer(
         dnsApiToken: cloudFlareKey,
@@ -334,7 +342,11 @@ class ServerInstallationRepository {
     final DnsProviderApi dnsProviderApi =
         dnsProviderApiFactory!.getDnsProvider();
     final ServerProviderApi serverApi =
-        serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+        serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
 
     await dnsProviderApi.removeSimilarRecords(
       ip4: serverDetails.ip4,
@@ -406,12 +418,20 @@ class ServerInstallationRepository {
   }
 
   Future<ServerHostingDetails> restart() async {
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     return api.restart();
   }
 
   Future<ServerHostingDetails> powerOn() async {
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     return api.powerOn();
   }
 
@@ -654,7 +674,11 @@ class ServerInstallationRepository {
   }
 
   Future<List<ServerBasicInfo>> getServersOnProviderAccount() async {
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     return api.getServers();
   }
 
@@ -732,7 +756,11 @@ class ServerInstallationRepository {
   }
 
   Future<void> deleteServer(final ServerDomain serverDomain) async {
-    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(settings: const ServerProviderApiSettings(region: 'fra1',),);
+    final ServerProviderApi api = serverProviderApiFactory!.getServerProvider(
+      settings: const ServerProviderApiSettings(
+        region: 'fra1',
+      ),
+    );
     final DnsProviderApi dnsProviderApi =
         dnsProviderApiFactory!.getDnsProvider();
 
diff --git a/lib/logic/models/server_provider_location.dart b/lib/logic/models/server_provider_location.dart
new file mode 100644
index 00000000..dea063a4
--- /dev/null
+++ b/lib/logic/models/server_provider_location.dart
@@ -0,0 +1,13 @@
+class ServerProviderLocation {
+  ServerProviderLocation({
+    required this.title,
+    this.description,
+    this.flag,
+  });
+
+  final String title;
+  final String? description;
+
+  /// as emoji
+  final String? flag;
+}