From 41dc77103f8a98b7076a9bee7469f387c6bf2d83 Mon Sep 17 00:00:00 2001
From: NaiJi <naijiworld@protonmail.com>
Date: Thu, 22 Dec 2022 22:45:06 +0400
Subject: [PATCH] feat: Implement error handling on server deletion

Notify users when errors occured and handle application state accordingly
---
 assets/translations/en.json                   |  2 +
 assets/translations/ru.json                   |  2 +
 .../digital_ocean/digital_ocean.dart          | 48 +++++++++++++++---
 .../server_providers/hetzner/hetzner.dart     | 49 ++++++++++++-------
 .../server_providers/server_provider.dart     |  4 +-
 .../server_installation_cubit.dart            |  6 ++-
 .../server_installation_repository.dart       | 25 ++++++++--
 7 files changed, 107 insertions(+), 29 deletions(-)

diff --git a/assets/translations/en.json b/assets/translations/en.json
index daa4544f..1b596cba 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -392,6 +392,8 @@
         "generation_error": "Couldn't generate a recovery key. {}"
     },
     "modals": {
+        "dns_removal_error": "Couldn't remove DNS records.",
+        "server_deletion_error": "Couldn't delete active server.",
         "server_validators_error": "Couldn't fetch available servers.",
         "already_exists": "Such server already exists.",
         "unexpected_error": "Unexpected error during placement from the provider side.",
diff --git a/assets/translations/ru.json b/assets/translations/ru.json
index a5cc6634..8764580a 100644
--- a/assets/translations/ru.json
+++ b/assets/translations/ru.json
@@ -392,6 +392,8 @@
         "generation_error": "Не удалось сгенерировать ключ. {}"
     },
     "modals": {
+        "dns_removal_error": "Невозможно удалить DNS записи.",
+        "server_deletion_error": "Невозможно удалить сервер.",
         "server_validators_error": "Не удалось получить список серверов.",
         "already_exists": "Такой сервер уже существует.",
         "unexpected_error": "Непредвиденная ошибка со стороны провайдера.",
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 c7928653..2898b609 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
@@ -431,17 +431,41 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
   }
 
   @override
