diff --git a/assets/images/logos/cloudflare.svg b/assets/images/logos/cloudflare.svg
index 7099a7e9..03a60465 100644
--- a/assets/images/logos/cloudflare.svg
+++ b/assets/images/logos/cloudflare.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 653e33f8..b45e6ac2 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -279,6 +279,8 @@
"no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon."
},
"initializing": {
+ "server_provider_description": "A place where your data and SelfPrivacy services will reside:",
+ "dns_provider_description": "A service which lets your IP point towards domain names:",
"connect_to_server": "Let's start with a server.",
"select_provider": "Pick any provider from the following list, they all support SelfPrivacy",
"select_provider_notice": "By 'Relatively small' we mean a machine with 2 cores of CPU and 2 gigabytes of RAM.",
@@ -313,8 +315,10 @@
"choose_server_type_storage": "{} GB of system storage",
"choose_server_type_payment_per_month": "{} per month",
"no_server_types_found": "No available server types found. Make sure your account is accessible and try to change your server location.",
- "cloudflare_bad_key_error": "DNS Provider API key is invalid",
+ "dns_provider_bad_key_error": "API key is invalid",
"backblaze_bad_key_error": "Backblaze storage information is invalid",
+ "connect_to_dns": "Connect the DNS provider",
+ "connect_to_dns_provider_text": "With API token SelfPrivacy will manage all DNS entries",
"select_dns": "Now let's select a DNS provider",
"manage_domain_dns": "To manage your domain's DNS",
"use_this_domain": "Use this domain?",
@@ -444,6 +448,7 @@
"modals": {
"dns_removal_error": "Couldn't remove DNS records.",
"server_deletion_error": "Couldn't delete active server.",
+ "volume_creation_error": "Couldn't create volume.",
"server_validators_error": "Couldn't fetch available servers.",
"already_exists": "Such server already exists.",
"unexpected_error": "Unexpected error during placement from the provider side.",
@@ -488,7 +493,7 @@
"required": "Required",
"already_exist": "Already exists",
"invalid_format": "Invalid format",
- "invalid_format_password": "Must not contain empty characters",
+ "invalid_format_password": "Password must not contain spaces",
"invalid_format_ssh": "Must follow the SSH key format",
"root_name": "Cannot be 'root'",
"length_not_equal": "Length is [], should be {}",
@@ -502,10 +507,12 @@
"subtitle": "These settings are for debugging only. Don't change them unless you know what you're doing.",
"server_setup": "Server setup",
"use_staging_acme": "Use staging ACME server",
- "use_staging_acme_description": "Rebuild your app to change this value.",
+ "use_staging_acme_description": "Applies when setting up a new server.",
+ "ignore_tls": "Do not verify TLS certificates",
+ "ignore_tls_description": "App will not verify TLS certificates when connecting to the server.",
"routing": "App routing",
"reset_onboarding": "Reset onboarding switch",
"reset_onboarding_description": "Reset onboarding switch to show onboarding screen again",
"cubit_statuses": "Cubit loading statuses"
}
-}
\ No newline at end of file
+}
diff --git a/assets/translations/ru.json b/assets/translations/ru.json
index b4bdd938..9b0c93ad 100644
--- a/assets/translations/ru.json
+++ b/assets/translations/ru.json
@@ -273,6 +273,7 @@
"no_ssh_notice": "Для этого пользователя созданы только SSH и Email аккаунты. Единая авторизация для всех сервисов ещё не реализована."
},
"initializing": {
+ "dns_provider_description": "Это позволит связать ваш домен с IP адресом:",
"connect_to_server": "Начнём с сервера.",
"select_provider": "Ниже подборка провайдеров, которых поддерживает SelfPrivacy",
"select_provider_notice": "Под 'Небольшим сервером' имеется ввиду сервер с двумя потоками процессора и двумя гигабайтами оперативной памяти.",
@@ -307,8 +308,10 @@
"choose_server_type_storage": "{} GB системного хранилища",
"choose_server_type_payment_per_month": "{} в месяц",
"no_server_types_found": "Не найдено доступных типов сервера! Пожалуйста, убедитесь, что у вас есть доступ к провайдеру сервера...",
- "cloudflare_bad_key_error": "API ключ неверен",
+ "dns_provider_bad_key_error": "API ключ неверен",
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
+ "connect_to_dns": "Подключите DNS провайдера",
+ "connect_to_dns_provider_text": "С помощью API токена приложение SelfPrivacy настроит DNS записи",
"manage_domain_dns": "Для управления DNS вашего домена",
"use_this_domain": "Используем этот домен?",
"use_this_domain_text": "Указанный вами токен даёт контроль над этим доменом",
@@ -469,10 +472,10 @@
"required": "Обязательное поле",
"already_exist": "Уже существует",
"invalid_format": "Неверный формат",
- "invalid_format_password": "Должен не содержать пустые символы",
+ "invalid_format_password": "Пароль не должен содержать пробелы",
"invalid_format_ssh": "Должен следовать формату SSH ключей",
"root_name": "Имя пользователя не может быть 'root'",
"length_not_equal": "Длина строки [], должна быть равна {}",
"length_longer": "Длина строки [], должна быть меньше либо равна {}"
}
-}
\ No newline at end of file
+}
diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart
index 44b03f26..afaae80b 100644
--- a/lib/config/hive_config.dart
+++ b/lib/config/hive_config.dart
@@ -18,10 +18,9 @@ class HiveConfig {
Hive.registerAdapter(BackblazeCredentialAdapter());
Hive.registerAdapter(BackblazeBucketAdapter());
Hive.registerAdapter(ServerVolumeAdapter());
-
- Hive.registerAdapter(DnsProviderAdapter());
- Hive.registerAdapter(ServerProviderAdapter());
Hive.registerAdapter(UserTypeAdapter());
+ Hive.registerAdapter(DnsProviderTypeAdapter());
+ Hive.registerAdapter(ServerProviderTypeAdapter());
await Hive.openBox(BNames.appSettingsBox);
@@ -35,8 +34,8 @@ class HiveConfig {
final Box deprecatedUsers = Hive.box(BNames.usersDeprecated);
if (deprecatedUsers.isNotEmpty) {
final Box users = Hive.box(BNames.usersBox);
- users.addAll(deprecatedUsers.values.toList());
- deprecatedUsers.clear();
+ await users.addAll(deprecatedUsers.values.toList());
+ await deprecatedUsers.clear();
}
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
diff --git a/lib/logic/api_maps/api_generic_result.dart b/lib/logic/api_maps/generic_result.dart
similarity index 85%
rename from lib/logic/api_maps/api_generic_result.dart
rename to lib/logic/api_maps/generic_result.dart
index 81e1760a..5ce31561 100644
--- a/lib/logic/api_maps/api_generic_result.dart
+++ b/lib/logic/api_maps/generic_result.dart
@@ -1,5 +1,5 @@
-class APIGenericResult {
- APIGenericResult({
+class GenericResult {
+ GenericResult({
required this.success,
required this.data,
this.message,
diff --git a/lib/logic/api_maps/graphql_maps/api_map.dart b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart
similarity index 95%
rename from lib/logic/api_maps/graphql_maps/api_map.dart
rename to lib/logic/api_maps/graphql_maps/graphql_api_map.dart
index 34e39b7a..2c11c127 100644
--- a/lib/logic/api_maps/graphql_maps/api_map.dart
+++ b/lib/logic/api_maps/graphql_maps/graphql_api_map.dart
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:http/io_client.dart';
import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/staging_options.dart';
+import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/models/message.dart';
void _logToAppConsole(final T objectToLog) {
@@ -53,10 +53,10 @@ class ResponseLoggingParser extends ResponseParser {
}
}
-abstract class ApiMap {
+abstract class GraphQLApiMap {
Future getClient() async {
IOClient? ioClient;
- if (StagingOptions.stagingAcme || !StagingOptions.verifyCertificate) {
+ if (TlsOptions.stagingAcme || !TlsOptions.verifyCertificate) {
final HttpClient httpClient = HttpClient();
httpClient.badCertificateCallback = (
final cert,
diff --git a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart
index 84550cc2..fe3ec7f0 100644
--- a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart
@@ -3,6 +3,7 @@ import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart';
import 'schema.graphql.dart';
+import 'services.graphql.dart';
class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({
diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql
index 89bc30c8..a4394ee6 100644
--- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql
+++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql
@@ -76,7 +76,8 @@ type DeviceApiTokenMutationReturn implements MutationReturnInterface {
enum DnsProvider {
CLOUDFLARE,
- DESEC
+ DESEC,
+ DIGITALOCEAN
}
type DnsRecord {
diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
index 9325f5cb..710b305f 100644
--- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart
@@ -1096,7 +1096,7 @@ class _CopyWithStubImpl$Input$UserMutationInput
_res;
}
-enum Enum$DnsProvider { CLOUDFLARE, DESEC, $unknown }
+enum Enum$DnsProvider { CLOUDFLARE, DESEC, DIGITALOCEAN, $unknown }
String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
switch (e) {
@@ -1104,6 +1104,8 @@ String toJson$Enum$DnsProvider(Enum$DnsProvider e) {
return r'CLOUDFLARE';
case Enum$DnsProvider.DESEC:
return r'DESEC';
+ case Enum$DnsProvider.DIGITALOCEAN:
+ return r'DIGITALOCEAN';
case Enum$DnsProvider.$unknown:
return r'$unknown';
}
@@ -1115,6 +1117,8 @@ Enum$DnsProvider fromJson$Enum$DnsProvider(String value) {
return Enum$DnsProvider.CLOUDFLARE;
case r'DESEC':
return Enum$DnsProvider.DESEC;
+ case r'DIGITALOCEAN':
+ return Enum$DnsProvider.DIGITALOCEAN;
default:
return Enum$DnsProvider.$unknown;
}
diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql
index 35df3749..f1012815 100644
--- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql
+++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql
@@ -80,7 +80,6 @@ query SystemDnsProvider {
}
}
-
query GetApiTokens {
api {
devices {
diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart
index f41e841f..a80b783c 100644
--- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart
@@ -1,9 +1,9 @@
import 'dart:async';
-import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart';
import 'schema.graphql.dart';
+import 'services.graphql.dart';
class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({
diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart
index 64738ad8..f834457e 100644
--- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart
@@ -1,8 +1,8 @@
import 'dart:async';
-import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'schema.graphql.dart';
+import 'services.graphql.dart';
class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({
diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart
index 616788d8..d23b0112 100644
--- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'package:selfprivacy/utils/scalars.dart';
diff --git a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart
index 02cde074..d8ef0287 100644
--- a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart
+++ b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart
@@ -1,8 +1,8 @@
import 'dart:async';
-import 'disk_volumes.graphql.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart' as graphql;
import 'schema.graphql.dart';
+import 'services.graphql.dart';
class Fragment$basicMutationReturnFields {
Fragment$basicMutationReturnFields({
diff --git a/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart b/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart
index c14aa98d..8ed73a5d 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/jobs_api.dart
@@ -1,6 +1,6 @@
part of 'server_api.dart';
-mixin JobsApi on ApiMap {
+mixin JobsApi on GraphQLApiMap {
Future> getServerJobs() async {
QueryResult response;
List jobsList = [];
@@ -22,13 +22,13 @@ mixin JobsApi on ApiMap {
return jobsList;
}
- Future> removeApiJob(final String uid) async {
+ Future> removeApiJob(final String uid) async {
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$RemoveJob(jobId: uid);
final mutation = Options$Mutation$RemoveJob(variables: variables);
final response = await client.mutate$RemoveJob(mutation);
- return APIGenericResult(
+ return GenericResult(
data: response.parsedData?.removeJob.success ?? false,
success: true,
code: response.parsedData?.removeJob.code ?? 0,
@@ -36,7 +36,7 @@ mixin JobsApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
code: 0,
diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart
index 65e77b98..f6fd5201 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart
@@ -1,6 +1,6 @@
part of 'server_api.dart';
-mixin ServerActionsApi on ApiMap {
+mixin ServerActionsApi on GraphQLApiMap {
Future _commonBoolRequest(final Function graphQLMethod) async {
QueryResult response;
bool result = false;
diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart
index 51da63ac..91d4eeed 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart
@@ -1,7 +1,7 @@
import 'package:graphql/client.dart';
import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
-import 'package:selfprivacy/logic/api_maps/graphql_maps/api_map.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/graphql_maps/graphql_api_map.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
@@ -24,7 +24,7 @@ import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/ssh_settings.dart';
import 'package:selfprivacy/logic/models/system_settings.dart';
-export 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
+export 'package:selfprivacy/logic/api_maps/generic_result.dart';
part 'jobs_api.dart';
part 'server_actions_api.dart';
@@ -32,7 +32,7 @@ part 'services_api.dart';
part 'users_api.dart';
part 'volume_api.dart';
-class ServerApi extends ApiMap
+class ServerApi extends GraphQLApiMap
with VolumeApi, JobsApi, ServerActionsApi, ServicesApi, UsersApi {
ServerApi({
this.hasLogger = false,
@@ -69,9 +69,9 @@ class ServerApi extends ApiMap
return apiVersion;
}
- Future getServerProviderType() async {
+ Future getServerProviderType() async {
QueryResult response;
- ServerProvider providerType = ServerProvider.unknown;
+ ServerProviderType providerType = ServerProviderType.unknown;
try {
final GraphQLClient client = await getClient();
@@ -79,7 +79,7 @@ class ServerApi extends ApiMap
if (response.hasException) {
print(response.exception.toString());
}
- providerType = ServerProvider.fromGraphQL(
+ providerType = ServerProviderType.fromGraphQL(
response.parsedData!.system.provider.provider,
);
} catch (e) {
@@ -88,9 +88,9 @@ class ServerApi extends ApiMap
return providerType;
}
- Future getDnsProviderType() async {
+ Future getDnsProviderType() async {
QueryResult response;
- DnsProvider providerType = DnsProvider.unknown;
+ DnsProviderType providerType = DnsProviderType.unknown;
try {
final GraphQLClient client = await getClient();
@@ -98,7 +98,7 @@ class ServerApi extends ApiMap
if (response.hasException) {
print(response.exception.toString());
}
- providerType = DnsProvider.fromGraphQL(
+ providerType = DnsProviderType.fromGraphQL(
response.parsedData!.system.domainInfo.provider,
);
} catch (e) {
@@ -205,7 +205,7 @@ class ServerApi extends ApiMap
return settings;
}
- Future> getRecoveryTokenStatus() async {
+ Future> getRecoveryTokenStatus() async {
RecoveryKeyStatus? key;
QueryResult response;
String? error;
@@ -222,18 +222,18 @@ class ServerApi extends ApiMap
print(e);
}
- return APIGenericResult(
+ return GenericResult(
success: error == null,
data: key,
message: error,
);
}
- Future> generateRecoveryToken(
+ Future> generateRecoveryToken(
final DateTime? expirationDate,
final int? numberOfUses,
) async {
- APIGenericResult key;
+ GenericResult key;
QueryResult response;
try {
@@ -254,19 +254,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
- key = APIGenericResult(
+ key = GenericResult(
success: false,
data: '',
message: response.exception.toString(),
);
}
- key = APIGenericResult(
+ key = GenericResult(
success: true,
data: response.parsedData!.getNewRecoveryApiKey.key!,
);
} catch (e) {
print(e);
- key = APIGenericResult(
+ key = GenericResult(
success: false,
data: '',
message: e.toString(),
@@ -299,8 +299,8 @@ class ServerApi extends ApiMap
return records;
}
- Future>> getApiTokens() async {
- APIGenericResult> tokens;
+ Future>> getApiTokens() async {
+ GenericResult> tokens;
QueryResult response;
try {
@@ -309,7 +309,7 @@ class ServerApi extends ApiMap
if (response.hasException) {
final message = response.exception.toString();
print(message);
- tokens = APIGenericResult>(
+ tokens = GenericResult>(
success: false,
data: [],
message: message,
@@ -323,13 +323,13 @@ class ServerApi extends ApiMap
ApiToken.fromGraphQL(device),
)
.toList();
- tokens = APIGenericResult>(
+ tokens = GenericResult>(
success: true,
data: parsed,
);
} catch (e) {
print(e);
- tokens = APIGenericResult>(
+ tokens = GenericResult>(
success: false,
data: [],
message: e.toString(),
@@ -339,8 +339,8 @@ class ServerApi extends ApiMap
return tokens;
}
- Future> deleteApiToken(final String name) async {
- APIGenericResult returnable;
+ Future> deleteApiToken(final String name) async {
+ GenericResult returnable;
QueryResult response;
try {
@@ -357,19 +357,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
- returnable = APIGenericResult(
+ returnable = GenericResult(
success: false,
data: null,
message: response.exception.toString(),
);
}
- returnable = APIGenericResult(
+ returnable = GenericResult(
success: true,
data: null,
);
} catch (e) {
print(e);
- returnable = APIGenericResult(
+ returnable = GenericResult(
success: false,
data: null,
message: e.toString(),
@@ -379,8 +379,8 @@ class ServerApi extends ApiMap
return returnable;
}
- Future> createDeviceToken() async {
- APIGenericResult token;
+ Future> createDeviceToken() async {
+ GenericResult token;
QueryResult response;
try {
@@ -392,19 +392,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: response.exception.toString(),
);
}
- token = APIGenericResult(
+ token = GenericResult(
success: true,
data: response.parsedData!.getNewDeviceApiKey.key!,
);
} catch (e) {
print(e);
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: e.toString(),
@@ -416,10 +416,10 @@ class ServerApi extends ApiMap
Future isHttpServerWorking() async => (await getApiVersion()) != null;
- Future> authorizeDevice(
+ Future> authorizeDevice(
final DeviceToken deviceToken,
) async {
- APIGenericResult token;
+ GenericResult token;
QueryResult response;
try {
@@ -441,19 +441,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: response.exception.toString(),
);
}
- token = APIGenericResult(
+ token = GenericResult(
success: true,
data: response.parsedData!.authorizeWithNewDeviceApiKey.token!,
);
} catch (e) {
print(e);
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: e.toString(),
@@ -463,10 +463,10 @@ class ServerApi extends ApiMap
return token;
}
- Future> useRecoveryToken(
+ Future> useRecoveryToken(
final DeviceToken deviceToken,
) async {
- APIGenericResult token;
+ GenericResult token;
QueryResult response;
try {
@@ -488,19 +488,19 @@ class ServerApi extends ApiMap
);
if (response.hasException) {
print(response.exception.toString());
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: response.exception.toString(),
);
}
- token = APIGenericResult(
+ token = GenericResult(
success: true,
data: response.parsedData!.useRecoveryApiKey.token!,
);
} catch (e) {
print(e);
- token = APIGenericResult(
+ token = GenericResult(
success: false,
data: '',
message: e.toString(),
diff --git a/lib/logic/api_maps/graphql_maps/server_api/services_api.dart b/lib/logic/api_maps/graphql_maps/server_api/services_api.dart
index adfe806f..1632533b 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/services_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/services_api.dart
@@ -1,6 +1,6 @@
part of 'server_api.dart';
-mixin ServicesApi on ApiMap {
+mixin ServicesApi on GraphQLApiMap {
Future> getAllServices() async {
QueryResult response;
List services = [];
@@ -20,7 +20,7 @@ mixin ServicesApi on ApiMap {
return services;
}
- Future> enableService(
+ Future> enableService(
final String serviceId,
) async {
try {
@@ -28,7 +28,7 @@ mixin ServicesApi on ApiMap {
final variables = Variables$Mutation$EnableService(serviceId: serviceId);
final mutation = Options$Mutation$EnableService(variables: variables);
final response = await client.mutate$EnableService(mutation);
- return APIGenericResult(
+ return GenericResult(
data: response.parsedData?.enableService.success ?? false,
success: true,
code: response.parsedData?.enableService.code ?? 0,
@@ -36,7 +36,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
code: 0,
@@ -45,7 +45,7 @@ mixin ServicesApi on ApiMap {
}
}
- Future> disableService(
+ Future> disableService(
final String serviceId,
) async {
try {
@@ -53,7 +53,7 @@ mixin ServicesApi on ApiMap {
final variables = Variables$Mutation$DisableService(serviceId: serviceId);
final mutation = Options$Mutation$DisableService(variables: variables);
final response = await client.mutate$DisableService(mutation);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: response.parsedData?.disableService.success ?? false,
code: response.parsedData?.disableService.code ?? 0,
@@ -61,7 +61,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: false,
code: 0,
@@ -70,7 +70,7 @@ mixin ServicesApi on ApiMap {
}
}
- Future> stopService(
+ Future> stopService(
final String serviceId,
) async {
try {
@@ -78,7 +78,7 @@ mixin ServicesApi on ApiMap {
final variables = Variables$Mutation$StopService(serviceId: serviceId);
final mutation = Options$Mutation$StopService(variables: variables);
final response = await client.mutate$StopService(mutation);
- return APIGenericResult(
+ return GenericResult(
data: response.parsedData?.stopService.success ?? false,
success: true,
code: response.parsedData?.stopService.code ?? 0,
@@ -86,7 +86,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
code: 0,
@@ -95,13 +95,13 @@ mixin ServicesApi on ApiMap {
}
}
- Future startService(final String serviceId) async {
+ Future startService(final String serviceId) async {
try {
final GraphQLClient client = await getClient();
final variables = Variables$Mutation$StartService(serviceId: serviceId);
final mutation = Options$Mutation$StartService(variables: variables);
final response = await client.mutate$StartService(mutation);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: response.parsedData?.startService.success ?? false,
code: response.parsedData?.startService.code ?? 0,
@@ -109,7 +109,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: false,
code: 0,
@@ -118,7 +118,7 @@ mixin ServicesApi on ApiMap {
}
}
- Future> restartService(
+ Future> restartService(
final String serviceId,
) async {
try {
@@ -126,7 +126,7 @@ mixin ServicesApi on ApiMap {
final variables = Variables$Mutation$RestartService(serviceId: serviceId);
final mutation = Options$Mutation$RestartService(variables: variables);
final response = await client.mutate$RestartService(mutation);
- return APIGenericResult(
+ return GenericResult(
data: response.parsedData?.restartService.success ?? false,
success: true,
code: response.parsedData?.restartService.code ?? 0,
@@ -134,7 +134,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
code: 0,
@@ -143,7 +143,7 @@ mixin ServicesApi on ApiMap {
}
}
- Future> moveService(
+ Future> moveService(
final String serviceId,
final String destination,
) async {
@@ -158,7 +158,7 @@ mixin ServicesApi on ApiMap {
final mutation = Options$Mutation$MoveService(variables: variables);
final response = await client.mutate$MoveService(mutation);
final jobJson = response.parsedData?.moveService.job?.toJson();
- return APIGenericResult(
+ return GenericResult(
success: true,
code: response.parsedData?.moveService.code ?? 0,
message: response.parsedData?.moveService.message,
@@ -166,7 +166,7 @@ mixin ServicesApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
success: false,
code: 0,
message: e.toString(),
diff --git a/lib/logic/api_maps/graphql_maps/server_api/users_api.dart b/lib/logic/api_maps/graphql_maps/server_api/users_api.dart
index f1851353..11327290 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/users_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/users_api.dart
@@ -1,6 +1,6 @@
part of 'server_api.dart';
-mixin UsersApi on ApiMap {
+mixin UsersApi on GraphQLApiMap {
Future> getAllUsers() async {
QueryResult response;
List users = [];
@@ -45,7 +45,7 @@ mixin UsersApi on ApiMap {
return user;
}
- Future> createUser(
+ Future> createUser(
final String username,
final String password,
) async {
@@ -56,7 +56,7 @@ mixin UsersApi on ApiMap {
);
final mutation = Options$Mutation$CreateUser(variables: variables);
final response = await client.mutate$CreateUser(mutation);
- return APIGenericResult(
+ return GenericResult(
success: true,
code: response.parsedData?.createUser.code ?? 500,
message: response.parsedData?.createUser.message,
@@ -66,7 +66,7 @@ mixin UsersApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
success: false,
code: 0,
message: e.toString(),
@@ -75,7 +75,7 @@ mixin UsersApi on ApiMap {
}
}
- Future> deleteUser(
+ Future> deleteUser(
final String username,
) async {
try {
@@ -83,7 +83,7 @@ mixin UsersApi on ApiMap {
final variables = Variables$Mutation$DeleteUser(username: username);
final mutation = Options$Mutation$DeleteUser(variables: variables);
final response = await client.mutate$DeleteUser(mutation);
- return APIGenericResult(
+ return GenericResult(
data: response.parsedData?.deleteUser.success ?? false,
success: true,
code: response.parsedData?.deleteUser.code ?? 500,
@@ -91,7 +91,7 @@ mixin UsersApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
code: 500,
@@ -100,7 +100,7 @@ mixin UsersApi on ApiMap {
}
}
- Future> updateUser(
+ Future> updateUser(
final String username,
final String password,
) async {
@@ -111,7 +111,7 @@ mixin UsersApi on ApiMap {
);
final mutation = Options$Mutation$UpdateUser(variables: variables);
final response = await client.mutate$UpdateUser(mutation);
- return APIGenericResult(
+ return GenericResult(
success: true,
code: response.parsedData?.updateUser.code ?? 500,
message: response.parsedData?.updateUser.message,
@@ -121,7 +121,7 @@ mixin UsersApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: false,
code: 0,
@@ -130,7 +130,7 @@ mixin UsersApi on ApiMap {
}
}
- Future> addSshKey(
+ Future> addSshKey(
final String username,
final String sshKey,
) async {
@@ -144,7 +144,7 @@ mixin UsersApi on ApiMap {
);
final mutation = Options$Mutation$AddSshKey(variables: variables);
final response = await client.mutate$AddSshKey(mutation);
- return APIGenericResult(
+ return GenericResult(
success: true,
code: response.parsedData?.addSshKey.code ?? 500,
message: response.parsedData?.addSshKey.message,
@@ -154,7 +154,7 @@ mixin UsersApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: false,
code: 0,
@@ -163,7 +163,7 @@ mixin UsersApi on ApiMap {
}
}
- Future> removeSshKey(
+ Future> removeSshKey(
final String username,
final String sshKey,
) async {
@@ -177,7 +177,7 @@ mixin UsersApi on ApiMap {
);
final mutation = Options$Mutation$RemoveSshKey(variables: variables);
final response = await client.mutate$RemoveSshKey(mutation);
- return APIGenericResult(
+ return GenericResult(
success: response.parsedData?.removeSshKey.success ?? false,
code: response.parsedData?.removeSshKey.code ?? 500,
message: response.parsedData?.removeSshKey.message,
@@ -187,7 +187,7 @@ mixin UsersApi on ApiMap {
);
} catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: null,
success: false,
code: 0,
diff --git a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart
index e830cabd..a7d23ba8 100644
--- a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart
+++ b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart
@@ -1,6 +1,6 @@
part of 'server_api.dart';
-mixin VolumeApi on ApiMap {
+mixin VolumeApi on GraphQLApiMap {
Future> getServerDiskVolumes() async {
QueryResult response;
List volumes = [];
@@ -57,10 +57,10 @@ mixin VolumeApi on ApiMap {
}
}
- Future> migrateToBinds(
+ Future> migrateToBinds(
final Map serviceToDisk,
) async {
- APIGenericResult? mutation;
+ GenericResult? mutation;
try {
final GraphQLClient client = await getClient();
@@ -78,7 +78,7 @@ mixin VolumeApi on ApiMap {
await client.mutate$MigrateToBinds(
migrateMutation,
);
- mutation = mutation = APIGenericResult(
+ mutation = mutation = GenericResult(
success: true,
code: result.parsedData!.migrateToBinds.code,
message: result.parsedData!.migrateToBinds.message,
@@ -86,7 +86,7 @@ mixin VolumeApi on ApiMap {
);
} catch (e) {
print(e);
- mutation = APIGenericResult(
+ mutation = GenericResult(
success: false,
code: 0,
message: e.toString(),
diff --git a/lib/logic/api_maps/rest_maps/api_controller.dart b/lib/logic/api_maps/rest_maps/api_controller.dart
deleted file mode 100644
index 440d25af..00000000
--- a/lib/logic/api_maps/rest_maps/api_controller.dart
+++ /dev/null
@@ -1,44 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
-
-class ApiController {
- static VolumeProviderApiFactory? get currentVolumeProviderApiFactory =>
- _volumeProviderApiFactory;
- static DnsProviderApiFactory? get currentDnsProviderApiFactory =>
- _dnsProviderApiFactory;
- static ServerProviderApiFactory? get currentServerProviderApiFactory =>
- _serverProviderApiFactory;
-
- static void initVolumeProviderApiFactory(
- final ServerProviderApiFactorySettings settings,
- ) {
- _volumeProviderApiFactory =
- VolumeApiFactoryCreator.createVolumeProviderApiFactory(settings);
- }
-
- static void initDnsProviderApiFactory(
- final DnsProviderApiFactorySettings settings,
- ) {
- _dnsProviderApiFactory =
- ApiFactoryCreator.createDnsProviderApiFactory(settings);
- }
-
- static void initServerProviderApiFactory(
- final ServerProviderApiFactorySettings settings,
- ) {
- _serverProviderApiFactory =
- ApiFactoryCreator.createServerProviderApiFactory(settings);
- }
-
- static void clearProviderApiFactories() {
- _volumeProviderApiFactory = null;
- _dnsProviderApiFactory = null;
- _serverProviderApiFactory = null;
- }
-
- static VolumeProviderApiFactory? _volumeProviderApiFactory;
- static DnsProviderApiFactory? _dnsProviderApiFactory;
- static ServerProviderApiFactory? _serverProviderApiFactory;
-}
diff --git a/lib/logic/api_maps/rest_maps/api_factory_creator.dart b/lib/logic/api_maps/rest_maps/api_factory_creator.dart
deleted file mode 100644
index c1762429..00000000
--- a/lib/logic/api_maps/rest_maps/api_factory_creator.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_settings.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
-import 'package:selfprivacy/logic/models/hive/server_details.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
-
-class UnknownApiProviderException implements Exception {
- UnknownApiProviderException(this.message);
- final String message;
-}
-
-class ApiFactoryCreator {
- static ServerProviderApiFactory createServerProviderApiFactory(
- final ServerProviderApiFactorySettings settings,
- ) {
- switch (settings.provider) {
- case ServerProvider.hetzner:
- return HetznerApiFactory(region: settings.location);
- case ServerProvider.digitalOcean:
- return DigitalOceanApiFactory(region: settings.location);
- case ServerProvider.unknown:
- throw UnknownApiProviderException('Unknown server provider');
- }
- }
-
- static DnsProviderApiFactory createDnsProviderApiFactory(
- final DnsProviderApiFactorySettings settings,
- ) {
- switch (settings.provider) {
- case DnsProvider.desec:
- return DesecApiFactory();
- case DnsProvider.cloudflare:
- return CloudflareApiFactory();
- case DnsProvider.unknown:
- throw UnknownApiProviderException('Unknown DNS provider');
- }
- }
-}
-
-class VolumeApiFactoryCreator {
- static VolumeProviderApiFactory createVolumeProviderApiFactory(
- final ServerProviderApiFactorySettings settings,
- ) {
- switch (settings.provider) {
- case ServerProvider.hetzner:
- return HetznerApiFactory();
- case ServerProvider.digitalOcean:
- return DigitalOceanApiFactory();
- case ServerProvider.unknown:
- throw UnknownApiProviderException('Unknown volume provider');
- }
- }
-}
diff --git a/lib/logic/api_maps/rest_maps/backblaze.dart b/lib/logic/api_maps/rest_maps/backblaze.dart
index 59292775..7169f5cb 100644
--- a/lib/logic/api_maps/rest_maps/backblaze.dart
+++ b/lib/logic/api_maps/rest_maps/backblaze.dart
@@ -2,11 +2,11 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
-export 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
+export 'package:selfprivacy/logic/api_maps/generic_result.dart';
class BackblazeApiAuth {
BackblazeApiAuth({required this.authorizationToken, required this.apiUrl});
@@ -25,7 +25,7 @@ class BackblazeApplicationKey {
final String applicationKey;
}
-class BackblazeApi extends ApiMap {
+class BackblazeApi extends RestApiMap {
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
@override
@@ -78,7 +78,7 @@ class BackblazeApi extends ApiMap {
);
}
- Future> isApiTokenValid(
+ Future> isApiTokenValid(
final String encodedApiKey,
) async {
final Dio client = await getClient();
@@ -103,7 +103,7 @@ class BackblazeApi extends ApiMap {
}
} on DioError catch (e) {
print(e);
- return APIGenericResult(
+ return GenericResult(
data: false,
success: false,
message: e.toString(),
@@ -112,7 +112,7 @@ class BackblazeApi extends ApiMap {
close(client);
}
- return APIGenericResult(
+ return GenericResult(
data: isTokenValid,
success: true,
);
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart
deleted file mode 100644
index f594029f..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart
+++ /dev/null
@@ -1,468 +0,0 @@
-import 'dart:io';
-
-import 'package:dio/dio.dart';
-import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
-import 'package:selfprivacy/logic/models/json/dns_records.dart';
-import 'package:selfprivacy/utils/network_utils.dart';
-
-class CloudflareApi extends DnsProviderApi {
- CloudflareApi({
- this.hasLogger = false,
- this.isWithToken = true,
- this.customToken,
- });
- @override
- final bool hasLogger;
- @override
- final bool isWithToken;
-
- final String? customToken;
-
- @override
- RegExp getApiTokenValidation() =>
- RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
-
- @override
- BaseOptions get options {
- final BaseOptions options = BaseOptions(
- baseUrl: rootAddress,
- contentType: Headers.jsonContentType,
- responseType: ResponseType.json,
- );
- if (isWithToken) {
- final String? token = getIt().dnsProviderKey;
- assert(token != null);
- options.headers = {'Authorization': 'Bearer $token'};
- }
-
- if (customToken != null) {
- options.headers = {'Authorization': 'Bearer $customToken'};
- }
-
- if (validateStatus != null) {
- options.validateStatus = validateStatus!;
- }
- return options;
- }
-
- @override
- String rootAddress = 'https://api.cloudflare.com/client/v4';
-
- @override
- Future> isApiTokenValid(final String token) async {
- bool isValid = false;
- Response? response;
- String message = '';
- final Dio client = await getClient();
- try {
- response = await client.get(
- '/user/tokens/verify',
- options: Options(
- followRedirects: false,
- validateStatus: (final status) =>
- status != null && (status >= 200 || status == 401),
- headers: {'Authorization': 'Bearer $token'},
- ),
- );
- } catch (e) {
- print(e);
- isValid = false;
- message = e.toString();
- } finally {
- close(client);
- }
-
- if (response == null) {
- return APIGenericResult(
- data: isValid,
- success: false,
- message: message,
- );
- }
-
- if (response.statusCode == HttpStatus.ok) {
- isValid = true;
- } else if (response.statusCode == HttpStatus.unauthorized) {
- isValid = false;
- } else {
- throw Exception('code: ${response.statusCode}');
- }
-
- return APIGenericResult(
- data: isValid,
- success: true,
- message: response.statusMessage,
- );
- }
-
- @override
- Future getZoneId(final String domain) async {
- String? zoneId;
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get(
- '/zones',
- queryParameters: {'name': domain},
- );
- zoneId = response.data['result'][0]['id'];
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return zoneId;
- }
-
- @override
- Future> removeSimilarRecords({
- required final ServerDomain domain,
- final String? ip4,
- }) async {
- final String domainName = domain.domainName;
- final String domainZoneId = domain.zoneId;
-
- final String url = '/zones/$domainZoneId/dns_records';
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get(url);
-
- final List records = response.data['result'] ?? [];
- final List allDeleteFutures = [];
-
- for (final record in records) {
- if (record['zone_name'] == domainName) {
- allDeleteFutures.add(
- client.delete('$url/${record["id"]}'),
- );
- }
- }
- await Future.wait(allDeleteFutures);
- } catch (e) {
- print(e);
- return APIGenericResult(
- success: false,
- data: null,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(success: true, data: null);
- }
-
- @override
- Future> getDnsRecords({
- required final ServerDomain domain,
- }) async {
- Response response;
- final String domainName = domain.domainName;
- final String domainZoneId = domain.zoneId;
- final List allRecords = [];
-
- final String url = '/zones/$domainZoneId/dns_records';
-
- final Dio client = await getClient();
- try {
- response = await client.get(url);
- final List records = response.data['result'] ?? [];
-
- for (final record in records) {
- if (record['zone_name'] == domainName) {
- allRecords.add(
- DnsRecord(
- name: record['name'],
- type: record['type'],
- content: record['content'],
- ttl: record['ttl'],
- proxied: record['proxied'],
- ),
- );
- }
- }
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return allRecords;
- }
-
- @override
- Future> createMultipleDnsRecords({
- required final ServerDomain domain,
- final String? ip4,
- }) async {
- final String domainName = domain.domainName;
- final String domainZoneId = domain.zoneId;
- final List listDnsRecords = projectDnsRecords(domainName, ip4);
- final List allCreateFutures = [];
-
- final Dio client = await getClient();
- try {
- for (final DnsRecord record in listDnsRecords) {
- allCreateFutures.add(
- client.post(
- '/zones/$domainZoneId/dns_records',
- data: record.toJson(),
- ),
- );
- }
- await Future.wait(allCreateFutures);
- } on DioError catch (e) {
- print(e.message);
- rethrow;
- } catch (e) {
- print(e);
- return APIGenericResult(
- success: false,
- data: null,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(success: true, data: null);
- }
-
- List projectDnsRecords(
- final String? domainName,
- final String? ip4,
- ) {
- final DnsRecord domainA =
- DnsRecord(type: 'A', name: domainName, content: ip4);
-
- final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: domainName);
- final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
- final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
- final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
- final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
- final DnsRecord passwordA =
- DnsRecord(type: 'A', name: 'password', content: ip4);
- final DnsRecord socialA =
- DnsRecord(type: 'A', name: 'social', content: ip4);
- final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
-
- final DnsRecord txt1 = DnsRecord(
- type: 'TXT',
- name: '_dmarc',
- content: 'v=DMARC1; p=none',
- ttl: 18000,
- );
-
- final DnsRecord txt2 = DnsRecord(
- type: 'TXT',
- name: domainName,
- content: 'v=spf1 a mx ip4:$ip4 -all',
- ttl: 18000,
- );
-
- return [
- domainA,
- apiA,
- cloudA,
- gitA,
- meetA,
- passwordA,
- socialA,
- mx,
- txt1,
- txt2,
- vpn
- ];
- }
-
- @override
- Future setDnsRecord(
- final DnsRecord record,
- final ServerDomain domain,
- ) async {
- final String domainZoneId = domain.zoneId;
- final String url = '$rootAddress/zones/$domainZoneId/dns_records';
-
- final Dio client = await getClient();
- try {
- await client.post(
- url,
- data: record.toJson(),
- );
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
- }
-
- @override
- Future> domainList() async {
- final String url = '$rootAddress/zones';
- List domains = [];
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get(
- url,
- queryParameters: {'per_page': 50},
- );
- domains = response.data['result']
- .map((final el) => el['name'] as String)
- .toList();
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return domains;
- }
-
- @override
- Future>> validateDnsRecords(
- final ServerDomain domain,
- final String ip4,
- final String dkimPublicKey,
- ) async {
- final List records = await getDnsRecords(domain: domain);
- final List foundRecords = [];
- try {
- final List desiredRecords =
- getDesiredDnsRecords(domain.domainName, ip4, dkimPublicKey);
- for (final DesiredDnsRecord record in desiredRecords) {
- if (record.description == 'record.dkim') {
- final DnsRecord foundRecord = records.firstWhere(
- (final r) => (r.name == record.name) && r.type == record.type,
- orElse: () => DnsRecord(
- name: record.name,
- type: record.type,
- content: '',
- ttl: 800,
- proxied: false,
- ),
- );
- // remove all spaces and tabulators from
- // the foundRecord.content and the record.content
- // to compare them
- final String? foundContent =
- foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
- final String content = record.content.replaceAll(RegExp(r'\s+'), '');
- if (foundContent == content) {
- foundRecords.add(record.copyWith(isSatisfied: true));
- } else {
- foundRecords.add(record.copyWith(isSatisfied: false));
- }
- } else {
- if (records.any(
- (final r) =>
- (r.name == record.name) &&
- r.type == record.type &&
- r.content == record.content,
- )) {
- foundRecords.add(record.copyWith(isSatisfied: true));
- } else {
- foundRecords.add(record.copyWith(isSatisfied: false));
- }
- }
- }
- } catch (e) {
- print(e);
- return APIGenericResult(
- data: [],
- success: false,
- message: e.toString(),
- );
- }
- return APIGenericResult(
- data: foundRecords,
- success: true,
- );
- }
-
- @override
- List getDesiredDnsRecords(
- final String? domainName,
- final String? ip4,
- final String? dkimPublicKey,
- ) {
- if (domainName == null || ip4 == null) {
- return [];
- }
- return [
- DesiredDnsRecord(
- name: domainName,
- content: ip4,
- description: 'record.root',
- ),
- DesiredDnsRecord(
- name: 'api.$domainName',
- content: ip4,
- description: 'record.api',
- ),
- DesiredDnsRecord(
- name: 'cloud.$domainName',
- content: ip4,
- description: 'record.cloud',
- ),
- DesiredDnsRecord(
- name: 'git.$domainName',
- content: ip4,
- description: 'record.git',
- ),
- DesiredDnsRecord(
- name: 'meet.$domainName',
- content: ip4,
- description: 'record.meet',
- ),
- DesiredDnsRecord(
- name: 'social.$domainName',
- content: ip4,
- description: 'record.social',
- ),
- DesiredDnsRecord(
- name: 'password.$domainName',
- content: ip4,
- description: 'record.password',
- ),
- DesiredDnsRecord(
- name: 'vpn.$domainName',
- content: ip4,
- description: 'record.vpn',
- ),
- DesiredDnsRecord(
- name: domainName,
- content: domainName,
- description: 'record.mx',
- type: 'MX',
- category: DnsRecordsCategory.email,
- ),
- DesiredDnsRecord(
- name: '_dmarc.$domainName',
- content: 'v=DMARC1; p=none',
- description: 'record.dmarc',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- DesiredDnsRecord(
- name: domainName,
- content: 'v=spf1 a mx ip4:$ip4 -all',
- description: 'record.spf',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- if (dkimPublicKey != null)
- DesiredDnsRecord(
- name: 'selector._domainkey.$domainName',
- content: dkimPublicKey,
- description: 'record.dkim',
- type: 'TXT',
- category: DnsRecordsCategory.email,
- ),
- ];
- }
-}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart
new file mode 100644
index 00000000..9fe74841
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart
@@ -0,0 +1,252 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:selfprivacy/config/get_it_config.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
+import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+import 'package:selfprivacy/logic/models/json/dns_records.dart';
+
+class CloudflareApi extends RestApiMap {
+ CloudflareApi({
+ this.hasLogger = false,
+ this.isWithToken = true,
+ this.customToken,
+ });
+ @override
+ final bool hasLogger;
+ @override
+ final bool isWithToken;
+
+ final String? customToken;
+
+ @override
+ BaseOptions get options {
+ final BaseOptions options = BaseOptions(
+ baseUrl: rootAddress,
+ contentType: Headers.jsonContentType,
+ responseType: ResponseType.json,
+ );
+ if (isWithToken) {
+ final String? token = getIt().dnsProviderKey;
+ assert(token != null);
+ options.headers = {'Authorization': 'Bearer $token'};
+ }
+
+ if (customToken != null) {
+ options.headers = {'Authorization': 'Bearer $customToken'};
+ }
+
+ if (validateStatus != null) {
+ options.validateStatus = validateStatus!;
+ }
+ return options;
+ }
+
+ @override
+ String rootAddress = 'https://api.cloudflare.com/client/v4';
+
+ Future> isApiTokenValid(final String token) async {
+ bool isValid = false;
+ Response? response;
+ String message = '';
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '/user/tokens/verify',
+ options: Options(
+ followRedirects: false,
+ validateStatus: (final status) =>
+ status != null && (status >= 200 || status == 401),
+ headers: {'Authorization': 'Bearer $token'},
+ ),
+ );
+ } catch (e) {
+ print(e);
+ isValid = false;
+ message = e.toString();
+ } finally {
+ close(client);
+ }
+
+ if (response == null) {
+ return GenericResult(
+ data: isValid,
+ success: false,
+ message: message,
+ );
+ }
+
+ if (response.statusCode == HttpStatus.ok) {
+ isValid = true;
+ } else if (response.statusCode == HttpStatus.unauthorized) {
+ isValid = false;
+ } else {
+ throw Exception('code: ${response.statusCode}');
+ }
+
+ return GenericResult(
+ data: isValid,
+ success: true,
+ message: response.statusMessage,
+ );
+ }
+
+ Future>> getZones(final String domain) async {
+ List zones = [];
+
+ late final Response? response;
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '/zones',
+ queryParameters: {'name': domain},
+ );
+ zones = response.data['result'];
+ } catch (e) {
+ print(e);
+ GenericResult(
+ success: false,
+ data: zones,
+ code: response?.statusCode,
+ message: response?.statusMessage,
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: zones);
+ }
+
+ Future> removeSimilarRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainZoneId = domain.zoneId;
+ final String url = '/zones/$domainZoneId/dns_records';
+
+ final Dio client = await getClient();
+ try {
+ final List allDeleteFutures = [];
+
+ for (final record in records) {
+ allDeleteFutures.add(
+ client.delete('$url/${record["id"]}'),
+ );
+ }
+ await Future.wait(allDeleteFutures);
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> getDnsRecords({
+ required final ServerDomain domain,
+ }) async {
+ Response response;
+ final String domainName = domain.domainName;
+ final String domainZoneId = domain.zoneId;
+ final List allRecords = [];
+
+ final String url = '/zones/$domainZoneId/dns_records';
+
+ final Dio client = await getClient();
+ try {
+ response = await client.get(url);
+ final List records = response.data['result'] ?? [];
+
+ for (final record in records) {
+ if (record['zone_name'] == domainName) {
+ allRecords.add(record);
+ }
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: allRecords, success: true);
+ }
+
+ Future> createMultipleDnsRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainZoneId = domain.zoneId;
+ final List allCreateFutures = [];
+
+ final Dio client = await getClient();
+ try {
+ for (final DnsRecord record in records) {
+ allCreateFutures.add(
+ client.post(
+ '/zones/$domainZoneId/dns_records',
+ data: record.toJson(),
+ ),
+ );
+ }
+ await Future.wait(allCreateFutures);
+ } on DioError catch (e) {
+ print(e.message);
+ rethrow;
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> getDomains() async {
+ final String url = '$rootAddress/zones';
+ List domains = [];
+
+ late final Response? response;
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ url,
+ queryParameters: {'per_page': 50},
+ );
+ domains = response.data['result'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: domains,
+ code: response?.statusCode,
+ message: response?.statusMessage,
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(
+ success: true,
+ data: domains,
+ code: response.statusCode,
+ message: response.statusMessage,
+ );
+ }
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart
deleted file mode 100644
index ccb58e6a..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_factory.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
-
-class CloudflareApiFactory extends DnsProviderApiFactory {
- @override
- DnsProviderApi getDnsProvider({
- final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
- }) =>
- CloudflareApi(
- hasLogger: settings.hasLogger,
- isWithToken: settings.isWithToken,
- customToken: settings.customToken,
- );
-}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart
new file mode 100644
index 00000000..e5eff146
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_api.dart
@@ -0,0 +1,204 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:selfprivacy/config/get_it_config.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
+import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+
+class DesecApi extends RestApiMap {
+ DesecApi({
+ this.hasLogger = false,
+ this.isWithToken = true,
+ this.customToken,
+ });
+ @override
+ final bool hasLogger;
+ @override
+ final bool isWithToken;
+
+ final String? customToken;
+
+ @override
+ BaseOptions get options {
+ final BaseOptions options = BaseOptions(
+ baseUrl: rootAddress,
+ contentType: Headers.jsonContentType,
+ responseType: ResponseType.json,
+ );
+ if (isWithToken) {
+ final String? token = getIt().dnsProviderKey;
+ assert(token != null);
+ options.headers = {'Authorization': 'Token $token'};
+ }
+
+ if (customToken != null) {
+ options.headers = {'Authorization': 'Token $customToken'};
+ }
+
+ if (validateStatus != null) {
+ options.validateStatus = validateStatus!;
+ }
+ return options;
+ }
+
+ @override
+ String rootAddress = 'https://desec.io/api/v1/domains/';
+
+ Future> isApiTokenValid(final String token) async {
+ bool isValid = false;
+ Response? response;
+ String message = '';
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '',
+ options: Options(
+ followRedirects: false,
+ validateStatus: (final status) =>
+ status != null && (status >= 200 || status == 401),
+ headers: {'Authorization': 'Token $token'},
+ ),
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ isValid = false;
+ message = e.toString();
+ } finally {
+ close(client);
+ }
+
+ if (response == null) {
+ return GenericResult(
+ data: isValid,
+ success: false,
+ message: message,
+ );
+ }
+
+ if (response.statusCode == HttpStatus.ok) {
+ isValid = true;
+ } else if (response.statusCode == HttpStatus.unauthorized) {
+ isValid = false;
+ } else {
+ throw Exception('code: ${response.statusCode}');
+ }
+
+ return GenericResult(
+ data: isValid,
+ success: true,
+ message: response.statusMessage,
+ );
+ }
+
+ Future> updateRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainName = domain.domainName;
+ final String url = '/$domainName/rrsets/';
+
+ final Dio client = await getClient();
+ try {
+ await client.put(url, data: records);
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future>> getDnsRecords({
+ required final ServerDomain domain,
+ }) async {
+ Response? response;
+ final String domainName = domain.domainName;
+ List allRecords = [];
+
+ final String url = '/$domainName/rrsets/';
+
+ final Dio client = await getClient();
+ try {
+ response = await client.get(url);
+ await Future.delayed(const Duration(seconds: 1));
+ allRecords = response.data;
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: allRecords,
+ success: false,
+ message: e.toString(),
+ code: response?.statusCode,
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: allRecords, success: true);
+ }
+
+ Future> createRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainName = domain.domainName;
+ final String url = '/$domainName/rrsets/';
+
+ final Dio client = await getClient();
+ try {
+ await client.post(url, data: records);
+ await Future.delayed(const Duration(seconds: 1));
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> getDomains() async {
+ List domains = [];
+
+ late final Response? response;
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '',
+ );
+ await Future.delayed(const Duration(seconds: 1));
+ domains = response.data;
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: domains,
+ code: response?.statusCode,
+ message: response?.statusMessage,
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(
+ success: true,
+ data: domains,
+ code: response.statusCode,
+ message: response.statusMessage,
+ );
+ }
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart
deleted file mode 100644
index 6c10259b..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/desec/desec_factory.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desec/desec.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
-
-class DesecApiFactory extends DnsProviderApiFactory {
- @override
- DnsProviderApi getDnsProvider({
- final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
- }) =>
- DesecApi(
- hasLogger: settings.hasLogger,
- isWithToken: settings.isWithToken,
- customToken: settings.customToken,
- );
-}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart b/lib/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart
new file mode 100644
index 00000000..4a64d49e
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart
@@ -0,0 +1,44 @@
+enum DnsRecordsCategory {
+ services,
+ email,
+ other,
+}
+
+class DesiredDnsRecord {
+ const DesiredDnsRecord({
+ required this.name,
+ required this.content,
+ this.type = 'A',
+ this.description = '',
+ this.category = DnsRecordsCategory.services,
+ this.isSatisfied = false,
+ this.displayName,
+ });
+
+ final String name;
+ final String type;
+ final String content;
+ final String description;
+ final String? displayName;
+ final DnsRecordsCategory category;
+ final bool isSatisfied;
+
+ DesiredDnsRecord copyWith({
+ final String? name,
+ final String? type,
+ final String? content,
+ final String? description,
+ final String? displayName,
+ final DnsRecordsCategory? category,
+ final bool? isSatisfied,
+ }) =>
+ DesiredDnsRecord(
+ name: name ?? this.name,
+ type: type ?? this.type,
+ content: content ?? this.content,
+ description: description ?? this.description,
+ category: category ?? this.category,
+ isSatisfied: isSatisfied ?? this.isSatisfied,
+ displayName: displayName ?? this.displayName,
+ );
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns_api.dart
new file mode 100644
index 00000000..348edc77
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns_api.dart
@@ -0,0 +1,217 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:selfprivacy/config/get_it_config.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
+import 'package:selfprivacy/logic/models/hive/server_domain.dart';
+import 'package:selfprivacy/logic/models/json/dns_records.dart';
+
+class DigitalOceanDnsApi extends RestApiMap {
+ DigitalOceanDnsApi({
+ this.hasLogger = false,
+ this.isWithToken = true,
+ this.customToken,
+ });
+ @override
+ final bool hasLogger;
+ @override
+ final bool isWithToken;
+
+ final String? customToken;
+
+ @override
+ BaseOptions get options {
+ final BaseOptions options = BaseOptions(
+ baseUrl: rootAddress,
+ contentType: Headers.jsonContentType,
+ responseType: ResponseType.json,
+ );
+ if (isWithToken) {
+ final String? token = getIt().dnsProviderKey;
+ assert(token != null);
+ options.headers = {'Authorization': 'Bearer $token'};
+ }
+
+ if (customToken != null) {
+ options.headers = {'Authorization': 'Bearer $customToken'};
+ }
+
+ if (validateStatus != null) {
+ options.validateStatus = validateStatus!;
+ }
+ return options;
+ }
+
+ @override
+ String rootAddress = 'https://api.digitalocean.com/v2';
+
+ Future> isApiTokenValid(final String token) async {
+ bool isValid = false;
+ Response? response;
+ String message = '';
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '/account',
+ options: Options(
+ followRedirects: false,
+ validateStatus: (final status) =>
+ status != null && (status >= 200 || status == 401),
+ headers: {'Authorization': 'Bearer $token'},
+ ),
+ );
+ } catch (e) {
+ print(e);
+ isValid = false;
+ message = e.toString();
+ } finally {
+ close(client);
+ }
+
+ if (response == null) {
+ return GenericResult(
+ data: isValid,
+ success: false,
+ message: message,
+ );
+ }
+
+ if (response.statusCode == HttpStatus.ok) {
+ isValid = true;
+ } else if (response.statusCode == HttpStatus.unauthorized) {
+ isValid = false;
+ } else {
+ throw Exception('code: ${response.statusCode}');
+ }
+
+ return GenericResult(
+ data: isValid,
+ success: true,
+ message: response.statusMessage,
+ );
+ }
+
+ Future> removeSimilarRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainName = domain.domainName;
+
+ final Dio client = await getClient();
+ try {
+ final List allDeleteFutures = [];
+ for (final record in records) {
+ allDeleteFutures.add(
+ client.delete("/domains/$domainName/records/${record['id']}"),
+ );
+ }
+ await Future.wait(allDeleteFutures);
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> getDnsRecords({
+ required final ServerDomain domain,
+ }) async {
+ Response response;
+ final String domainName = domain.domainName;
+ List allRecords = [];
+
+ /// Default amount is 20, but we will eventually overflow it,
+ /// so I hardcode it to the maximum available amount in advance just in case
+ ///
+ /// https://docs.digitalocean.com/reference/api/api-reference/#operation/domains_list_records
+ const int amountPerPage = 200;
+ final String url = '/domains/$domainName/records?per_page=$amountPerPage';
+
+ final Dio client = await getClient();
+ try {
+ response = await client.get(url);
+ allRecords = response.data['domain_records'] ?? [];
+ } catch (e) {
+ print(e);
+ GenericResult(
+ data: allRecords,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: allRecords, success: true);
+ }
+
+ Future> createMultipleDnsRecords({
+ required final ServerDomain domain,
+ required final List records,
+ }) async {
+ final String domainName = domain.domainName;
+ final List allCreateFutures = [];
+
+ final Dio client = await getClient();
+ try {
+ for (final DnsRecord record in records) {
+ allCreateFutures.add(
+ client.post(
+ '/domains/$domainName/records',
+ data: {
+ 'type': record.type,
+ 'name': record.name,
+ 'data': record.content,
+ 'ttl': record.ttl,
+ 'priority': record.priority,
+ },
+ ),
+ );
+ }
+ await Future.wait(allCreateFutures);
+ } on DioError catch (e) {
+ print(e.message);
+ rethrow;
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> domainList() async {
+ List domains = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get('/domains');
+ domains = response.data['domains'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: domains,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: domains, success: true);
+ }
+}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart
deleted file mode 100644
index b85f94d1..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart
+++ /dev/null
@@ -1,44 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
-import 'package:selfprivacy/logic/models/json/dns_records.dart';
-import 'package:selfprivacy/utils/network_utils.dart';
-
-export 'package:selfprivacy/logic/api_maps/api_generic_result.dart';
-
-class DomainNotFoundException implements Exception {
- DomainNotFoundException(this.message);
- final String message;
-}
-
-abstract class DnsProviderApi extends ApiMap {
- Future> getDnsRecords({
- required final ServerDomain domain,
- });
- Future> removeSimilarRecords({
- required final ServerDomain domain,
- final String? ip4,
- });
- Future> createMultipleDnsRecords({
- required final ServerDomain domain,
- final String? ip4,
- });
- Future setDnsRecord(
- final DnsRecord record,
- final ServerDomain domain,
- );
- Future>> validateDnsRecords(
- final ServerDomain domain,
- final String ip4,
- final String dkimPublicKey,
- );
- List getDesiredDnsRecords(
- final String? domainName,
- final String? ip4,
- final String? dkimPublicKey,
- );
- Future getZoneId(final String domain);
- Future> domainList();
- Future> isApiTokenValid(final String token);
- RegExp getApiTokenValidation();
-}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart
deleted file mode 100644
index 6b737df5..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
-
-class DnsProviderApiSettings extends ProviderApiSettings {
- const DnsProviderApiSettings({
- super.hasLogger = false,
- super.isWithToken = true,
- this.customToken,
- });
- final String? customToken;
-}
diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart
deleted file mode 100644
index fb573135..00000000
--- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_api_settings.dart';
-
-abstract class DnsProviderApiFactory {
- DnsProviderApi getDnsProvider({
- final DnsProviderApiSettings settings,
- });
-}
diff --git a/lib/logic/api_maps/rest_maps/provider_api_settings.dart b/lib/logic/api_maps/rest_maps/provider_api_settings.dart
deleted file mode 100644
index 9e601d2a..00000000
--- a/lib/logic/api_maps/rest_maps/provider_api_settings.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-class ProviderApiSettings {
- const ProviderApiSettings({
- this.hasLogger = false,
- this.isWithToken = true,
- });
- final bool hasLogger;
- final bool isWithToken;
-}
diff --git a/lib/logic/api_maps/rest_maps/api_map.dart b/lib/logic/api_maps/rest_maps/rest_api_map.dart
similarity index 99%
rename from lib/logic/api_maps/rest_maps/api_map.dart
rename to lib/logic/api_maps/rest_maps/rest_api_map.dart
index 86f53e25..547ce4aa 100644
--- a/lib/logic/api_maps/rest_maps/api_map.dart
+++ b/lib/logic/api_maps/rest_maps/rest_api_map.dart
@@ -8,7 +8,7 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/message.dart';
-abstract class ApiMap {
+abstract class RestApiMap {
Future getClient({final BaseOptions? customOptions}) async {
final Dio dio = Dio(customOptions ?? (await options));
if (hasLogger) {
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
deleted file mode 100644
index 81b7a9fb..00000000
--- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart
+++ /dev/null
@@ -1,866 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:dio/dio.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
-import 'package:selfprivacy/logic/api_maps/staging_options.dart';
-import 'package:selfprivacy/logic/models/disk_size.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
-import 'package:selfprivacy/logic/models/hive/server_details.dart';
-import 'package:selfprivacy/logic/models/hive/user.dart';
-import 'package:selfprivacy/logic/models/metrics.dart';
-import 'package:selfprivacy/logic/models/price.dart';
-import 'package:selfprivacy/logic/models/server_basic_info.dart';
-import 'package:selfprivacy/logic/models/server_metadata.dart';
-import 'package:selfprivacy/logic/models/server_provider_location.dart';
-import 'package:selfprivacy/logic/models/server_type.dart';
-import 'package:selfprivacy/utils/extensions/string_extensions.dart';
-import 'package:selfprivacy/utils/network_utils.dart';
-import 'package:selfprivacy/utils/password_generator.dart';
-
-class DigitalOceanApi extends ServerProviderApi with VolumeProviderApi {
- DigitalOceanApi({
- required this.region,
- this.hasLogger = true,
- this.isWithToken = true,
- });
- @override
- bool hasLogger;
- @override
- bool isWithToken;
-
- final String? region;
-
- @override
- BaseOptions get options {
- final BaseOptions options = BaseOptions(
- baseUrl: rootAddress,
- contentType: Headers.jsonContentType,
- responseType: ResponseType.json,
- );
- if (isWithToken) {
- final String? token = getIt().serverProviderKey;
- assert(token != null);
- options.headers = {'Authorization': 'Bearer $token'};
- }
-
- if (validateStatus != null) {
- options.validateStatus = validateStatus!;
- }
-
- return options;
- }
-
- @override
- String get rootAddress => 'https://api.digitalocean.com/v2';
-
- @override
- String get infectProviderName => 'digitalocean';
-
- @override
- String get displayProviderName => 'Digital Ocean';
-
- @override
- Future> isApiTokenValid(final String token) async {
- bool isValid = false;
- Response? response;
- String message = '';
- final Dio client = await getClient();
- try {
- response = await client.get(
- '/account',
- options: Options(
- followRedirects: false,
- validateStatus: (final status) =>
- status != null && (status >= 200 || status == 401),
- headers: {'Authorization': 'Bearer $token'},
- ),
- );
- } catch (e) {
- print(e);
- isValid = false;
- message = e.toString();
- } finally {
- close(client);
- }
-
- if (response == null) {
- return APIGenericResult(
- data: isValid,
- success: false,
- message: message,
- );
- }
-
- if (response.statusCode == HttpStatus.ok) {
- isValid = true;
- } else if (response.statusCode == HttpStatus.unauthorized) {
- isValid = false;
- } else {
- throw Exception('code: ${response.statusCode}');
- }
-
- return APIGenericResult(
- data: isValid,
- success: true,
- message: response.statusMessage,
- );
- }
-
- /// Hardcoded on their documentation and there is no pricing API at all
- /// Probably we should scrap the doc page manually
- @override
- Future getPricePerGb() async => Price(
- value: 0.10,
- currency: 'USD',
- );
-
- @override
- Future> createVolume() async {
- ServerVolume? volume;
-
- Response? createVolumeResponse;
- final Dio client = await getClient();
- try {
- final List volumes = await getVolumes();
- await Future.delayed(const Duration(seconds: 6));
-
- createVolumeResponse = await client.post(
- '/volumes',
- data: {
- 'size_gigabytes': 10,
- 'name': 'volume${StringGenerators.storageName()}',
- 'labels': {'labelkey': 'value'},
- 'region': region,
- 'filesystem_type': 'ext4',
- },
- );
- final volumeId = createVolumeResponse.data['volume']['id'];
- final volumeSize = createVolumeResponse.data['volume']['size_gigabytes'];
- final volumeName = createVolumeResponse.data['volume']['name'];
- volume = ServerVolume(
- id: volumes.length,
- name: volumeName,
- sizeByte: volumeSize,
- serverId: null,
- linuxDevice: '/dev/disk/by-id/scsi-0DO_Volume_$volumeName',
- uuid: volumeId,
- );
- } catch (e) {
- print(e);
- return APIGenericResult(
- data: null,
- success: false,
- message: e.toString(),
- );
- } finally {
- client.close();
- }
-
- return APIGenericResult(
- data: volume,
- success: true,
- code: createVolumeResponse.statusCode,
- message: createVolumeResponse.statusMessage,
- );
- }
-
- @override
- Future> getVolumes({final String? status}) async {
- final List volumes = [];
-
- final Response getVolumesResponse;
- final Dio client = await getClient();
- try {
- getVolumesResponse = await client.get(
- '/volumes',
- queryParameters: {
- 'status': status,
- },
- );
- final List rawVolumes = getVolumesResponse.data['volumes'];
- int id = 0;
- for (final rawVolume in rawVolumes) {
- final volumeId = rawVolume['id'];
- final int volumeSize = rawVolume['size_gigabytes'] * 1024 * 1024 * 1024;
- final volumeDropletIds = rawVolume['droplet_ids'];
- final String volumeName = rawVolume['name'];
- final volume = ServerVolume(
- id: id++,
- name: volumeName,
- sizeByte: volumeSize,
- serverId: volumeDropletIds.isNotEmpty ? volumeDropletIds[0] : null,
- linuxDevice: 'scsi-0DO_Volume_$volumeName',
- uuid: volumeId,
- );
- volumes.add(volume);
- }
- } catch (e) {
- print(e);
- } finally {
- client.close();
- }
-
- return volumes;
- }
-
- Future getVolume(final String volumeUuid) async {
- ServerVolume? requestedVolume;
-
- final List volumes = await getVolumes();
-
- for (final volume in volumes) {
- if (volume.uuid == volumeUuid) {
- requestedVolume = volume;
- }
- }
-
- return requestedVolume;
- }
-
- @override
- Future deleteVolume(final ServerVolume volume) async {
- final Dio client = await getClient();
- try {
- await client.delete('/volumes/${volume.uuid}');
- } catch (e) {
- print(e);
- } finally {
- client.close();
- }
- }
-
- @override
- Future> attachVolume(
- final ServerVolume volume,
- final int serverId,
- ) async {
- bool success = false;
-
- Response? attachVolumeResponse;
- final Dio client = await getClient();
- try {
- attachVolumeResponse = await client.post(
- '/volumes/actions',
- data: {
- 'type': 'attach',
- 'volume_name': volume.name,
- 'region': region,
- 'droplet_id': serverId,
- },
- );
- success =
- attachVolumeResponse.data['action']['status'].toString() != 'error';
- } catch (e) {
- print(e);
- return APIGenericResult(
- data: false,
- success: false,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(
- data: success,
- success: true,
- code: attachVolumeResponse.statusCode,
- message: attachVolumeResponse.statusMessage,
- );
- }
-
- @override
- Future detachVolume(final ServerVolume volume) async {
- bool success = false;
-
- final Response detachVolumeResponse;
- final Dio client = await getClient();
- try {
- detachVolumeResponse = await client.post(
- '/volumes/actions',
- data: {
- 'type': 'detach',
- 'volume_name': volume.name,
- 'droplet_id': volume.serverId,
- 'region': region,
- },
- );
- success =
- detachVolumeResponse.data['action']['status'].toString() != 'error';
- } catch (e) {
- print(e);
- } finally {
- client.close();
- }
-
- return success;
- }
-
- @override
- Future resizeVolume(
- final ServerVolume volume,
- final DiskSize size,
- ) async {
- bool success = false;
-
- final Response resizeVolumeResponse;
- final Dio client = await getClient();
- try {
- resizeVolumeResponse = await client.post(
- '/volumes/actions',
- data: {
- 'type': 'resize',
- 'volume_name': volume.name,
- 'size_gigabytes': size.gibibyte,
- 'region': region,
- },
- );
- success =
- resizeVolumeResponse.data['action']['status'].toString() != 'error';
- } catch (e) {
- print(e);
- } finally {
- client.close();
- }
-
- return success;
- }
-
- @override
- Future> createServer({
- required final String dnsApiToken,
- required final User rootUser,
- required final String domainName,
- required final String serverType,
- required final DnsProvider dnsProvider,
- }) async {
- ServerHostingDetails? serverDetails;
-
- final String dbPassword = StringGenerators.dbPassword();
- final String apiToken = StringGenerators.apiToken();
-
- final String base64Password =
- base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
-
- final String formattedHostname = getHostnameFromDomain(domainName);
- const String infectBranch = 'providers/digital-ocean';
- final String stagingAcme = StagingOptions.stagingAcme ? 'true' : 'false';
- final String dnsProviderType = dnsProviderToInfectName(dnsProvider);
-
- final String userdataString =
- "#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/$infectBranch/nixos-infect | DNS_PROVIDER_TYPE=$dnsProviderType PROVIDER=$infectProviderName STAGING_ACME='$stagingAcme' DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$formattedHostname bash 2>&1 | tee /tmp/infect.log";
- print(userdataString);
-
- Response? serverCreateResponse;
- final Dio client = await getClient();
- try {
- final Map data = {
- 'name': formattedHostname,
- 'size': serverType,
- 'image': 'ubuntu-20-04-x64',
- 'user_data': userdataString,
- 'region': region!,
- };
- print('Decoded data: $data');
-
- serverCreateResponse = await client.post(
- '/droplets',
- data: data,
- );
-
- final int serverId = serverCreateResponse.data['droplet']['id'];
- final ServerVolume? newVolume = (await createVolume()).data;
- final bool attachedVolume =
- (await attachVolume(newVolume!, serverId)).data;
-
- String? ipv4;
- int attempts = 0;
- while (attempts < 5 && ipv4 == null) {
- await Future.delayed(const Duration(seconds: 20));
- final List servers = await getServers();
- for (final server in servers) {
- if (server.name == formattedHostname && server.ip != '0.0.0.0') {
- ipv4 = server.ip;
- break;
- }
- }
- ++attempts;
- }
-
- if (attachedVolume && ipv4 != null) {
- serverDetails = ServerHostingDetails(
- id: serverId,
- ip4: ipv4,
- createTime: DateTime.now(),
- volume: newVolume,
- apiToken: apiToken,
- provider: ServerProvider.digitalOcean,
- );
- }
- } catch (e) {
- print(e);
- return APIGenericResult(
- success: false,
- data: null,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(
- data: serverDetails,
- success: true,
- code: serverCreateResponse.statusCode,
- message: serverCreateResponse.statusMessage,
- );
- }
-
- @override
- Future> deleteServer({
- required final String domainName,
- }) async {
- final Dio client = await getClient();
-
- final String hostname = getHostnameFromDomain(domainName);
- final servers = await getServers();
- final ServerBasicInfo serverToRemove;
- try {
- serverToRemove = servers.firstWhere(
- (final el) => el.name == hostname,
- );
- } 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 laterFutures = [];
-
- await detachVolume(volumeToRemove);
- await Future.delayed(const Duration(seconds: 10));
-
- try {
- laterFutures.add(deleteVolume(volumeToRemove));
- laterFutures.add(client.delete('/droplets/${serverToRemove.id}'));
- 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
- Future restart() async {
- final ServerHostingDetails server = getIt().serverDetails!;
-
- final Dio client = await getClient();
- try {
- await client.post(
- '/droplets/${server.id}/actions',
- data: {
- 'type': 'reboot',
- },
- );
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return server.copyWith(startTime: DateTime.now());
- }
-
- @override
- Future powerOn() async {
- final ServerHostingDetails server = getIt().serverDetails!;
-
- final Dio client = await getClient();
- try {
- await client.post(
- '/droplets/${server.id}/actions',
- data: {
- 'type': 'power_on',
- },
- );
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return server.copyWith(startTime: DateTime.now());
- }
-
- /// Digital Ocean returns a map of lists of /proc/stat values,
- /// so here we are trying to implement average CPU
- /// load calculation for each point in time on a given interval.
- ///
- /// For each point of time:
- ///
- /// `Average Load = 100 * (1 - (Idle Load / Total Load))`
- ///
- /// For more info please proceed to read:
- /// https://rosettacode.org/wiki/Linux_CPU_utilization
- List calculateCpuLoadMetrics(final List rawProcStatMetrics) {
- final List cpuLoads = [];
-
- final int pointsInTime = (rawProcStatMetrics[0]['values'] as List).length;
- for (int i = 0; i < pointsInTime; ++i) {
- double currentMetricLoad = 0.0;
- double? currentMetricIdle;
- for (final rawProcStat in rawProcStatMetrics) {
- final String rawProcValue = rawProcStat['values'][i][1];
- // Converting MBit into bit
- final double procValue = double.parse(rawProcValue) * 1000000;
- currentMetricLoad += procValue;
- if (currentMetricIdle == null &&
- rawProcStat['metric']['mode'] == 'idle') {
- currentMetricIdle = procValue;
- }
- }
- currentMetricIdle ??= 0.0;
- currentMetricLoad = 100.0 * (1 - (currentMetricIdle / currentMetricLoad));
- cpuLoads.add(
- TimeSeriesData(
- rawProcStatMetrics[0]['values'][i][0],
- currentMetricLoad,
- ),
- );
- }
-
- return cpuLoads;
- }
-
- @override
- Future getMetrics(
- final int serverId,
- final DateTime start,
- final DateTime end,
- ) async {
- ServerMetrics? metrics;
-
- const int step = 15;
- final Dio client = await getClient();
- try {
- Response response = await client.get(
- '/monitoring/metrics/droplet/bandwidth',
- queryParameters: {
- 'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
- 'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
- 'host_id': '$serverId',
- 'interface': 'public',
- 'direction': 'inbound',
- },
- );
-
- final List inbound = response.data['data']['result'][0]['values'];
-
- response = await client.get(
- '/monitoring/metrics/droplet/bandwidth',
- queryParameters: {
- 'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
- 'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
- 'host_id': '$serverId',
- 'interface': 'public',
- 'direction': 'outbound',
- },
- );
-
- final List outbound = response.data['data']['result'][0]['values'];
-
- response = await client.get(
- '/monitoring/metrics/droplet/cpu',
- queryParameters: {
- 'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
- 'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
- 'host_id': '$serverId',
- },
- );
-
- metrics = ServerMetrics(
- bandwidthIn: inbound
- .map(
- (final el) => TimeSeriesData(el[0], double.parse(el[1]) * 100000),
- )
- .toList(),
- bandwidthOut: outbound
- .map(
- (final el) => TimeSeriesData(el[0], double.parse(el[1]) * 100000),
- )
- .toList(),
- cpu: calculateCpuLoadMetrics(response.data['data']['result']),
- start: start,
- end: end,
- stepsInSecond: step,
- );
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return metrics;
- }
-
- @override
- Future> getMetadata(final int serverId) async {
- List metadata = [];
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get('/droplets/$serverId');
- final droplet = response.data!['droplet'];
- metadata = [
- ServerMetadataEntity(
- type: MetadataType.id,
- name: 'server.server_id'.tr(),
- value: droplet['id'].toString(),
- ),
- ServerMetadataEntity(
- type: MetadataType.status,
- name: 'server.status'.tr(),
- value: droplet['status'].toString().capitalize(),
- ),
- ServerMetadataEntity(
- type: MetadataType.cpu,
- name: 'server.cpu'.tr(),
- value: 'server.core_count'.plural(droplet['vcpus']),
- ),
- ServerMetadataEntity(
- type: MetadataType.ram,
- name: 'server.ram'.tr(),
- value: "${droplet['memory'].toString()} MB",
- ),
- ServerMetadataEntity(
- type: MetadataType.cost,
- name: 'server.monthly_cost'.tr(),
- value: droplet['size']['price_monthly'].toString(),
- ),
- ServerMetadataEntity(
- type: MetadataType.location,
- name: 'server.location'.tr(),
- value:
- '${droplet['region']['name']} ${getEmojiFlag(droplet['region']['slug'].toString()) ?? ''}',
- ),
- ServerMetadataEntity(
- type: MetadataType.other,
- name: 'server.provider'.tr(),
- value: displayProviderName,
- ),
- ];
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- return metadata;
- }
-
- @override
- Future> getServers() async {
- List servers = [];
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get('/droplets');
- servers = response.data!['droplets'].map(
- (final server) {
- String ipv4 = '0.0.0.0';
- if (server['networks']['v4'].isNotEmpty) {
- for (final v4 in server['networks']['v4']) {
- if (v4['type'].toString() == 'public') {
- ipv4 = v4['ip_address'].toString();
- }
- }
- }
-
- return ServerBasicInfo(
- id: server['id'],
- reverseDns: server['name'],
- created: DateTime.now(),
- ip: ipv4,
- name: server['name'],
- );
- },
- ).toList();
- } catch (e) {
- print(e);
- } finally {
- close(client);
- }
-
- print(servers);
- return servers;
- }
-
- String? getEmojiFlag(final String query) {
- String? emoji;
-
- switch (query.toLowerCase().substring(0, 3)) {
- 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>>
- getAvailableLocations() async {
- List locations = [];
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get(
- '/regions',
- );
-
- locations = response.data!['regions']
- .map(
- (final location) => ServerProviderLocation(
- title: location['slug'],
- description: location['name'],
- flag: getEmojiFlag(location['slug']),
- identifier: location['slug'],
- ),
- )
- .toList();
- } catch (e) {
- print(e);
- return APIGenericResult(
- data: [],
- success: false,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(data: locations, success: true);
- }
-
- @override
- Future>> getServerTypesByLocation({
- required final ServerProviderLocation location,
- }) async {
- final List types = [];
-
- final Dio client = await getClient();
- try {
- final Response response = await client.get(
- '/sizes',
- );
- final rawSizes = response.data!['sizes'];
- for (final rawSize in rawSizes) {
- for (final rawRegion in rawSize['regions']) {
- final ramMb = rawSize['memory'].toDouble();
- if (rawRegion.toString() == location.identifier && ramMb > 1024) {
- types.add(
- ServerType(
- title: rawSize['description'],
- identifier: rawSize['slug'],
- ram: ramMb / 1024,
- cores: rawSize['vcpus'],
- disk: DiskSize(byte: rawSize['disk'] * 1024 * 1024 * 1024),
- price: Price(
- value: rawSize['price_monthly'],
- currency: 'USD',
- ),
- location: location,
- ),
- );
- }
- }
- }
- } catch (e) {
- print(e);
- return APIGenericResult(
- data: [],
- success: false,
- message: e.toString(),
- );
- } finally {
- close(client);
- }
-
- return APIGenericResult(data: types, success: true);
- }
-
- @override
- Future> createReverseDns({
- required final ServerHostingDetails serverDetails,
- required final ServerDomain domain,
- }) async {
- /// TODO remove from provider interface
- const bool success = true;
- return APIGenericResult(success: success, data: null);
- }
-
- @override
- ProviderApiTokenValidation getApiTokenValidation() =>
- ProviderApiTokenValidation(
- regexp: RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'),
- length: 71,
- );
-}
diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_api.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_api.dart
new file mode 100644
index 00000000..807d03a0
--- /dev/null
+++ b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_api.dart
@@ -0,0 +1,561 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
+import 'package:selfprivacy/config/get_it_config.dart';
+import 'package:selfprivacy/logic/api_maps/generic_result.dart';
+import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
+import 'package:selfprivacy/logic/api_maps/tls_options.dart';
+import 'package:selfprivacy/logic/models/disk_size.dart';
+import 'package:selfprivacy/logic/models/hive/user.dart';
+import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart';
+import 'package:selfprivacy/utils/password_generator.dart';
+
+class DigitalOceanApi extends RestApiMap {
+ DigitalOceanApi({
+ required this.region,
+ this.hasLogger = true,
+ this.isWithToken = true,
+ });
+ @override
+ bool hasLogger;
+ @override
+ bool isWithToken;
+
+ final String? region;
+
+ @override
+ BaseOptions get options {
+ final BaseOptions options = BaseOptions(
+ baseUrl: rootAddress,
+ contentType: Headers.jsonContentType,
+ responseType: ResponseType.json,
+ );
+ if (isWithToken) {
+ final String? token = getIt().serverProviderKey;
+ assert(token != null);
+ options.headers = {'Authorization': 'Bearer $token'};
+ }
+
+ if (validateStatus != null) {
+ options.validateStatus = validateStatus!;
+ }
+
+ return options;
+ }
+
+ @override
+ String get rootAddress => 'https://api.digitalocean.com/v2';
+ String get infectProviderName => 'digitalocean';
+ String get displayProviderName => 'Digital Ocean';
+
+ Future> isApiTokenValid(final String token) async {
+ bool isValid = false;
+ Response? response;
+ String message = '';
+ final Dio client = await getClient();
+ try {
+ response = await client.get(
+ '/account',
+ options: Options(
+ followRedirects: false,
+ validateStatus: (final status) =>
+ status != null && (status >= 200 || status == 401),
+ headers: {'Authorization': 'Bearer $token'},
+ ),
+ );
+ } catch (e) {
+ print(e);
+ isValid = false;
+ message = e.toString();
+ } finally {
+ close(client);
+ }
+
+ if (response == null) {
+ return GenericResult(
+ data: isValid,
+ success: false,
+ message: message,
+ );
+ }
+
+ if (response.statusCode == HttpStatus.ok) {
+ isValid = true;
+ } else if (response.statusCode == HttpStatus.unauthorized) {
+ isValid = false;
+ } else {
+ throw Exception('code: ${response.statusCode}');
+ }
+
+ return GenericResult(
+ data: isValid,
+ success: true,
+ message: response.statusMessage,
+ );
+ }
+
+ Future> createVolume() async {
+ DigitalOceanVolume? volume;
+ Response? createVolumeResponse;
+ final Dio client = await getClient();
+ try {
+ await Future.delayed(const Duration(seconds: 6));
+
+ createVolumeResponse = await client.post(
+ '/volumes',
+ data: {
+ 'size_gigabytes': 10,
+ 'name': 'volume${StringGenerators.storageName()}',
+ 'labels': {'labelkey': 'value'},
+ 'region': region,
+ 'filesystem_type': 'ext4',
+ },
+ );
+ volume = DigitalOceanVolume.fromJson(createVolumeResponse.data['volume']);
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: null,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ client.close();
+ }
+
+ return GenericResult(
+ data: volume,
+ success: true,
+ code: createVolumeResponse.statusCode,
+ message: createVolumeResponse.statusMessage,
+ );
+ }
+
+ Future>> getVolumes({
+ final String? status,
+ }) async {
+ final List volumes = [];
+
+ Response? getVolumesResponse;
+ final Dio client = await getClient();
+ try {
+ getVolumesResponse = await client.get(
+ '/volumes',
+ queryParameters: {
+ 'status': status,
+ },
+ );
+ for (final volume in getVolumesResponse.data['volumes']) {
+ volumes.add(DigitalOceanVolume.fromJson(volume));
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ client.close();
+ }
+
+ return GenericResult(
+ data: volumes,
+ success: true,
+ );
+ }
+
+ Future> deleteVolume(final String uuid) async {
+ final Dio client = await getClient();
+ try {
+ await client.delete('/volumes/$uuid');
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: null,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ client.close();
+ }
+
+ return GenericResult(
+ data: null,
+ success: true,
+ );
+ }
+
+ Future> attachVolume(
+ final String name,
+ final int serverId,
+ ) async {
+ bool success = false;
+
+ Response? attachVolumeResponse;
+ final Dio client = await getClient();
+ try {
+ attachVolumeResponse = await client.post(
+ '/volumes/actions',
+ data: {
+ 'type': 'attach',
+ 'volume_name': name,
+ 'region': region,
+ 'droplet_id': serverId,
+ },
+ );
+ success =
+ attachVolumeResponse.data['action']['status'].toString() != 'error';
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: false,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(
+ data: success,
+ success: true,
+ code: attachVolumeResponse.statusCode,
+ message: attachVolumeResponse.statusMessage,
+ );
+ }
+
+ Future> detachVolume(
+ final String name,
+ final int serverId,
+ ) async {
+ bool success = false;
+
+ final Response detachVolumeResponse;
+ final Dio client = await getClient();
+ try {
+ detachVolumeResponse = await client.post(
+ '/volumes/actions',
+ data: {
+ 'type': 'detach',
+ 'volume_name': name,
+ 'droplet_id': serverId,
+ 'region': region,
+ },
+ );
+ success =
+ detachVolumeResponse.data['action']['status'].toString() != 'error';
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: false,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ client.close();
+ }
+
+ return GenericResult(
+ data: success,
+ success: true,
+ );
+ }
+
+ Future> resizeVolume(
+ final String name,
+ final DiskSize size,
+ ) async {
+ bool success = false;
+
+ final Response resizeVolumeResponse;
+ final Dio client = await getClient();
+ try {
+ resizeVolumeResponse = await client.post(
+ '/volumes/actions',
+ data: {
+ 'type': 'resize',
+ 'volume_name': name,
+ 'size_gigabytes': size.gibibyte,
+ 'region': region,
+ },
+ );
+ success =
+ resizeVolumeResponse.data['action']['status'].toString() != 'error';
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: false,
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ client.close();
+ }
+
+ return GenericResult(
+ data: success,
+ success: true,
+ );
+ }
+
+ Future> createServer({
+ required final String dnsApiToken,
+ required final String dnsProviderType,
+ required final String serverApiToken,
+ required final User rootUser,
+ required final String base64Password,
+ required final String databasePassword,
+ required final String domainName,
+ required final String hostName,
+ required final String serverType,
+ }) async {
+ final String stagingAcme = TlsOptions.stagingAcme ? 'true' : 'false';
+
+ int? dropletId;
+ Response? serverCreateResponse;
+ final Dio client = await getClient();
+ try {
+ final Map data = {
+ 'name': hostName,
+ 'size': serverType,
+ 'image': 'ubuntu-20-04-x64',
+ 'user_data': '#cloud-config\n'
+ 'runcmd:\n'
+ '- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/providers/digital-ocean/nixos-infect | '
+ "PROVIDER=$infectProviderName DNS_PROVIDER_TYPE=$dnsProviderType STAGING_ACME='$stagingAcme' DOMAIN='$domainName' "
+ "LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$databasePassword "
+ 'API_TOKEN=$serverApiToken HOSTNAME=$hostName bash 2>&1 | tee /tmp/infect.log',
+ 'region': region!,
+ };
+ print('Decoded data: $data');
+
+ serverCreateResponse = await client.post(
+ '/droplets',
+ data: data,
+ );
+ dropletId = serverCreateResponse.data['droplet']['id'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(
+ data: dropletId,
+ success: true,
+ code: serverCreateResponse.statusCode,
+ message: serverCreateResponse.statusMessage,
+ );
+ }
+
+ Future> deleteServer(final int serverId) async {
+ final Dio client = await getClient();
+ try {
+ await client.delete('/droplets/$serverId');
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> restart(final int serverId) async {
+ final Dio client = await getClient();
+ try {
+ await client.post(
+ '/droplets/$serverId/actions',
+ data: {
+ 'type': 'reboot',
+ },
+ );
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> powerOn(final int serverId) async {
+ final Dio client = await getClient();
+ try {
+ await client.post(
+ '/droplets/$serverId/actions',
+ data: {
+ 'type': 'power_on',
+ },
+ );
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: null,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: null);
+ }
+
+ Future> getMetricsCpu(
+ final int serverId,
+ final DateTime start,
+ final DateTime end,
+ ) async {
+ List metrics = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get(
+ '/monitoring/metrics/droplet/cpu',
+ queryParameters: {
+ 'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
+ 'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
+ 'host_id': '$serverId',
+ },
+ );
+ metrics = response.data['data']['result'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: [],
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: metrics);
+ }
+
+ Future> getMetricsBandwidth(
+ final int serverId,
+ final DateTime start,
+ final DateTime end,
+ final bool isInbound,
+ ) async {
+ List metrics = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get(
+ '/monitoring/metrics/droplet/bandwidth',
+ queryParameters: {
+ 'start': '${(start.microsecondsSinceEpoch / 1000000).round()}',
+ 'end': '${(end.microsecondsSinceEpoch / 1000000).round()}',
+ 'host_id': '$serverId',
+ 'interface': 'public',
+ 'direction': isInbound ? 'inbound' : 'outbound',
+ },
+ );
+ metrics = response.data['data']['result'][0]['values'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: [],
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: metrics);
+ }
+
+ Future> getServers() async {
+ List servers = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get('/droplets');
+ servers = response.data['droplets'];
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ success: false,
+ data: servers,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(success: true, data: servers);
+ }
+
+ Future>>
+ getAvailableLocations() async {
+ final List locations = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get(
+ '/regions',
+ );
+
+ for (final region in response.data!['regions']) {
+ locations.add(DigitalOceanLocation.fromJson(region));
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: locations, success: true);
+ }
+
+ Future>>
+ getAvailableServerTypes() async {
+ final List types = [];
+
+ final Dio client = await getClient();
+ try {
+ final Response response = await client.get(
+ '/sizes',
+ );
+ for (final size in response.data!['sizes']) {
+ types.add(DigitalOceanServerType.fromJson(size));
+ }
+ } catch (e) {
+ print(e);
+ return GenericResult(
+ data: [],
+ success: false,
+ message: e.toString(),
+ );
+ } finally {
+ close(client);
+ }
+
+ return GenericResult(data: types, success: true);
+ }
+}
diff --git a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart b/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart
deleted file mode 100644
index 73a1e647..00000000
--- a/lib/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean_factory.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/digital_ocean/digital_ocean.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_api_settings.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
-
-class DigitalOceanApiFactory extends ServerProviderApiFactory
- with VolumeProviderApiFactory {
- DigitalOceanApiFactory({this.region});
-
- final String? region;
-
- @override
- ServerProviderApi getServerProvider({
- final ServerProviderApiSettings settings =
- const ServerProviderApiSettings(),
- }) =>
- DigitalOceanApi(
- region: settings.region ?? region,
- hasLogger: settings.hasLogger,
- isWithToken: settings.isWithToken,
- );
-
- @override
- VolumeProviderApi getVolumeProvider({
- final ServerProviderApiSettings settings =
- const ServerProviderApiSettings(),
- }) =>
- DigitalOceanApi(
- region: settings.region ?? region,
- hasLogger: settings.hasLogger,
- isWithToken: settings.isWithToken,
- );
-}
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
deleted file mode 100644
index 372722fa..00000000
--- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart
+++ /dev/null
@@ -1,850 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:dio/dio.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:selfprivacy/config/get_it_config.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
-import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
-import 'package:selfprivacy/logic/api_maps/staging_options.dart';
-import 'package:selfprivacy/logic/models/disk_size.dart';
-import 'package:selfprivacy/logic/models/hive/server_domain.dart';
-import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
-import 'package:selfprivacy/logic/models/hive/server_details.dart';
-import 'package:selfprivacy/logic/models/hive/user.dart';
-import 'package:selfprivacy/logic/models/metrics.dart';
-import 'package:selfprivacy/logic/models/price.dart';
-import 'package:selfprivacy/logic/models/server_basic_info.dart';
-import 'package:selfprivacy/logic/models/server_metadata.dart';
-import 'package:selfprivacy/logic/models/server_provider_location.dart';
-import 'package:selfprivacy/logic/models/server_type.dart';
-import 'package:selfprivacy/utils/extensions/string_extensions.dart';
-import 'package:selfprivacy/utils/network_utils.dart';
-import 'package:selfprivacy/utils/password_generator.dart';
-
-class HetznerApi extends ServerProviderApi with VolumeProviderApi {
- HetznerApi({
- this.region,
- this.hasLogger = true,
- this.isWithToken = true,
- });
- @override
- bool hasLogger;
- @override
- bool isWithToken;
-
- final String? region;
-
- @override
- BaseOptions get options {
- final BaseOptions options = BaseOptions(
- baseUrl: rootAddress,
- contentType: Headers.jsonContentType,
- responseType: ResponseType.json,
- );
- if (isWithToken) {
- final String? token = getIt().serverProviderKey;
- assert(token != null);
- options.headers = {'Authorization': 'Bearer $token'};
- }
-
- if (validateStatus != null) {
- options.validateStatus = validateStatus!;
- }
-
- return options;
- }
-
- @override
- String get rootAddress => 'https://api.hetzner.cloud/v1';
-
- @override
- String get infectProviderName => 'hetzner';
-
- @override
- String get displayProviderName => 'Hetzner';
-
- @override
- Future