-  Future<void> deleteServer({
+  Future<APIGenericResult<bool>> deleteServer({
     required final String domainName,
   }) async {
     final Dio client = await getClient();
 
-    final ServerBasicInfo serverToRemove = (await getServers()).firstWhere(
-      (final el) => el.name == domainName,
-    );
-    final ServerVolume volumeToRemove = (await getVolumes()).firstWhere(
-      (final el) => el.serverId == serverToRemove.id,
-    );
+    final servers = await getServers();
+    final ServerBasicInfo serverToRemove;
+    try {
+      serverToRemove = servers.firstWhere(
+        (final el) => el.name == domainName,
+      );
+    } catch (e) {
+      print(e);
+      return APIGenericResult(
+        data: false,
+        success: false,
+        message: e.toString(),
+      );
+    }
+
+    final volumes = await getVolumes();
+    final ServerVolume volumeToRemove;
+    try {
+      volumeToRemove = volumes.firstWhere(
+        (final el) => el.serverId == serverToRemove.id,
+      );
+    } catch (e) {
+      print(e);
+      return APIGenericResult(
+        data: false,
+        success: false,
+        message: e.toString(),
+      );
+    }
+
     final List<Future> laterFutures = <Future>[];
 
     await detachVolume(volumeToRemove);
@@ -453,9 +477,19 @@ class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
       await Future.wait(laterFutures);
     } catch (e) {
       print(e);
+      return APIGenericResult(
+        success: false,
+        data: false,
+        message: e.toString(),
+      );
     } finally {
       close(client);
     }
+
+    return APIGenericResult(
+      success: true,
+      data: true,
+    );
   }
 
   @override
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 f0e032e8..d7a13b95 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
@@ -479,31 +479,46 @@ class HetznerApi extends ServerProviderApi with VolumeProviderApi {
   }
 
   @override
-  Future<void> deleteServer({
+  Future<APIGenericResult<bool>> deleteServer({
     required final String domainName,
   }) async {
     final Dio client = await getClient();
+    try {
+      final String hostname = getHostnameFromDomain(domainName);
 
-    final String hostname = getHostnameFromDomain(domainName);
+      final Response serversReponse = await client.get('/servers');
+      final List servers = serversReponse.data['servers'];
+      final Map server =
+          servers.firstWhere((final el) => el['name'] == hostname);
+      final List volumes = server['volumes'];
+      final List<Future> laterFutures = <Future>[];
 
-    final Response serversReponse = await client.get('/servers');
-    final List servers = serversReponse.data['servers'];
-    final Map server = servers.firstWhere((final el) => el['name'] == hostname);
-    final List volumes = server['volumes'];
-    final List<Future> laterFutures = <Future>[];
+      for (final volumeId in volumes) {
+        await client.post('/volumes/$volumeId/actions/detach');
+      }
+      await Future.delayed(const Duration(seconds: 10));
 
-    for (final volumeId in volumes) {
-      await client.post('/volumes/$volumeId/actions/detach');
+      for (final volumeId in volumes) {
+        laterFutures.add(client.delete('/volumes/$volumeId'));
+      }
+      laterFutures.add(client.delete('/servers/${server['id']}'));
+
+      await Future.wait(laterFutures);
+    } catch (e) {
+      print(e);
+      return APIGenericResult(
+        success: false,
+        data: false,
+        message: e.toString(),
+      );
+    } finally {
+      close(client);
     }
-    await Future.delayed(const Duration(seconds: 10));
 
-    for (final volumeId in volumes) {
-      laterFutures.add(client.delete('/volumes/$volumeId'));
-    }
-    laterFutures.add(client.delete('/servers/${server['id']}'));
-
-    await Future.wait(laterFutures);
-    close(client);
+    return APIGenericResult(
+      success: true,
+      data: true,
+    );
   }
 
   @override
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 05fb5e61..c858d67b 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
@@ -31,7 +31,9 @@ abstract class ServerProviderApi extends ApiMap {
   Future<ServerHostingDetails> restart();
   Future<ServerHostingDetails> powerOn();
 
-  Future<void> deleteServer({required final String domainName});
+  Future<APIGenericResult<bool>> deleteServer({
+    required final String domainName,
+  });
   Future<APIGenericResult<ServerHostingDetails?>> createServer({
     required final String dnsApiToken,
     required final User rootUser,
diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart
index 4da57479..5638b765 100644
--- a/lib/logic/cubit/server_installation/server_installation_cubit.dart
+++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart
@@ -756,7 +756,11 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
     closeTimer();
 
     if (state.serverDetails != null) {
-      await repository.deleteServer(state.serverDomain!);
+      final bool deletionResult =
+          await repository.deleteServer(state.serverDomain!);
+      if (!deletionResult) {
+        return;
+      }
     }
     await repository.deleteServerRelatedRecords();
     emit(
diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart
index f1aeadf5..5d45e7b9 100644
--- a/lib/logic/cubit/server_installation/server_installation_repository.dart
+++ b/lib/logic/cubit/server_installation/server_installation_repository.dart
@@ -759,13 +759,26 @@ class ServerInstallationRepository {
     await box.put(BNames.hasFinalChecked, value);
   }
 
-  Future<void> deleteServer(final ServerDomain serverDomain) async {
-    await ApiController.currentServerProviderApiFactory!
+  Future<bool> deleteServer(final ServerDomain serverDomain) async {
+    final APIGenericResult<bool> deletionResult = await ApiController
+        .currentServerProviderApiFactory!
         .getServerProvider()
         .deleteServer(
           domainName: serverDomain.domainName,
         );
 
+    if (!deletionResult.success) {
+      getIt<NavigationService>()
+          .showSnackBar('modals.server_validators_error'.tr());
+      return false;
+    }
+
+    if (!deletionResult.data) {
+      getIt<NavigationService>()
+          .showSnackBar('modals.server_deletion_error'.tr());
+      return false;
+    }
+
     await box.put(BNames.hasFinalChecked, false);
     await box.put(BNames.isServerStarted, false);
     await box.put(BNames.isServerResetedFirstTime, false);
@@ -773,9 +786,15 @@ class ServerInstallationRepository {
     await box.put(BNames.isLoading, false);
     await box.put(BNames.serverDetails, null);
 
-    await ApiController.currentDnsProviderApiFactory!
+    final APIGenericResult<void> removalResult = await ApiController
+        .currentDnsProviderApiFactory!
         .getDnsProvider()
         .removeSimilarRecords(domain: serverDomain);
+
+    if (!removalResult.success) {
+      getIt<NavigationService>().showSnackBar('modals.dns_removal_error'.tr());
+    }
+    return true;
   }
 
   Future<void> deleteServerRelatedRecords() async {