Compare commits

...

24 Commits

Author SHA1 Message Date
NaiJi ✨ 975c3e237b Merge pull request 'update ru clownflare markdown' (#106) from def/selfprivacy.org.app:master into develop
Reviewed-on: kherel/selfprivacy.org.app#106
2022-07-29 08:41:02 +03:00
def e7ebfdfac6 fix russian lang 2022-07-28 23:41:27 +02:00
def 5305059a3a update ru clownflare markdown 2022-07-28 13:41:17 +02:00
Inex Code dcf120bdbc Merge pull request 'Fix installation Timer errors' (#105) from installation-timer into develop
Reviewed-on: kherel/selfprivacy.org.app#105
2022-07-27 16:05:23 +03:00
NaiJi ✨ d8ea528621 Add magic sleep before deleteVolume on creation failure
It seems Hetzner fails to accept our deleteVolume request so quickly after failing. We need to wait for about 10 seconds, I guess. The same magic sleep happens on deleteServer pack of requests. Please have patience...
2022-07-25 03:03:19 +03:00
NaiJi ✨ dab2c569ec Fix installation Timer errors
Co-authored-by: Inex Code <inex.code@selfprivacy.org>
2022-07-22 19:22:10 +03:00
Inex Code 2c9dcbe5e6 Merge pull request 'Implement Dns Provider Api Abstractions' (#101) from dns-provider-api into develop
Reviewed-on: kherel/selfprivacy.org.app#101
2022-07-19 15:09:19 +03:00
NaiJi ✨ dac310f913 Implement Dns Provider Api Abstractions 2022-07-14 16:34:08 +03:00
Inex Code 8deb240426 Merge pull request 'Implement Provider Api Abstractions' (#99) from naiji-dev into develop
Reviewed-on: kherel/selfprivacy.org.app#99
2022-07-13 21:46:35 +03:00
NaiJi ✨ 9993b09e7f Turn VolumeApiProvider into a mixin 2022-07-13 14:58:23 +03:00
NaiJi ✨ 37b7e9f839 Implement Provider Api Abstractions 2022-07-12 15:54:16 +03:00
Inex Code f40749ca57 Merge pull request 'volumes-hetzner' (#97) from volumes-hetzner into develop
Reviewed-on: kherel/selfprivacy.org.app#97
2022-07-04 02:16:25 +03:00
NaiJi ✨ 5fd8a68597 Change volume size from Gb to Byte 2022-06-28 21:06:52 +03:00
NaiJi ✨ 0a919907c8 Implement hetzner volumes cubit 2022-06-27 10:07:11 +03:00
NaiJi ✨ 352351663f Implement endpoints for hetzner volumes 2022-06-24 00:34:09 +03:00
Inex Code c4f62e012b Merge pull request 'naiji-dev' (#91) from naiji-dev into develop
Reviewed-on: kherel/selfprivacy.org.app#91
2022-06-23 12:12:37 +03:00
Inex Code 4afd40f5da Merge branch 'develop' into naiji-dev 2022-06-23 12:11:59 +03:00
Inex Code 7dc35306c4 Merge pull request 'Force domain to lowercase on recovery pages' (#95) from recovery-domain into develop
Reviewed-on: kherel/selfprivacy.org.app#95
2022-06-23 01:03:14 +03:00
NaiJi ✨ 7e2319bf21 Force domain to lowercase on recovery pages 2022-06-22 22:42:38 +03:00
NaiJi ✨ 0feb9bc299 Return to main page on backbutton press for some recovery pages 2022-06-15 07:58:02 +03:00
NaiJi ✨ 7870cf9f99 Update pubsec version to 0.6.1 2022-06-15 07:06:57 +03:00
NaiJi ✨ 9d4f7b4786 Add changelog for 0.6.1 2022-06-15 06:27:05 +03:00
NaiJi ✨ 6a22e2db6f Fix revert routing errors 2022-06-15 06:23:54 +03:00
NaiJi ✨ 313cfc7187 Bug fix error processing for installation cubits 2022-06-15 04:55:45 +03:00
64 changed files with 1683 additions and 534 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,11 +1,14 @@
### Как получить Cloudflare API Token ### Как получить Cloudflare API Token
1. Переходим по [ссылке](https://dash.cloudflare.com/) и авторизуемся в ранее созданном аккаунте. https://dash.cloudflare.com/ 1. Переходим по [ссылке](https://dash.cloudflare.com/) и авторизуемся в ранее созданном аккаунте. https://dash.cloudflare.com/
В правом углу кликаем на иконку профиля (человечек в кружочке). Для мобильной версии сайта, в верхнем левом углу, нажимаем кнопку **Меню** (три горизонтальных полоски), в выпавшем меню, ищем пункт **My Profile**. 2. В правом верхнем углу кликаем на иконку профиля (для мобильной версии сайта: в верхнем левом углу нажимаем кнопку **Меню** с тремя горизонтальными полосками). В выпавшем меню кликаем на пункт **My Profile**.
![My profile](resource:assets/images/pics/myprofile.png)
3. Нам предлагается на выбор, четыре категории настройки: **Preferences**, **Authentication**, **API Tokens**, **Sessions**. Выбираем **API Tokens**. 3. Нам предлагается на выбор, четыре категории настройки: **Preferences**, **Authentication**, **API Tokens**, **Sessions**. Выбираем **API Tokens**.
4. Самым первым пунктом видим кнопку **Create Token**. С полной уверенностью в себе и желанием обрести приватность, нажимаем на неё. 4. Самым первым пунктом видим кнопку **Create Token**. С полной уверенностью в себе и желанием обрести приватность, нажимаем на неё.
5. Спускаемся в самый низ и видим поле **Create Custom Token** и кнопку **Get Started** с правой стороны. Нажимаем. 5. Спускаемся в самый низ и видим поле **Create Custom Token** и кнопку **Get Started** с правой стороны. Нажимаем.
6. В поле **Token Name** даём своему токену имя. Можете покреативить и отнестись к этому как к наименованию домашнего зверька :) 6. В поле **Token Name** даём своему токену имя. Можете покреативить и отнестись к этому как к наименованию домашнего зверька :)
7. Далее, у нас **Permissions**. В первом поле выбираем Zone. Во втором поле, по центру, выбираем **DNS**. В последнем поле выбираем **Edit**. 7. Далее, у нас **Permissions**. В первом поле выбираем **Zone**. Во втором поле, по центру, выбираем **DNS**. В последнем поле выбираем **Edit**.
8. Нажимаем на синюю надпись снизу **+ Add more** (сразу же под левым полем которое мы заполняли ранее). Вуаля, у нас появились новые поля. Заполняем по аналогии с предыдущим пунктом, в первом поле выбираем **Zone**, во-втором тоже **Zone**. А уже в третьем нажимаем на **Read**. Давайте сверим с тем, что у вас получилось:
![Permissions](resource:assets/images/pics/permissions.png)
8. Далее смотрим на **Zone Resources**. Под этой надписью есть строка с двумя полями. В первом должно быть **Include**, а во втором — **Specific Zone**. Как только Вы выберите **Specific Zone**, справа появится ещё одно поле. В нём выбираем наш домен. 8. Далее смотрим на **Zone Resources**. Под этой надписью есть строка с двумя полями. В первом должно быть **Include**, а во втором — **Specific Zone**. Как только Вы выберите **Specific Zone**, справа появится ещё одно поле. В нём выбираем наш домен.
9. Листаем в самый низ и нажимаем на синюю кнопку **Continue to Summary**. 9. Листаем в самый низ и нажимаем на синюю кнопку **Continue to Summary**.
10. Проверяем, всё ли мы правильно выбрали. Должна присутствовать подобная строка: ваш.домен — **DNS:Edit, Zone:Read**. 10. Проверяем, всё ли мы правильно выбрали. Должна присутствовать подобная строка: ваш.домен — **DNS:Edit, Zone:Read**.

View File

@ -259,6 +259,9 @@
"1": "Connect a server", "1": "Connect a server",
"2": "A place where your data and SelfPrivacy services will reside:", "2": "A place where your data and SelfPrivacy services will reside:",
"how": "How to obtain API token", "how": "How to obtain API token",
"hetzner_bad_key_error": "Hetzner API key is invalid",
"cloudflare_bad_key_error": "Cloudflare API key is invalid",
"backblaze_bad_key_error": "Backblaze storage information is invalid",
"3": "Connect CloudFlare", "3": "Connect CloudFlare",
"4": "To manage your domain's DNS", "4": "To manage your domain's DNS",
"5": "CloudFlare API Token", "5": "CloudFlare API Token",
@ -281,7 +284,7 @@
"22": "Create master account", "22": "Create master account",
"23": "Enter a nickname and strong password", "23": "Enter a nickname and strong password",
"finish": "Everything is initialized", "finish": "Everything is initialized",
"checks": "Checks have been completed \n{} ouf of {}" "checks": "Checks have been completed \n{} out of {}"
}, },
"recovering": { "recovering": {
"recovery_main_header": "Connect to an existing server", "recovery_main_header": "Connect to an existing server",

View File

@ -260,6 +260,9 @@
"1": "Подключите сервер", "1": "Подключите сервер",
"2": "Здесь будут жить наши данные и SelfPrivacy-сервисы", "2": "Здесь будут жить наши данные и SelfPrivacy-сервисы",
"how": "Как получить API Token", "how": "Как получить API Token",
"hetzner_bad_key_error": "Hetzner API ключ неверен",
"cloudflare_bad_key_error": "Cloudflare API ключ неверен",
"backblaze_bad_key_error": "Информация о Backblaze хранилище неверна",
"3": "Подключите CloudFlare", "3": "Подключите CloudFlare",
"4": "Для управления DNS вашего домена", "4": "Для управления DNS вашего домена",
"5": "CloudFlare API Token", "5": "CloudFlare API Token",

View File

@ -1,7 +1,17 @@
targets: targets:
$default: $default:
builders: builders:
graphql_codegen:
options:
scalars:
DateTime:
type: DateTime
fromJsonFunctionName: dateTimeFromJson
toJsonFunctionName: dateTimeToJson
import: package:selfprivacy/utils/scalars.dart
clients:
- graphql
json_serializable: json_serializable:
options: options:
create_factory: true create_factory: true
create_to_json: false create_to_json: false

View File

@ -0,0 +1,3 @@
- Fixed routing errors and broken "back" buttons on recovery stages
- Fixed broken validation on api token fields
- Minor improvements

View File

@ -0,0 +1,9 @@
query GetApiTokensQuery {
api {
devices {
creationDate
isCaller
name
}
}
}

View File

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_api_tokens.graphql.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Query$GetApiTokensQuery _$Query$GetApiTokensQueryFromJson(
Map<String, dynamic> json) =>
Query$GetApiTokensQuery(
api: Query$GetApiTokensQuery$api.fromJson(
json['api'] as Map<String, dynamic>),
$__typename: json['__typename'] as String,
);
Query$GetApiTokensQuery$api _$Query$GetApiTokensQuery$apiFromJson(
Map<String, dynamic> json) =>
Query$GetApiTokensQuery$api(
devices: (json['devices'] as List<dynamic>)
.map((e) => Query$GetApiTokensQuery$api$devices.fromJson(
e as Map<String, dynamic>))
.toList(),
$__typename: json['__typename'] as String,
);
Query$GetApiTokensQuery$api$devices
_$Query$GetApiTokensQuery$api$devicesFromJson(Map<String, dynamic> json) =>
Query$GetApiTokensQuery$api$devices(
creationDate: dateTimeFromJson(json['creationDate']),
isCaller: json['isCaller'] as bool,
name: json['name'] as String,
$__typename: json['__typename'] as String,
);

View File

@ -0,0 +1,5 @@
query GetApiVersionQuery {
api {
version
}
}

View File

@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_api_version.graphql.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Query$GetApiVersionQuery _$Query$GetApiVersionQueryFromJson(
Map<String, dynamic> json) =>
Query$GetApiVersionQuery(
api: Query$GetApiVersionQuery$api.fromJson(
json['api'] as Map<String, dynamic>),
$__typename: json['__typename'] as String,
);
Query$GetApiVersionQuery$api _$Query$GetApiVersionQuery$apiFromJson(
Map<String, dynamic> json) =>
Query$GetApiVersionQuery$api(
version: json['version'] as String,
$__typename: json['__typename'] as String,
);

View File

@ -0,0 +1,151 @@
scalar DateTime
type Alert {
severity: Severity!
title: String!
message: String!
timestamp: DateTime
}
type Api {
version: String!
devices: [ApiDevice!]!
recoveryKey: ApiRecoveryKeyStatus!
}
type ApiDevice {
name: String!
creationDate: DateTime!
isCaller: Boolean!
}
type ApiKeyMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
key: String
}
type ApiRecoveryKeyStatus {
exists: Boolean!
valid: Boolean!
creationDate: DateTime
expirationDate: DateTime
usesLeft: Int
}
type AutoUpgradeOptions {
enable: Boolean!
allowReboot: Boolean!
}
type DeviceApiTokenMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
token: String
}
enum DnsProvider {
CLOUDFLARE
}
type DnsRecord {
recordType: String!
name: String!
content: String!
ttl: Int!
priority: Int
}
type GenericMutationReturn implements MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
}
type Mutation {
getNewRecoveryApiKey(limits: RecoveryKeyLimitsInput!): ApiKeyMutationReturn!
useRecoveryApiKey(input: UseRecoveryKeyInput!): DeviceApiTokenMutationReturn!
refreshDeviceApiToken: DeviceApiTokenMutationReturn!
deleteDeviceApiToken(device: String!): GenericMutationReturn!
getNewDeviceApiKey: ApiKeyMutationReturn!
invalidateNewDeviceApiKey: GenericMutationReturn!
authorizeWithNewDeviceApiKey(input: UseNewDeviceKeyInput!): DeviceApiTokenMutationReturn!
}
interface MutationReturnInterface {
success: Boolean!
message: String!
code: Int!
}
type Query {
system: System!
api: Api!
}
input RecoveryKeyLimitsInput {
expirationDate: DateTime
uses: Int
}
enum ServerProvider {
HETZNER
}
enum Severity {
INFO
WARNING
ERROR
CRITICAL
SUCCESS
}
type SshSettings {
enable: Boolean!
passwordAuthentication: Boolean!
rootSshKeys: [String!]!
}
type System {
status: Alert!
domain: SystemDomainInfo!
settings: SystemSettings!
info: SystemInfo!
provider: SystemProviderInfo!
busy: Boolean!
}
type SystemDomainInfo {
domain: String!
hostname: String!
provider: DnsProvider!
requiredDnsRecords: [DnsRecord!]!
}
type SystemInfo {
systemVersion: String!
pythonVersion: String!
}
type SystemProviderInfo {
provider: ServerProvider!
id: String!
}
type SystemSettings {
autoUpgrade: AutoUpgradeOptions!
ssh: SshSettings!
timezone: String!
}
input UseNewDeviceKeyInput {
key: String!
deviceName: String!
}
input UseRecoveryKeyInput {
key: String!
deviceName: String!
}

View File

@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'schema.graphql.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Input$RecoveryKeyLimitsInput _$Input$RecoveryKeyLimitsInputFromJson(
Map<String, dynamic> json) =>
Input$RecoveryKeyLimitsInput(
expirationDate: _nullable$dateTimeFromJson(json['expirationDate']),
uses: json['uses'] as int?,
);
Input$UseNewDeviceKeyInput _$Input$UseNewDeviceKeyInputFromJson(
Map<String, dynamic> json) =>
Input$UseNewDeviceKeyInput(
key: json['key'] as String,
deviceName: json['deviceName'] as String,
);
Input$UseRecoveryKeyInput _$Input$UseRecoveryKeyInputFromJson(
Map<String, dynamic> json) =>
Input$UseRecoveryKeyInput(
key: json['key'] as String,
deviceName: json['deviceName'] as String,
);

View File

@ -1,277 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/api_map.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/utils/password_generator.dart';
class HetznerApi extends ApiMap {
HetznerApi({this.hasLogger = false, this.isWithToken = true});
@override
bool hasLogger;
@override
bool isWithToken;
@override
BaseOptions get options {
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().hetznerKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
}
return options;
}
@override
String rootAddress = 'https://api.hetzner.cloud/v1';
Future<bool> isValid(final String token) async {
validateStatus = (final int? status) =>
status == HttpStatus.ok || status == HttpStatus.unauthorized;
final Dio client = await getClient();
final Response response = await client.get(
'/servers',
options: Options(
headers: {'Authorization': 'Bearer $token'},
),
);
close(client);
if (response.statusCode == HttpStatus.ok) {
return true;
} else if (response.statusCode == HttpStatus.unauthorized) {
return false;
} else {
throw Exception('code: ${response.statusCode}');
}
}
Future<ServerVolume> createVolume() async {
final Dio client = await getClient();
final Response dbCreateResponse = await client.post(
'/volumes',
data: {
'size': 10,
'name': StringGenerators.dbStorageName(),
'labels': {'labelkey': 'value'},
'location': 'fsn1',
'automount': false,
'format': 'ext4'
},
);
final dbId = dbCreateResponse.data['volume']['id'];
return ServerVolume(
id: dbId,
name: dbCreateResponse.data['volume']['name'],
);
}
Future<ServerHostingDetails?> createServer({
required final String cloudFlareKey,
required final User rootUser,
required final String domainName,
required final ServerVolume dataBase,
}) async {
final Dio client = await getClient();
final String dbPassword = StringGenerators.dbPassword();
final int dbId = dataBase.id;
final String apiToken = StringGenerators.apiToken();
final String hostname = getHostnameFromDomain(domainName);
final String base64Password =
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
print('hostname: $hostname');
/// add ssh key when you need it: e.g. "ssh_keys":["kherel"]
/// check the branch name, it could be "development" or "master".
///
final String userdataString =
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
print(userdataString);
final Map<String, Object> data = {
'name': hostname,
'server_type': 'cx11',
'start_after_create': false,
'image': 'ubuntu-20.04',
'volumes': [dbId],
'networks': [],
'user_data': userdataString,
'labels': {},
'automount': true,
'location': 'fsn1'
};
print('Decoded data: $data');
ServerHostingDetails? serverDetails;
try {
final Response serverCreateResponse = await client.post(
'/servers',
data: data,
);
print(serverCreateResponse.data);
serverDetails = ServerHostingDetails(
id: serverCreateResponse.data['server']['id'],
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
createTime: DateTime.now(),
volume: dataBase,
apiToken: apiToken,
provider: ServerProvider.hetzner,
);
} on DioError catch (e) {
print(e);
rethrow;
} catch (e) {
print(e);
} finally {
client.close();
}
return serverDetails;
}
static String getHostnameFromDomain(final String domain) {
// Replace all non-alphanumeric characters with an underscore
String hostname =
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
if (hostname.endsWith('-')) {
hostname = hostname.substring(0, hostname.length - 1);
}
if (hostname.startsWith('-')) {
hostname = hostname.substring(1);
}
if (hostname.isEmpty) {
hostname = 'selfprivacy-server';
}
return hostname;
}
Future<void> deleteSelfprivacyServerAndAllVolumes({
required final String domainName,
}) async {
final Dio client = await getClient();
final String hostname = getHostnameFromDomain(domainName);
final Response serversReponse = await client.get('/servers');
final List servers = serversReponse.data['servers'];
final Map server = servers.firstWhere((final el) => el['name'] == hostname);
final List volumes = server['volumes'];
final List<Future> laterFutures = <Future>[];
for (final volumeId in volumes) {
await client.post('/volumes/$volumeId/actions/detach');
}
await Future.delayed(const Duration(seconds: 10));
for (final volumeId in volumes) {
laterFutures.add(client.delete('/volumes/$volumeId'));
}
laterFutures.add(client.delete('/servers/${server['id']}'));
await Future.wait(laterFutures);
close(client);
}
Future<ServerHostingDetails> reset() async {
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
final Dio client = await getClient();
await client.post('/servers/${server.id}/actions/reset');
close(client);
return server.copyWith(startTime: DateTime.now());
}
Future<ServerHostingDetails> powerOn() async {
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
final Dio client = await getClient();
await client.post('/servers/${server.id}/actions/poweron');
close(client);
return server.copyWith(startTime: DateTime.now());
}
Future<Map<String, dynamic>> getMetrics(
final DateTime start,
final DateTime end,
final String type,
) async {
final ServerHostingDetails? hetznerServer =
getIt<ApiConfigModel>().serverDetails;
final Dio client = await getClient();
final Map<String, dynamic> queryParameters = {
'start': start.toUtc().toIso8601String(),
'end': end.toUtc().toIso8601String(),
'type': type
};
final Response res = await client.get(
'/servers/${hetznerServer!.id}/metrics',
queryParameters: queryParameters,
);
close(client);
return res.data;
}
Future<HetznerServerInfo> getInfo() async {
final ServerHostingDetails? hetznerServer =
getIt<ApiConfigModel>().serverDetails;
final Dio client = await getClient();
final Response response = await client.get('/servers/${hetznerServer!.id}');
close(client);
return HetznerServerInfo.fromJson(response.data!['server']);
}
Future<List<HetznerServerInfo>> getServers() async {
final Dio client = await getClient();
final Response response = await client.get('/servers');
close(client);
return (response.data!['servers'] as List)
// ignore: unnecessary_lambdas
.map((final e) => HetznerServerInfo.fromJson(e))
.toList();
}
Future<void> createReverseDns({
required final String ip4,
required final String domainName,
}) async {
final ServerHostingDetails? hetznerServer =
getIt<ApiConfigModel>().serverDetails;
final Dio client = await getClient();
try {
await client.post(
'/servers/${hetznerServer!.id}/actions/change_dns_ptr',
data: {
'ip': ip4,
'dns_ptr': domainName,
},
);
} catch (e) {
print(e);
} finally {
close(client);
}
}
}

View File

@ -0,0 +1,48 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_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/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 ServerProvider provider,
) {
switch (provider) {
case ServerProvider.hetzner:
return HetznerApiFactory();
case ServerProvider.unknown:
throw UnknownApiProviderException('Unknown server provider');
}
}
static DnsProviderApiFactory createDnsProviderApiFactory(
final DnsProvider provider,
) {
switch (provider) {
case DnsProvider.cloudflare:
return CloudflareApiFactory();
case DnsProvider.unknown:
throw UnknownApiProviderException('Unknown DNS provider');
}
}
}
class VolumeApiFactoryCreator {
static VolumeProviderApiFactory createVolumeProviderApiFactory(
final ServerProvider provider,
) {
switch (provider) {
case ServerProvider.hetzner:
return HetznerApiFactory();
case ServerProvider.unknown:
throw UnknownApiProviderException('Unknown volume provider');
}
}
}

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
class BackblazeApiAuth { class BackblazeApiAuth {

View File

@ -2,16 +2,11 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/api_map.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/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
class DomainNotFoundException implements Exception { class CloudflareApi extends DnsProviderApi {
DomainNotFoundException(this.message);
final String message;
}
class CloudflareApi extends ApiMap {
CloudflareApi({ CloudflareApi({
this.hasLogger = false, this.hasLogger = false,
this.isWithToken = true, this.isWithToken = true,
@ -24,6 +19,10 @@ class CloudflareApi extends ApiMap {
final String? customToken; final String? customToken;
@override
RegExp getApiTokenValidation() =>
RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
@override @override
BaseOptions get options { BaseOptions get options {
final BaseOptions options = BaseOptions(baseUrl: rootAddress); final BaseOptions options = BaseOptions(baseUrl: rootAddress);
@ -46,27 +45,37 @@ class CloudflareApi extends ApiMap {
@override @override
String rootAddress = 'https://api.cloudflare.com/client/v4'; String rootAddress = 'https://api.cloudflare.com/client/v4';
Future<bool> isValid(final String token) async { @override
validateStatus = (final status) => Future<bool> isApiTokenValid(final String token) async {
status == HttpStatus.ok || status == HttpStatus.unauthorized; bool isValid = false;
Response? response;
final Dio client = await getClient(); final Dio client = await getClient();
final Response response = await client.get( try {
'/user/tokens/verify', response = await client.get(
options: Options(headers: {'Authorization': 'Bearer $token'}), '/user/tokens/verify',
); options: Options(headers: {'Authorization': 'Bearer $token'}),
);
close(client); } catch (e) {
print(e);
if (response.statusCode == HttpStatus.ok) { isValid = false;
return true; } finally {
} else if (response.statusCode == HttpStatus.unauthorized) { close(client);
return false;
} else {
throw Exception('code: ${response.statusCode}');
} }
if (response != null) {
if (response.statusCode == HttpStatus.ok) {
isValid = true;
} else if (response.statusCode == HttpStatus.unauthorized) {
isValid = false;
} else {
throw Exception('code: ${response.statusCode}');
}
}
return isValid;
} }
@override
Future<String> getZoneId(final String domain) async { Future<String> getZoneId(final String domain) async {
validateStatus = (final status) => validateStatus = (final status) =>
status == HttpStatus.ok || status == HttpStatus.forbidden; status == HttpStatus.ok || status == HttpStatus.forbidden;
@ -85,12 +94,13 @@ class CloudflareApi extends ApiMap {
} }
} }
@override
Future<void> removeSimilarRecords({ Future<void> removeSimilarRecords({
required final ServerDomain cloudFlareDomain, required final ServerDomain domain,
final String? ip4, final String? ip4,
}) async { }) async {
final String domainName = cloudFlareDomain.domainName; final String domainName = domain.domainName;
final String domainZoneId = cloudFlareDomain.zoneId; final String domainZoneId = domain.zoneId;
final String url = '/zones/$domainZoneId/dns_records'; final String url = '/zones/$domainZoneId/dns_records';
@ -112,11 +122,12 @@ class CloudflareApi extends ApiMap {
close(client); close(client);
} }
@override
Future<List<DnsRecord>> getDnsRecords({ Future<List<DnsRecord>> getDnsRecords({
required final ServerDomain cloudFlareDomain, required final ServerDomain domain,
}) async { }) async {
final String domainName = cloudFlareDomain.domainName; final String domainName = domain.domainName;
final String domainZoneId = cloudFlareDomain.zoneId; final String domainZoneId = domain.zoneId;
final String url = '/zones/$domainZoneId/dns_records'; final String url = '/zones/$domainZoneId/dns_records';
@ -144,12 +155,13 @@ class CloudflareApi extends ApiMap {
return allRecords; return allRecords;
} }
@override
Future<void> createMultipleDnsRecords({ Future<void> createMultipleDnsRecords({
required final ServerDomain cloudFlareDomain, required final ServerDomain domain,
final String? ip4, final String? ip4,
}) async { }) async {
final String domainName = cloudFlareDomain.domainName; final String domainName = domain.domainName;
final String domainZoneId = cloudFlareDomain.zoneId; final String domainZoneId = domain.zoneId;
final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4); final List<DnsRecord> listDnsRecords = projectDnsRecords(domainName, ip4);
final List<Future> allCreateFutures = <Future>[]; final List<Future> allCreateFutures = <Future>[];
@ -219,11 +231,12 @@ class CloudflareApi extends ApiMap {
]; ];
} }
@override
Future<void> setDkim( Future<void> setDkim(
final String dkimRecordString, final String dkimRecordString,
final ServerDomain cloudFlareDomain, final ServerDomain domain,
) async { ) async {
final String domainZoneId = cloudFlareDomain.zoneId; final String domainZoneId = domain.zoneId;
final String url = '$rootAddress/zones/$domainZoneId/dns_records'; final String url = '$rootAddress/zones/$domainZoneId/dns_records';
final DnsRecord dkimRecord = DnsRecord( final DnsRecord dkimRecord = DnsRecord(
@ -242,6 +255,7 @@ class CloudflareApi extends ApiMap {
client.close(); client.close();
} }
@override
Future<List<String>> domainList() async { Future<List<String>> domainList() async {
final String url = '$rootAddress/zones'; final String url = '$rootAddress/zones';
final Dio client = await getClient(); final Dio client = await getClient();

View File

@ -0,0 +1,15 @@
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_factory.dart';
class CloudflareApiFactory extends DnsProviderApiFactory {
@override
DnsProviderApi getDnsProvider({
final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
}) =>
CloudflareApi(
hasLogger: settings.hasLogger,
isWithToken: settings.isWithToken,
customToken: settings.customToken,
);
}

View File

@ -0,0 +1,31 @@
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';
class DomainNotFoundException implements Exception {
DomainNotFoundException(this.message);
final String message;
}
abstract class DnsProviderApi extends ApiMap {
Future<List<DnsRecord>> getDnsRecords({
required final ServerDomain domain,
});
Future<void> removeSimilarRecords({
required final ServerDomain domain,
final String? ip4,
});
Future<void> createMultipleDnsRecords({
required final ServerDomain domain,
final String? ip4,
});
Future<void> setDkim(
final String dkimRecordString,
final ServerDomain domain,
);
Future<String> getZoneId(final String domain);
Future<List<String>> domainList();
Future<bool> isApiTokenValid(final String token);
RegExp getApiTokenValidation();
}

View File

@ -0,0 +1,17 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
class DnsProviderApiSettings extends ProviderApiSettings {
const DnsProviderApiSettings({
final super.hasLogger = false,
final super.isWithToken = true,
final this.customToken,
});
final String? customToken;
}
abstract class DnsProviderApiFactory {
DnsProviderApi getDnsProvider({
final DnsProviderApiSettings settings = const DnsProviderApiSettings(),
});
}

View File

@ -0,0 +1,5 @@
class ProviderApiSettings {
const ProviderApiSettings({this.hasLogger = false, this.isWithToken = true});
final bool hasLogger;
final bool isWithToken;
}

View File

@ -15,7 +15,7 @@ import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
import 'package:selfprivacy/logic/models/timezone_settings.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
class ApiResponse<D> { class ApiResponse<D> {
ApiResponse({ ApiResponse({
@ -46,7 +46,10 @@ class ServerApi extends ApiMap {
@override @override
BaseOptions get options { BaseOptions get options {
BaseOptions options = BaseOptions(); BaseOptions options = BaseOptions(
connectTimeout: 10000,
receiveTimeout: 10000,
);
if (isWithToken) { if (isWithToken) {
final ServerDomain? cloudFlareDomain = final ServerDomain? cloudFlareDomain =
@ -56,6 +59,8 @@ class ServerApi extends ApiMap {
options = BaseOptions( options = BaseOptions(
baseUrl: 'https://api.$domainName', baseUrl: 'https://api.$domainName',
connectTimeout: 10000,
receiveTimeout: 10000,
headers: { headers: {
'Authorization': 'Bearer $apiToken', 'Authorization': 'Bearer $apiToken',
}, },
@ -65,6 +70,8 @@ class ServerApi extends ApiMap {
if (overrideDomain != null) { if (overrideDomain != null) {
options = BaseOptions( options = BaseOptions(
baseUrl: 'https://api.$overrideDomain', baseUrl: 'https://api.$overrideDomain',
connectTimeout: 10000,
receiveTimeout: 10000,
headers: customToken != null headers: customToken != null
? {'Authorization': 'Bearer $customToken'} ? {'Authorization': 'Bearer $customToken'}
: null, : null,
@ -619,7 +626,7 @@ class ServerApi extends ApiMap {
} }
} }
Future<String?> getDkim() async { Future<String> getDkim() async {
Response response; Response response;
final Dio client = await getClient(); final Dio client = await getClient();
@ -627,13 +634,13 @@ class ServerApi extends ApiMap {
response = await client.get('/services/mailserver/dkim'); response = await client.get('/services/mailserver/dkim');
} on DioError catch (e) { } on DioError catch (e) {
print(e.message); print(e.message);
return null; throw Exception('No DKIM key found');
} finally { } finally {
close(client); close(client);
} }
if (response.statusCode == null) { if (response.statusCode == null) {
return null; throw Exception('No DKIM key found');
} }
if (response.statusCode == HttpStatus.notFound || response.data == null) { if (response.statusCode == HttpStatus.notFound || response.data == null) {
@ -641,7 +648,7 @@ class ServerApi extends ApiMap {
} }
if (response.statusCode != HttpStatus.ok) { if (response.statusCode != HttpStatus.ok) {
return ''; throw Exception('No DKIM key found');
} }
final Codec<String, String> base64toString = utf8.fuse(base64); final Codec<String, String> base64toString = utf8.fuse(base64);

View File

@ -0,0 +1,519 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.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/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/server_basic_info.dart';
import 'package:selfprivacy/utils/password_generator.dart';
class HetznerApi extends ServerProviderApi with VolumeProviderApi {
HetznerApi({final this.hasLogger = false, final this.isWithToken = true});
@override
bool hasLogger;
@override
bool isWithToken;
@override
BaseOptions get options {
final BaseOptions options = BaseOptions(baseUrl: rootAddress);
if (isWithToken) {
final String? token = getIt<ApiConfigModel>().hetznerKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
}
return options;
}
@override
String rootAddress = 'https://api.hetzner.cloud/v1';
@override
Future<bool> isApiTokenValid(final String token) async {
bool isValid = false;
Response? response;
final Dio client = await getClient();
try {
response = await client.get(
'/servers',
options: Options(
headers: {'Authorization': 'Bearer $token'},
),
);
} catch (e) {
print(e);
isValid = false;
} finally {
close(client);
}
if (response != null) {
if (response.statusCode == HttpStatus.ok) {
isValid = true;
} else if (response.statusCode == HttpStatus.unauthorized) {
isValid = false;
} else {
throw Exception('code: ${response.statusCode}');
}
}
return isValid;
}
@override
RegExp getApiTokenValidation() =>
RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]');
@override
Future<ServerVolume?> createVolume() async {
ServerVolume? volume;
final Response dbCreateResponse;
final Dio client = await getClient();
try {
dbCreateResponse = await client.post(
'/volumes',
data: {
'size': 10,
'name': StringGenerators.dbStorageName(),
'labels': {'labelkey': 'value'},
'location': 'fsn1',
'automount': false,
'format': 'ext4'
},
);
final dbId = dbCreateResponse.data['volume']['id'];
final dbSize = dbCreateResponse.data['volume']['size'];
final dbServer = dbCreateResponse.data['volume']['server'];
final dbName = dbCreateResponse.data['volume']['name'];
volume = ServerVolume(
id: dbId,
name: dbName,
sizeByte: dbSize,
serverId: dbServer,
);
} catch (e) {
print(e);
} finally {
client.close();
}
return volume;
}
@override
Future<List<ServerVolume>> getVolumes({final String? status}) async {
final List<ServerVolume> volumes = [];
final Response dbGetResponse;
final Dio client = await getClient();
try {
dbGetResponse = await client.get(
'/volumes',
queryParameters: {
'status': status,
},
);
final List<dynamic> rawVolumes = dbGetResponse.data['volumes'];
for (final rawVolume in rawVolumes) {
final int dbId = rawVolume['id'];
final int dbSize = rawVolume['size'];
final dbServer = rawVolume['server'];
final String dbName = rawVolume['name'];
final volume = ServerVolume(
id: dbId,
name: dbName,
sizeByte: dbSize,
serverId: dbServer,
);
volumes.add(volume);
}
} catch (e) {
print(e);
} finally {
client.close();
}
return volumes;
}
@override
Future<ServerVolume?> getVolume(final int id) async {
ServerVolume? volume;
final Response dbGetResponse;
final Dio client = await getClient();
try {
dbGetResponse = await client.get('/volumes/$id');
final int dbId = dbGetResponse.data['volume']['id'];
final int dbSize = dbGetResponse.data['volume']['size'];
final int dbServer = dbGetResponse.data['volume']['server'];
final String dbName = dbGetResponse.data['volume']['name'];
volume = ServerVolume(
id: dbId,
name: dbName,
sizeByte: dbSize,
serverId: dbServer,
);
} catch (e) {
print(e);
} finally {
client.close();
}
return volume;
}
@override
Future<void> deleteVolume(final int id) async {
final Dio client = await getClient();
try {
await client.delete('/volumes/$id');
} catch (e) {
print(e);
} finally {
client.close();
}
}
@override
Future<bool> attachVolume(final int volumeId, final int serverId) async {
bool success = false;
final Response dbPostResponse;
final Dio client = await getClient();
try {
dbPostResponse = await client.post(
'/volumes/$volumeId/actions/attach',
data: {
'automount': true,
'server': serverId,
},
);
success = dbPostResponse.data['action']['status'].toString() != 'error';
} catch (e) {
print(e);
} finally {
client.close();
}
return success;
}
@override
Future<bool> detachVolume(final int volumeId) async {
bool success = false;
final Response dbPostResponse;
final Dio client = await getClient();
try {
dbPostResponse = await client.post('/volumes/$volumeId/actions/detach');
success = dbPostResponse.data['action']['status'].toString() != 'error';
} catch (e) {
print(e);
} finally {
client.close();
}
return success;
}
@override
Future<bool> resizeVolume(final int volumeId, final int sizeGb) async {
bool success = false;
final Response dbPostResponse;
final Dio client = await getClient();
try {
dbPostResponse = await client.post(
'/volumes/$volumeId/actions/resize',
data: {
'size': sizeGb,
},
);
success = dbPostResponse.data['action']['status'].toString() != 'error';
} catch (e) {
print(e);
} finally {
client.close();
}
return success;
}
@override
Future<ServerHostingDetails?> createServer({
required final String dnsApiToken,
required final User rootUser,
required final String domainName,
}) async {
ServerHostingDetails? details;
final ServerVolume? newVolume = await createVolume();
if (newVolume == null) {
return details;
}
details = await createServerWithVolume(
dnsApiToken: dnsApiToken,
rootUser: rootUser,
domainName: domainName,
dataBase: newVolume,
);
return details;
}
Future<ServerHostingDetails?> createServerWithVolume({
required final String dnsApiToken,
required final User rootUser,
required final String domainName,
required final ServerVolume dataBase,
}) async {
final Dio client = await getClient();
final String dbPassword = StringGenerators.dbPassword();
final int dbId = dataBase.id;
final String apiToken = StringGenerators.apiToken();
final String hostname = getHostnameFromDomain(domainName);
final String base64Password =
base64.encode(utf8.encode(rootUser.password ?? 'PASS'));
print('hostname: $hostname');
/// add ssh key when you need it: e.g. "ssh_keys":["kherel"]
/// check the branch name, it could be "development" or "master".
///
final String userdataString =
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN='$domainName' LUSER='${rootUser.login}' ENCODED_PASSWORD='$base64Password' CF_TOKEN=$dnsApiToken DB_PASSWORD=$dbPassword API_TOKEN=$apiToken HOSTNAME=$hostname bash 2>&1 | tee /tmp/infect.log";
print(userdataString);
final Map<String, Object> data = {
'name': hostname,
'server_type': 'cx11',
'start_after_create': false,
'image': 'ubuntu-20.04',
'volumes': [dbId],
'networks': [],
'user_data': userdataString,
'labels': {},
'automount': true,
'location': 'fsn1'
};
print('Decoded data: $data');
ServerHostingDetails? serverDetails;
DioError? hetznerError;
bool success = false;
try {
final Response serverCreateResponse = await client.post(
'/servers',
data: data,
);
print(serverCreateResponse.data);
serverDetails = ServerHostingDetails(
id: serverCreateResponse.data['server']['id'],
ip4: serverCreateResponse.data['server']['public_net']['ipv4']['ip'],
createTime: DateTime.now(),
volume: dataBase,
apiToken: apiToken,
provider: ServerProvider.hetzner,
);
success = true;
} on DioError catch (e) {
print(e);
hetznerError = e;
} catch (e) {
print(e);
} finally {
client.close();
}
if (!success) {
await Future.delayed(const Duration(seconds: 10));
await deleteVolume(dbId);
}
if (hetznerError != null) {
throw hetznerError;
}
return serverDetails;
}
static String getHostnameFromDomain(final String domain) {
// Replace all non-alphanumeric characters with an underscore
String hostname =
domain.split('.')[0].replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-');
if (hostname.endsWith('-')) {
hostname = hostname.substring(0, hostname.length - 1);
}
if (hostname.startsWith('-')) {
hostname = hostname.substring(1);
}
if (hostname.isEmpty) {
hostname = 'selfprivacy-server';
}
return hostname;
}
@override
Future<void> deleteServer({
required final String domainName,
}) async {
final Dio client = await getClient();
final String hostname = getHostnameFromDomain(domainName);
final Response serversReponse = await client.get('/servers');
final List servers = serversReponse.data['servers'];
final Map server = servers.firstWhere((final el) => el['name'] == hostname);
final List volumes = server['volumes'];
final List<Future> laterFutures = <Future>[];
for (final volumeId in volumes) {
await client.post('/volumes/$volumeId/actions/detach');
}
await Future.delayed(const Duration(seconds: 10));
for (final volumeId in volumes) {
laterFutures.add(client.delete('/volumes/$volumeId'));
}
laterFutures.add(client.delete('/servers/${server['id']}'));
await Future.wait(laterFutures);
close(client);
}
@override
Future<ServerHostingDetails> restart() async {
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
final Dio client = await getClient();
try {
await client.post('/servers/${server.id}/actions/reset');
} catch (e) {
print(e);
} finally {
close(client);
}
return server.copyWith(startTime: DateTime.now());
}
@override
Future<ServerHostingDetails> powerOn() async {
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
final Dio client = await getClient();
try {
await client.post('/servers/${server.id}/actions/poweron');
} catch (e) {
print(e);
} finally {
close(client);
}
return server.copyWith(startTime: DateTime.now());
}
Future<Map<String, dynamic>> getMetrics(
final DateTime start,
final DateTime end,
final String type,
) async {
final ServerHostingDetails? hetznerServer =
getIt<ApiConfigModel>().serverDetails;
final Dio client = await getClient();
final Map<String, dynamic> queryParameters = {
'start': start.toUtc().toIso8601String(),
'end': end.toUtc().toIso8601String(),
'type': type
};
final Response res = await client.get(
'/servers/${hetznerServer!.id}/metrics',
queryParameters: queryParameters,
);
close(client);
return res.data;
}
Future<HetznerServerInfo> getInfo() async {
final ServerHostingDetails? hetznerServer =
getIt<ApiConfigModel>().serverDetails;
final Dio client = await getClient();
final Response response = await client.get('/servers/${hetznerServer!.id}');
close(client);
return HetznerServerInfo.fromJson(response.data!['server']);
}
@override
Future<List<ServerBasicInfo>> getServers() async {
List<ServerBasicInfo> servers = [];
final Dio client = await getClient();
try {
final Response response = await client.get('/servers');
servers = (response.data!['servers'] as List)
.map(
(final e) => HetznerServerInfo.fromJson(e),
)
.toList()
.map(
(final HetznerServerInfo server) => ServerBasicInfo(
id: server.id,
name: server.name,
ip: server.publicNet.ipv4.ip,
reverseDns: server.publicNet.ipv4.reverseDns,
created: server.created,
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
),
)
.toList();
} catch (e) {
print(e);
} finally {
close(client);
}
return servers;
}
@override
Future<void> createReverseDns({
required final ServerHostingDetails serverDetails,
required final ServerDomain domain,
}) async {
final Dio client = await getClient();
try {
await client.post(
'/servers/${serverDetails.id}/actions/change_dns_ptr',
data: {
'ip': serverDetails.ip4,
'dns_ptr': domain.domainName,
},
);
} catch (e) {
print(e);
} finally {
close(client);
}
}
}

View File

@ -0,0 +1,26 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.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_factory.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
class HetznerApiFactory extends ServerProviderApiFactory
with VolumeProviderApiFactory {
@override
ServerProviderApi getServerProvider({
final ProviderApiSettings settings = const ProviderApiSettings(),
}) =>
HetznerApi(
hasLogger: settings.hasLogger,
isWithToken: settings.isWithToken,
);
@override
VolumeProviderApi getVolumeProvider({
final ProviderApiSettings settings = const ProviderApiSettings(),
}) =>
HetznerApi(
hasLogger: settings.hasLogger,
isWithToken: settings.isWithToken,
);
}

View File

@ -0,0 +1,26 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart';
abstract class ServerProviderApi extends ApiMap {
Future<List<ServerBasicInfo>> getServers();
Future<ServerHostingDetails> restart();
Future<ServerHostingDetails> powerOn();
Future<void> deleteServer({required final String domainName});
Future<ServerHostingDetails?> createServer({
required final String dnsApiToken,
required final User rootUser,
required final String domainName,
});
Future<void> createReverseDns({
required final ServerHostingDetails serverDetails,
required final ServerDomain domain,
});
Future<bool> isApiTokenValid(final String token);
RegExp getApiTokenValidation();
}

View File

@ -0,0 +1,15 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/volume_provider.dart';
abstract class ServerProviderApiFactory {
ServerProviderApi getServerProvider({
final ProviderApiSettings settings = const ProviderApiSettings(),
});
}
mixin VolumeProviderApiFactory {
VolumeProviderApi getVolumeProvider({
final ProviderApiSettings settings = const ProviderApiSettings(),
});
}

View File

@ -0,0 +1,12 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/api_map.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
mixin VolumeProviderApi on ApiMap {
Future<ServerVolume?> createVolume();
Future<List<ServerVolume>> getVolumes({final String? status});
Future<ServerVolume?> getVolume(final int id);
Future<bool> attachVolume(final int volumeId, final int serverId);
Future<bool> detachVolume(final int volumeId);
Future<bool> resizeVolume(final int volumeId, final int sizeGb);
Future<void> deleteVolume(final int id);
}

View File

@ -2,8 +2,8 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
import 'package:selfprivacy/logic/models/json/backup.dart'; import 'package:selfprivacy/logic/models/json/backup.dart';

View File

@ -1,5 +1,5 @@
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart';
@ -16,17 +16,16 @@ class ApiDevicesCubit
@override @override
void load() async { void load() async {
if (serverInstallationCubit.state is ServerInstallationFinished) { if (serverInstallationCubit.state is ServerInstallationFinished) {
final List<ApiToken>? devices = await _getApiTokens(); _refetch();
if (devices != null) {
emit(ApiDevicesState(devices, LoadingStatus.success));
} else {
emit(const ApiDevicesState([], LoadingStatus.error));
}
} }
} }
Future<void> refresh() async { Future<void> refresh() async {
emit(const ApiDevicesState([], LoadingStatus.refreshing)); emit(const ApiDevicesState([], LoadingStatus.refreshing));
_refetch();
}
void _refetch() async {
final List<ApiToken>? devices = await _getApiTokens(); final List<ApiToken>? devices = await _getApiTokens();
if (devices != null) { if (devices != null) {
emit(ApiDevicesState(devices, LoadingStatus.success)); emit(ApiDevicesState(devices, LoadingStatus.success));

View File

@ -1,10 +1,12 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.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_factory.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/api_maps/server.dart';
part 'dns_records_state.dart'; part 'dns_records_state.dart';
@ -16,8 +18,12 @@ class DnsRecordsCubit
const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing), const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing),
); );
DnsProviderApiFactory? dnsProviderApiFactory =
ApiFactoryCreator.createDnsProviderApiFactory(
DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!!
); // TODO: Remove when provider selection is implemented.
final ServerApi api = ServerApi(); final ServerApi api = ServerApi();
final CloudflareApi cloudflare = CloudflareApi();
@override @override
Future<void> load() async { Future<void> load() async {
@ -31,14 +37,15 @@ class DnsRecordsCubit
), ),
), ),
); );
print('Loading DNS status');
if (serverInstallationCubit.state is ServerInstallationFinished) { if (serverInstallationCubit.state is ServerInstallationFinished) {
final ServerDomain? domain = serverInstallationCubit.state.serverDomain; final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
final String? ipAddress = final String? ipAddress =
serverInstallationCubit.state.serverDetails?.ip4; serverInstallationCubit.state.serverDetails?.ip4;
if (domain != null && ipAddress != null) { if (domain != null && ipAddress != null) {
final List<DnsRecord> records = final List<DnsRecord> records = await dnsProviderApiFactory!
await cloudflare.getDnsRecords(cloudFlareDomain: domain); .getDnsProvider()
.getDnsRecords(domain: domain);
final String? dkimPublicKey = await api.getDkim(); final String? dkimPublicKey = await api.getDkim();
final List<DesiredDnsRecord> desiredRecords = final List<DesiredDnsRecord> desiredRecords =
_getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey); _getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey);
@ -116,12 +123,14 @@ class DnsRecordsCubit
final ServerDomain? domain = serverInstallationCubit.state.serverDomain; final ServerDomain? domain = serverInstallationCubit.state.serverDomain;
final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4; final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4;
final String? dkimPublicKey = await api.getDkim(); final String? dkimPublicKey = await api.getDkim();
await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!); final DnsProviderApi dnsProviderApi =
await cloudflare.createMultipleDnsRecords( dnsProviderApiFactory!.getDnsProvider();
cloudFlareDomain: domain, await dnsProviderApi.removeSimilarRecords(domain: domain!);
await dnsProviderApi.createMultipleDnsRecords(
domain: domain,
ip4: ipAddress, ip4: ipAddress,
); );
await cloudflare.setDkim(dkimPublicKey ?? '', domain); await dnsProviderApi.setDkim(dkimPublicKey ?? '', domain);
await load(); await load();
} }

View File

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -55,10 +55,11 @@ class BackblazeFormCubit extends FormCubit {
} }
if (!isKeyValid) { if (!isKeyValid) {
keyId.setError('bad key'); keyId.setError('initializing.backblaze_bad_key_error'.tr());
applicationKey.setError('bad key'); applicationKey.setError('initializing.backblaze_bad_key_error'.tr());
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -2,13 +2,12 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
class CloudFlareFormCubit extends FormCubit { class DnsProviderFormCubit extends FormCubit {
CloudFlareFormCubit(this.initializingCubit) { DnsProviderFormCubit(this.initializingCubit) {
final RegExp regExp = RegExp(r'\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); final RegExp regExp = initializingCubit.getDnsProviderApiTokenValidation();
apiKey = FieldCubit( apiKey = FieldCubit(
initalValue: '', initalValue: '',
validations: [ validations: [
@ -30,24 +29,25 @@ class CloudFlareFormCubit extends FormCubit {
} }
final ServerInstallationCubit initializingCubit; final ServerInstallationCubit initializingCubit;
late final FieldCubit<String> apiKey; late final FieldCubit<String> apiKey;
@override @override
FutureOr<bool> asyncValidation() async { FutureOr<bool> asyncValidation() async {
late bool isKeyValid; late bool isKeyValid;
final CloudflareApi apiClient = CloudflareApi(isWithToken: false);
try { try {
isKeyValid = await apiClient.isValid(apiKey.state.value); isKeyValid = await initializingCubit
.isDnsProviderApiTokenValid(apiKey.state.value);
} catch (e) { } catch (e) {
addError(e); addError(e);
isKeyValid = false;
} }
if (!isKeyValid) { if (!isKeyValid) {
apiKey.setError('bad key'); apiKey.setError('initializing.cloudflare_bad_key_error'.tr());
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -1,5 +1,4 @@
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
@ -10,9 +9,10 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
Future<void> load() async { Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain)); emit(Loading(LoadingTypes.loadingDomain));
final CloudflareApi api = CloudflareApi(); final List<String> list = await serverInstallationCubit
.repository.dnsProviderApiFactory!
final List<String> list = await api.domainList(); .getDnsProvider()
.domainList();
if (list.isEmpty) { if (list.isEmpty) {
emit(Empty()); emit(Empty());
} else if (list.length == 1) { } else if (list.length == 1) {
@ -28,11 +28,13 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
Future<void> saveDomain() async { Future<void> saveDomain() async {
assert(state is Loaded, 'wrong state'); assert(state is Loaded, 'wrong state');
final String domainName = (state as Loaded).domain; final String domainName = (state as Loaded).domain;
final CloudflareApi api = CloudflareApi();
emit(Loading(LoadingTypes.saving)); emit(Loading(LoadingTypes.saving));
final String zoneId = await api.getZoneId(domainName); final String zoneId = await serverInstallationCubit
.repository.dnsProviderApiFactory!
.getDnsProvider()
.getZoneId(domainName);
final ServerDomain domain = ServerDomain( final ServerDomain domain = ServerDomain(
domainName: domainName, domainName: domainName,

View File

@ -2,13 +2,13 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
class HetznerFormCubit extends FormCubit { class ProviderFormCubit extends FormCubit {
HetznerFormCubit(this.serverInstallationCubit) { ProviderFormCubit(this.serverInstallationCubit) {
final RegExp regExp = RegExp(r'\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]'); final RegExp regExp =
serverInstallationCubit.getServerProviderApiTokenValidation();
apiKey = FieldCubit( apiKey = FieldCubit(
initalValue: '', initalValue: '',
validations: [ validations: [
@ -30,24 +30,25 @@ class HetznerFormCubit extends FormCubit {
} }
final ServerInstallationCubit serverInstallationCubit; final ServerInstallationCubit serverInstallationCubit;
late final FieldCubit<String> apiKey; late final FieldCubit<String> apiKey;
@override @override
FutureOr<bool> asyncValidation() async { FutureOr<bool> asyncValidation() async {
late bool isKeyValid; late bool isKeyValid;
final HetznerApi apiClient = HetznerApi(isWithToken: false);
try { try {
isKeyValid = await apiClient.isValid(apiKey.state.value); isKeyValid = await serverInstallationCubit
.isServerProviderApiTokenValid(apiKey.state.value);
} catch (e) { } catch (e) {
addError(e); addError(e);
isKeyValid = false;
} }
if (!isKeyValid) { if (!isKeyValid) {
apiKey.setError('bad key'); apiKey.setError('initializing.hetzner_bad_key_error'.tr());
return false; return false;
} }
return true; return true;
} }
} }

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
@ -18,8 +18,9 @@ class RecoveryDomainFormCubit extends FormCubit {
@override @override
FutureOr<void> onSubmit() async { FutureOr<void> onSubmit() async {
initializingCubit initializingCubit.submitDomainForAccessRecovery(
.submitDomainForAccessRecovery(serverDomainField.state.value); serverDomainField.state.value.toLowerCase(),
);
} }
@override @override

View File

@ -41,7 +41,8 @@ class SshFormCubit extends FormCubit {
@override @override
FutureOr<void> onSubmit() { FutureOr<void> onSubmit() {
print(key.state.isValid); print(key.state.isValid);
jobsCubit.addJob(CreateSSHKeyJob(user: user, publicKey: key.state.value)); jobsCubit
.addJob(CreateSSHKeyJob(user: user, publicKey: key.state.value.trim()));
} }
late FieldCubit<String> key; late FieldCubit<String> key;

View File

@ -1,4 +1,4 @@
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart'; import 'package:selfprivacy/logic/models/hetzner_metrics.dart';

View File

@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/job.dart';

View File

@ -1,4 +1,4 @@
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';

View File

@ -1,5 +1,5 @@
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/hetzner/hetzner.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/models/json/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/json/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
import 'package:selfprivacy/logic/models/timezone_settings.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart';

View File

@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
@ -49,13 +51,40 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
} }
} }
RegExp getServerProviderApiTokenValidation() =>
repository.serverProviderApiFactory!
.getServerProvider()
.getApiTokenValidation();
RegExp getDnsProviderApiTokenValidation() => repository.dnsProviderApiFactory!
.getDnsProvider()
.getApiTokenValidation();
Future<bool> isServerProviderApiTokenValid(
final String providerToken,
) async =>
repository.serverProviderApiFactory!
.getServerProvider(
settings: const ProviderApiSettings(isWithToken: false),
)
.isApiTokenValid(providerToken);
Future<bool> isDnsProviderApiTokenValid(
final String providerToken,
) async =>
repository.dnsProviderApiFactory!
.getDnsProvider(
settings: const DnsProviderApiSettings(isWithToken: false),
)
.isApiTokenValid(providerToken);
void setHetznerKey(final String hetznerKey) async { void setHetznerKey(final String hetznerKey) async {
await repository.saveHetznerKey(hetznerKey); await repository.saveHetznerKey(hetznerKey);
if (state is ServerInstallationRecovery) { if (state is ServerInstallationRecovery) {
emit( emit(
(state as ServerInstallationRecovery).copyWith( (state as ServerInstallationRecovery).copyWith(
hetznerKey: hetznerKey, providerApiToken: hetznerKey,
currentStep: RecoveryStep.serverSelection, currentStep: RecoveryStep.serverSelection,
), ),
); );
@ -63,7 +92,9 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
} }
emit( emit(
(state as ServerInstallationNotFinished).copyWith(hetznerKey: hetznerKey), (state as ServerInstallationNotFinished).copyWith(
providerApiToken: hetznerKey,
),
); );
} }
@ -117,7 +148,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
Future<void> onSuccess(final ServerHostingDetails serverDetails) async { Future<void> onSuccess(final ServerHostingDetails serverDetails) async {
await repository.createDnsRecords( await repository.createDnsRecords(
serverDetails.ip4, serverDetails,
state.serverDomain!, state.serverDomain!,
onCancel: onCancel, onCancel: onCancel,
); );
@ -167,6 +198,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final ServerHostingDetails server = await repository.startServer( final ServerHostingDetails server = await repository.startServer(
dataState.serverDetails!, dataState.serverDetails!,
); );
await repository.saveServerDetails(server); await repository.saveServerDetails(server);
await repository.saveIsServerStarted(true); await repository.saveIsServerStarted(true);
@ -292,10 +324,22 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final bool isServerWorking = await repository.isHttpServerWorking(); final bool isServerWorking = await repository.isHttpServerWorking();
if (isServerWorking) { if (isServerWorking) {
await repository.createDkimRecord(dataState.serverDomain!); bool dkimCreated = true;
await repository.saveHasFinalChecked(true); try {
await repository.createDkimRecord(dataState.serverDomain!);
emit(dataState.finish()); } catch (e) {
dkimCreated = false;
}
if (dkimCreated) {
await repository.saveHasFinalChecked(true);
emit(dataState.finish());
} else {
runDelayed(
finishCheckIfServerIsOkay,
const Duration(seconds: 60),
dataState,
);
}
} else { } else {
runDelayed( runDelayed(
finishCheckIfServerIsOkay, finishCheckIfServerIsOkay,
@ -417,11 +461,11 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
), ),
); );
break; break;
case RecoveryStep.serverSelection: case RecoveryStep.cloudflareToken:
repository.deleteHetznerKey(); repository.deleteServerDetails();
emit( emit(
dataState.copyWith( dataState.copyWith(
currentStep: RecoveryStep.hetznerToken, currentStep: RecoveryStep.serverSelection,
), ),
); );
break; break;
@ -464,7 +508,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
final ServerInstallationRecovery dataState = final ServerInstallationRecovery dataState =
state as ServerInstallationRecovery; state as ServerInstallationRecovery;
final List<ServerBasicInfo> servers = final List<ServerBasicInfo> servers =
await repository.getServersOnHetznerAccount(); await repository.getServersOnProviderAccount();
final Iterable<ServerBasicInfoWithValidators> validated = servers.map( final Iterable<ServerBasicInfoWithValidators> validated = servers.map(
(final ServerBasicInfo server) => (final ServerBasicInfo server) =>
ServerBasicInfoWithValidators.fromServerBasicInfo( ServerBasicInfoWithValidators.fromServerBasicInfo(
@ -491,6 +535,8 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
volume: ServerVolume( volume: ServerVolume(
id: server.volumeId, id: server.volumeId,
name: 'recovered_volume', name: 'recovered_volume',
sizeByte: 0,
serverId: server.id,
), ),
apiToken: dataState.serverDetails!.apiToken, apiToken: dataState.serverDetails!.apiToken,
provider: ServerProvider.hetzner, provider: ServerProvider.hetzner,
@ -564,7 +610,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
print('================================'); print('================================');
print('ServerInstallationState changed!'); print('ServerInstallationState changed!');
print('Current type: ${change.nextState.runtimeType}'); print('Current type: ${change.nextState.runtimeType}');
print('Hetzner key: ${change.nextState.hetznerKey}'); print('Hetzner key: ${change.nextState.providerApiToken}');
print('Cloudflare key: ${change.nextState.cloudFlareKey}'); print('Cloudflare key: ${change.nextState.cloudFlareKey}');
print('Domain: ${change.nextState.serverDomain}'); print('Domain: ${change.nextState.serverDomain}');
print('BackblazeCredential: ${change.nextState.backblazeCredential}'); print('BackblazeCredential: ${change.nextState.backblazeCredential}');
@ -597,7 +643,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
await repository.deleteServerRelatedRecords(); await repository.deleteServerRelatedRecords();
emit( emit(
ServerInstallationNotFinished( ServerInstallationNotFinished(
hetznerKey: state.hetznerKey, providerApiToken: state.providerApiToken,
serverDomain: state.serverDomain, serverDomain: state.serverDomain,
cloudFlareKey: state.cloudFlareKey, cloudFlareKey: state.cloudFlareKey,
backblazeCredential: state.backblazeCredential, backblazeCredential: state.backblazeCredential,

View File

@ -9,16 +9,18 @@ import 'package:hive/hive.dart';
import 'package:pub_semver/pub_semver.dart'; import 'package:pub_semver/pub_semver.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart';
import 'package:selfprivacy/logic/api_maps/server.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.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/device_token.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart';
import 'package:selfprivacy/logic/models/json/hetzner_server_info.dart';
import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/logic/models/message.dart';
import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart';
import 'package:selfprivacy/ui/components/action_button/action_button.dart'; import 'package:selfprivacy/ui/components/action_button/action_button.dart';
@ -39,9 +41,17 @@ class ServerAuthorizationException implements Exception {
class ServerInstallationRepository { class ServerInstallationRepository {
Box box = Hive.box(BNames.serverInstallationBox); Box box = Hive.box(BNames.serverInstallationBox);
Box<User> usersBox = Hive.box(BNames.usersBox); Box<User> usersBox = Hive.box(BNames.usersBox);
ServerProviderApiFactory? serverProviderApiFactory =
ApiFactoryCreator.createServerProviderApiFactory(
ServerProvider.hetzner, // TODO: HARDCODE FOR NOW!!!
); // TODO: Remove when provider selection is implemented.
DnsProviderApiFactory? dnsProviderApiFactory =
ApiFactoryCreator.createDnsProviderApiFactory(
DnsProvider.cloudflare, // TODO: HARDCODE FOR NOW!!!
);
Future<ServerInstallationState> load() async { Future<ServerInstallationState> load() async {
final String? hetznerToken = getIt<ApiConfigModel>().hetznerKey; final String? providerApiToken = getIt<ApiConfigModel>().hetznerKey;
final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey; final String? cloudflareToken = getIt<ApiConfigModel>().cloudFlareKey;
final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain; final ServerDomain? serverDomain = getIt<ApiConfigModel>().serverDomain;
final BackblazeCredential? backblazeCredential = final BackblazeCredential? backblazeCredential =
@ -49,9 +59,23 @@ class ServerInstallationRepository {
final ServerHostingDetails? serverDetails = final ServerHostingDetails? serverDetails =
getIt<ApiConfigModel>().serverDetails; getIt<ApiConfigModel>().serverDetails;
if (serverDetails != null &&
serverDetails.provider != ServerProvider.unknown) {
serverProviderApiFactory =
ApiFactoryCreator.createServerProviderApiFactory(
serverDetails.provider,
);
}
if (serverDomain != null && serverDomain.provider != DnsProvider.unknown) {
dnsProviderApiFactory = ApiFactoryCreator.createDnsProviderApiFactory(
serverDomain.provider,
);
}
if (box.get(BNames.hasFinalChecked, defaultValue: false)) { if (box.get(BNames.hasFinalChecked, defaultValue: false)) {
return ServerInstallationFinished( return ServerInstallationFinished(
hetznerKey: hetznerToken!, providerApiToken: providerApiToken!,
cloudFlareKey: cloudflareToken!, cloudFlareKey: cloudflareToken!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
@ -68,14 +92,14 @@ class ServerInstallationRepository {
if (box.get(BNames.isRecoveringServer, defaultValue: false) && if (box.get(BNames.isRecoveringServer, defaultValue: false) &&
serverDomain != null) { serverDomain != null) {
return ServerInstallationRecovery( return ServerInstallationRecovery(
hetznerKey: hetznerToken, providerApiToken: providerApiToken,
cloudFlareKey: cloudflareToken, cloudFlareKey: cloudflareToken,
serverDomain: serverDomain, serverDomain: serverDomain,
backblazeCredential: backblazeCredential, backblazeCredential: backblazeCredential,
serverDetails: serverDetails, serverDetails: serverDetails,
rootUser: box.get(BNames.rootUser), rootUser: box.get(BNames.rootUser),
currentStep: _getCurrentRecoveryStep( currentStep: _getCurrentRecoveryStep(
hetznerToken, providerApiToken,
cloudflareToken, cloudflareToken,
serverDomain, serverDomain,
serverDetails, serverDetails,
@ -85,7 +109,7 @@ class ServerInstallationRepository {
} }
return ServerInstallationNotFinished( return ServerInstallationNotFinished(
hetznerKey: hetznerToken, providerApiToken: providerApiToken,
cloudFlareKey: cloudflareToken, cloudFlareKey: cloudflareToken,
serverDomain: serverDomain, serverDomain: serverDomain,
backblazeCredential: backblazeCredential, backblazeCredential: backblazeCredential,
@ -130,20 +154,24 @@ class ServerInstallationRepository {
Future<ServerHostingDetails> startServer( Future<ServerHostingDetails> startServer(
final ServerHostingDetails hetznerServer, final ServerHostingDetails hetznerServer,
) async { ) async {
final HetznerApi hetznerApi = HetznerApi(); ServerHostingDetails serverDetails;
final ServerHostingDetails serverDetails = await hetznerApi.powerOn();
final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
serverDetails = await api.powerOn();
return serverDetails; return serverDetails;
} }
Future<String?> getDomainId(final String token, final String domain) async { Future<String?> getDomainId(final String token, final String domain) async {
final CloudflareApi cloudflareApi = CloudflareApi( final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(
isWithToken: false, settings: DnsProviderApiSettings(
customToken: token, isWithToken: false,
customToken: token,
),
); );
try { try {
final String domainId = await cloudflareApi.getZoneId(domain); final String domainId = await dnsProviderApi.getZoneId(domain);
return domainId; return domainId;
} on DomainNotFoundException { } on DomainNotFoundException {
return null; return null;
@ -208,18 +236,14 @@ class ServerInstallationRepository {
required final Future<void> Function(ServerHostingDetails serverDetails) required final Future<void> Function(ServerHostingDetails serverDetails)
onSuccess, onSuccess,
}) async { }) async {
final HetznerApi hetznerApi = HetznerApi(); final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
late ServerVolume dataBase;
try { try {
dataBase = await hetznerApi.createVolume(); final ServerHostingDetails? serverDetails = await api.createServer(
dnsApiToken: cloudFlareKey,
final ServerHostingDetails? serverDetails = await hetznerApi.createServer(
cloudFlareKey: cloudFlareKey,
rootUser: rootUser, rootUser: rootUser,
domainName: domainName, domainName: domainName,
dataBase: dataBase,
); );
if (serverDetails == null) { if (serverDetails == null) {
print('Server is not initialized!'); print('Server is not initialized!');
return; return;
@ -238,17 +262,21 @@ class ServerInstallationRepository {
text: 'basis.delete'.tr(), text: 'basis.delete'.tr(),
isRed: true, isRed: true,
onPressed: () async { onPressed: () async {
await hetznerApi.deleteSelfprivacyServerAndAllVolumes( await api.deleteServer(
domainName: domainName, domainName: domainName,
); );
final ServerHostingDetails? serverDetails = ServerHostingDetails? serverDetails;
await hetznerApi.createServer( try {
cloudFlareKey: cloudFlareKey, serverDetails = await api.createServer(
rootUser: rootUser, dnsApiToken: cloudFlareKey,
domainName: domainName, rootUser: rootUser,
dataBase: dataBase, domainName: domainName,
); );
} catch (e) {
print(e);
}
if (serverDetails == null) { if (serverDetails == null) {
print('Server is not initialized!'); print('Server is not initialized!');
return; return;
@ -268,25 +296,27 @@ class ServerInstallationRepository {
} }
} }
Future<void> createDnsRecords( Future<bool> createDnsRecords(
final String ip4, final ServerHostingDetails serverDetails,
final ServerDomain cloudFlareDomain, { final ServerDomain domain, {
required final void Function() onCancel, required final void Function() onCancel,
}) async { }) async {
final CloudflareApi cloudflareApi = CloudflareApi(); final DnsProviderApi dnsProviderApi =
dnsProviderApiFactory!.getDnsProvider();
final ServerProviderApi serverApi =
serverProviderApiFactory!.getServerProvider();
await cloudflareApi.removeSimilarRecords( await dnsProviderApi.removeSimilarRecords(
ip4: ip4, ip4: serverDetails.ip4,
cloudFlareDomain: cloudFlareDomain, domain: domain,
); );
try { try {
await cloudflareApi.createMultipleDnsRecords( await dnsProviderApi.createMultipleDnsRecords(
ip4: ip4, ip4: serverDetails.ip4,
cloudFlareDomain: cloudFlareDomain, domain: domain,
); );
} on DioError catch (e) { } on DioError catch (e) {
final HetznerApi hetznerApi = HetznerApi();
final NavigationService nav = getIt.get<NavigationService>(); final NavigationService nav = getIt.get<NavigationService>();
nav.showPopUpDialog( nav.showPopUpDialog(
BrandAlert( BrandAlert(
@ -299,8 +329,8 @@ class ServerInstallationRepository {
text: 'basis.delete'.tr(), text: 'basis.delete'.tr(),
isRed: true, isRed: true,
onPressed: () async { onPressed: () async {
await hetznerApi.deleteSelfprivacyServerAndAllVolumes( await serverApi.deleteServer(
domainName: cloudFlareDomain.domainName, domainName: domain.domainName,
); );
onCancel(); onCancel();
@ -313,42 +343,46 @@ class ServerInstallationRepository {
], ],
), ),
); );
return false;
} }
await HetznerApi().createReverseDns( await serverApi.createReverseDns(
ip4: ip4, serverDetails: serverDetails,
domainName: cloudFlareDomain.domainName, domain: domain,
); );
return true;
} }
Future<void> createDkimRecord(final ServerDomain cloudFlareDomain) async { Future<void> createDkimRecord(final ServerDomain cloudFlareDomain) async {
final CloudflareApi cloudflareApi = CloudflareApi(); final DnsProviderApi dnsProviderApi =
dnsProviderApiFactory!.getDnsProvider();
final ServerApi api = ServerApi(); final ServerApi api = ServerApi();
final String? dkimRecordString = await api.getDkim(); String dkimRecordString = '';
try {
dkimRecordString = await api.getDkim();
} catch (e) {
print(e);
rethrow;
}
await cloudflareApi.setDkim(dkimRecordString ?? '', cloudFlareDomain); await dnsProviderApi.setDkim(dkimRecordString, cloudFlareDomain);
} }
Future<bool> isHttpServerWorking() async { Future<bool> isHttpServerWorking() async {
final ServerApi api = ServerApi(); final ServerApi api = ServerApi();
final bool isHttpServerWorking = await api.isHttpServerWorking(); return api.isHttpServerWorking();
try {
await api.getDkim();
} catch (e) {
return false;
}
return isHttpServerWorking;
} }
Future<ServerHostingDetails> restart() async { Future<ServerHostingDetails> restart() async {
final HetznerApi hetznerApi = HetznerApi(); final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
return hetznerApi.reset(); return api.restart();
} }
Future<ServerHostingDetails> powerOn() async { Future<ServerHostingDetails> powerOn() async {
final HetznerApi hetznerApi = HetznerApi(); final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
return hetznerApi.powerOn(); return api.powerOn();
} }
Future<ServerRecoveryCapabilities> getRecoveryCapabilities( Future<ServerRecoveryCapabilities> getRecoveryCapabilities(
@ -439,6 +473,8 @@ class ServerInstallationRepository {
volume: ServerVolume( volume: ServerVolume(
id: 0, id: 0,
name: '', name: '',
sizeByte: 0,
serverId: 0,
), ),
provider: ServerProvider.unknown, provider: ServerProvider.unknown,
id: 0, id: 0,
@ -473,6 +509,8 @@ class ServerInstallationRepository {
volume: ServerVolume( volume: ServerVolume(
id: 0, id: 0,
name: '', name: '',
sizeByte: 0,
serverId: 0,
), ),
provider: ServerProvider.unknown, provider: ServerProvider.unknown,
id: 0, id: 0,
@ -507,6 +545,8 @@ class ServerInstallationRepository {
volume: ServerVolume( volume: ServerVolume(
id: 0, id: 0,
name: '', name: '',
serverId: 0,
sizeByte: 0,
), ),
provider: ServerProvider.unknown, provider: ServerProvider.unknown,
id: 0, id: 0,
@ -532,6 +572,8 @@ class ServerInstallationRepository {
volume: ServerVolume( volume: ServerVolume(
id: 0, id: 0,
name: '', name: '',
sizeByte: 0,
serverId: 0,
), ),
provider: ServerProvider.unknown, provider: ServerProvider.unknown,
id: 0, id: 0,
@ -575,21 +617,9 @@ class ServerInstallationRepository {
} }
} }
Future<List<ServerBasicInfo>> getServersOnHetznerAccount() async { Future<List<ServerBasicInfo>> getServersOnProviderAccount() async {
final HetznerApi hetznerApi = HetznerApi(); final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
final List<HetznerServerInfo> servers = await hetznerApi.getServers(); return api.getServers();
return servers
.map(
(final HetznerServerInfo server) => ServerBasicInfo(
id: server.id,
name: server.name,
ip: server.publicNet.ipv4.ip,
reverseDns: server.publicNet.ipv4.reverseDns,
created: server.created,
volumeId: server.volumes.isNotEmpty ? server.volumes[0] : 0,
),
)
.toList();
} }
Future<void> saveServerDetails( Future<void> saveServerDetails(
@ -598,6 +628,11 @@ class ServerInstallationRepository {
await getIt<ApiConfigModel>().storeServerDetails(serverDetails); await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
} }
Future<void> deleteServerDetails() async {
await box.delete(BNames.serverDetails);
getIt<ApiConfigModel>().init();
}
Future<void> saveHetznerKey(final String key) async { Future<void> saveHetznerKey(final String key) async {
print('saved'); print('saved');
await getIt<ApiConfigModel>().storeHetznerKey(key); await getIt<ApiConfigModel>().storeHetznerKey(key);
@ -614,10 +649,20 @@ class ServerInstallationRepository {
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential); await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
} }
Future<void> deleteBackblazeKey() async {
await box.delete(BNames.backblazeCredential);
getIt<ApiConfigModel>().init();
}
Future<void> saveCloudFlareKey(final String key) async { Future<void> saveCloudFlareKey(final String key) async {
await getIt<ApiConfigModel>().storeCloudFlareKey(key); await getIt<ApiConfigModel>().storeCloudFlareKey(key);
} }
Future<void> deleteCloudFlareKey() async {
await box.delete(BNames.cloudFlareKey);
getIt<ApiConfigModel>().init();
}
Future<void> saveDomain(final ServerDomain serverDomain) async { Future<void> saveDomain(final ServerDomain serverDomain) async {
await getIt<ApiConfigModel>().storeServerDomain(serverDomain); await getIt<ApiConfigModel>().storeServerDomain(serverDomain);
} }
@ -652,10 +697,11 @@ class ServerInstallationRepository {
} }
Future<void> deleteServer(final ServerDomain serverDomain) async { Future<void> deleteServer(final ServerDomain serverDomain) async {
final HetznerApi hetznerApi = HetznerApi(); final ServerProviderApi api = serverProviderApiFactory!.getServerProvider();
final CloudflareApi cloudFlare = CloudflareApi(); final DnsProviderApi dnsProviderApi =
dnsProviderApiFactory!.getDnsProvider();
await hetznerApi.deleteSelfprivacyServerAndAllVolumes( await api.deleteServer(
domainName: serverDomain.domainName, domainName: serverDomain.domainName,
); );
@ -666,7 +712,7 @@ class ServerInstallationRepository {
await box.put(BNames.isLoading, false); await box.put(BNames.isLoading, false);
await box.put(BNames.serverDetails, null); await box.put(BNames.serverDetails, null);
await cloudFlare.removeSimilarRecords(cloudFlareDomain: serverDomain); await dnsProviderApi.removeSimilarRecords(domain: serverDomain);
} }
Future<void> deleteServerRelatedRecords() async { Future<void> deleteServerRelatedRecords() async {

View File

@ -2,7 +2,7 @@ part of '../server_installation/server_installation_cubit.dart';
abstract class ServerInstallationState extends Equatable { abstract class ServerInstallationState extends Equatable {
const ServerInstallationState({ const ServerInstallationState({
required this.hetznerKey, required this.providerApiToken,
required this.cloudFlareKey, required this.cloudFlareKey,
required this.backblazeCredential, required this.backblazeCredential,
required this.serverDomain, required this.serverDomain,
@ -15,7 +15,7 @@ abstract class ServerInstallationState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
hetznerKey, providerApiToken,
cloudFlareKey, cloudFlareKey,
backblazeCredential, backblazeCredential,
serverDomain, serverDomain,
@ -25,7 +25,7 @@ abstract class ServerInstallationState extends Equatable {
isServerResetedFirstTime, isServerResetedFirstTime,
]; ];
final String? hetznerKey; final String? providerApiToken;
final String? cloudFlareKey; final String? cloudFlareKey;
final BackblazeCredential? backblazeCredential; final BackblazeCredential? backblazeCredential;
final ServerDomain? serverDomain; final ServerDomain? serverDomain;
@ -35,11 +35,11 @@ abstract class ServerInstallationState extends Equatable {
final bool isServerResetedFirstTime; final bool isServerResetedFirstTime;
final bool isServerResetedSecondTime; final bool isServerResetedSecondTime;
bool get isHetznerFilled => hetznerKey != null; bool get isServerProviderFilled => providerApiToken != null;
bool get isCloudFlareFilled => cloudFlareKey != null; bool get isDnsProviderFilled => cloudFlareKey != null;
bool get isBackblazeFilled => backblazeCredential != null; bool get isBackupsProviderFilled => backblazeCredential != null;
bool get isDomainFilled => serverDomain != null; bool get isDomainSelected => serverDomain != null;
bool get isUserFilled => rootUser != null; bool get isPrimaryUserFilled => rootUser != null;
bool get isServerCreated => serverDetails != null; bool get isServerCreated => serverDetails != null;
bool get isFullyInitilized => _fulfilementList.every((final el) => el!); bool get isFullyInitilized => _fulfilementList.every((final el) => el!);
@ -58,11 +58,11 @@ abstract class ServerInstallationState extends Equatable {
List<bool?> get _fulfilementList { List<bool?> get _fulfilementList {
final List<bool> res = [ final List<bool> res = [
isHetznerFilled, isServerProviderFilled,
isCloudFlareFilled, isDnsProviderFilled,
isBackblazeFilled, isBackupsProviderFilled,
isDomainFilled, isDomainSelected,
isUserFilled, isPrimaryUserFilled,
isServerCreated, isServerCreated,
isServerStarted, isServerStarted,
isServerResetedFirstTime, isServerResetedFirstTime,
@ -80,7 +80,7 @@ class TimerState extends ServerInstallationNotFinished {
this.timerStart, this.timerStart,
this.duration, this.duration,
}) : super( }) : super(
hetznerKey: dataState.hetznerKey, providerApiToken: dataState.providerApiToken,
cloudFlareKey: dataState.cloudFlareKey, cloudFlareKey: dataState.cloudFlareKey,
backblazeCredential: dataState.backblazeCredential, backblazeCredential: dataState.backblazeCredential,
serverDomain: dataState.serverDomain, serverDomain: dataState.serverDomain,
@ -124,7 +124,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
required final super.isServerResetedSecondTime, required final super.isServerResetedSecondTime,
required final this.isLoading, required final this.isLoading,
required this.dnsMatches, required this.dnsMatches,
final super.hetznerKey, final super.providerApiToken,
final super.cloudFlareKey, final super.cloudFlareKey,
final super.backblazeCredential, final super.backblazeCredential,
final super.serverDomain, final super.serverDomain,
@ -136,7 +136,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
@override @override
List<Object?> get props => [ List<Object?> get props => [
hetznerKey, providerApiToken,
cloudFlareKey, cloudFlareKey,
backblazeCredential, backblazeCredential,
serverDomain, serverDomain,
@ -149,7 +149,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
]; ];
ServerInstallationNotFinished copyWith({ ServerInstallationNotFinished copyWith({
final String? hetznerKey, final String? providerApiToken,
final String? cloudFlareKey, final String? cloudFlareKey,
final BackblazeCredential? backblazeCredential, final BackblazeCredential? backblazeCredential,
final ServerDomain? serverDomain, final ServerDomain? serverDomain,
@ -162,7 +162,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
final Map<String, bool>? dnsMatches, final Map<String, bool>? dnsMatches,
}) => }) =>
ServerInstallationNotFinished( ServerInstallationNotFinished(
hetznerKey: hetznerKey ?? this.hetznerKey, providerApiToken: providerApiToken ?? this.providerApiToken,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
backblazeCredential: backblazeCredential ?? this.backblazeCredential, backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain, serverDomain: serverDomain ?? this.serverDomain,
@ -178,7 +178,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
); );
ServerInstallationFinished finish() => ServerInstallationFinished( ServerInstallationFinished finish() => ServerInstallationFinished(
hetznerKey: hetznerKey!, providerApiToken: providerApiToken!,
cloudFlareKey: cloudFlareKey!, cloudFlareKey: cloudFlareKey!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,
@ -193,7 +193,7 @@ class ServerInstallationNotFinished extends ServerInstallationState {
class ServerInstallationEmpty extends ServerInstallationNotFinished { class ServerInstallationEmpty extends ServerInstallationNotFinished {
const ServerInstallationEmpty() const ServerInstallationEmpty()
: super( : super(
hetznerKey: null, providerApiToken: null,
cloudFlareKey: null, cloudFlareKey: null,
backblazeCredential: null, backblazeCredential: null,
serverDomain: null, serverDomain: null,
@ -209,7 +209,7 @@ class ServerInstallationEmpty extends ServerInstallationNotFinished {
class ServerInstallationFinished extends ServerInstallationState { class ServerInstallationFinished extends ServerInstallationState {
const ServerInstallationFinished({ const ServerInstallationFinished({
required final String super.hetznerKey, required final String super.providerApiToken,
required final String super.cloudFlareKey, required final String super.cloudFlareKey,
required final BackblazeCredential super.backblazeCredential, required final BackblazeCredential super.backblazeCredential,
required final ServerDomain super.serverDomain, required final ServerDomain super.serverDomain,
@ -222,7 +222,7 @@ class ServerInstallationFinished extends ServerInstallationState {
@override @override
List<Object?> get props => [ List<Object?> get props => [
hetznerKey, providerApiToken,
cloudFlareKey, cloudFlareKey,
backblazeCredential, backblazeCredential,
serverDomain, serverDomain,
@ -260,7 +260,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
const ServerInstallationRecovery({ const ServerInstallationRecovery({
required this.currentStep, required this.currentStep,
required this.recoveryCapabilities, required this.recoveryCapabilities,
final super.hetznerKey, final super.providerApiToken,
final super.cloudFlareKey, final super.cloudFlareKey,
final super.backblazeCredential, final super.backblazeCredential,
final super.serverDomain, final super.serverDomain,
@ -276,7 +276,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
@override @override
List<Object?> get props => [ List<Object?> get props => [
hetznerKey, providerApiToken,
cloudFlareKey, cloudFlareKey,
backblazeCredential, backblazeCredential,
serverDomain, serverDomain,
@ -288,7 +288,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
]; ];
ServerInstallationRecovery copyWith({ ServerInstallationRecovery copyWith({
final String? hetznerKey, final String? providerApiToken,
final String? cloudFlareKey, final String? cloudFlareKey,
final BackblazeCredential? backblazeCredential, final BackblazeCredential? backblazeCredential,
final ServerDomain? serverDomain, final ServerDomain? serverDomain,
@ -298,7 +298,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
final ServerRecoveryCapabilities? recoveryCapabilities, final ServerRecoveryCapabilities? recoveryCapabilities,
}) => }) =>
ServerInstallationRecovery( ServerInstallationRecovery(
hetznerKey: hetznerKey ?? this.hetznerKey, providerApiToken: providerApiToken ?? this.providerApiToken,
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey, cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
backblazeCredential: backblazeCredential ?? this.backblazeCredential, backblazeCredential: backblazeCredential ?? this.backblazeCredential,
serverDomain: serverDomain ?? this.serverDomain, serverDomain: serverDomain ?? this.serverDomain,
@ -309,7 +309,7 @@ class ServerInstallationRecovery extends ServerInstallationState {
); );
ServerInstallationFinished finish() => ServerInstallationFinished( ServerInstallationFinished finish() => ServerInstallationFinished(
hetznerKey: hetznerKey!, providerApiToken: providerApiToken!,
cloudFlareKey: cloudFlareKey!, cloudFlareKey: cloudFlareKey!,
backblazeCredential: backblazeCredential!, backblazeCredential: backblazeCredential!,
serverDomain: serverDomain!, serverDomain: serverDomain!,

View File

@ -1,4 +1,4 @@
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';

View File

@ -3,7 +3,7 @@ import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/api_maps/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/server.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';

View File

@ -0,0 +1,74 @@
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/hive/server_details.dart';
part 'volumes_state.dart';
class ApiVolumesCubit
extends ServerInstallationDependendCubit<ApiVolumesState> {
ApiVolumesCubit(final ServerInstallationCubit serverInstallationCubit)
: super(serverInstallationCubit, const ApiVolumesState.initial());
final VolumeProviderApiFactory providerApi =
VolumeApiFactoryCreator.createVolumeProviderApiFactory(
getIt<ApiConfigModel>().serverDetails!.provider,
);
@override
void load() async {
if (serverInstallationCubit.state is ServerInstallationFinished) {
_refetch();
}
}
void refresh() async {
emit(const ApiVolumesState([], LoadingStatus.refreshing));
_refetch();
}
void _refetch() async {
final List<ServerVolume> volumes =
await providerApi.getVolumeProvider().getVolumes();
if (volumes.isNotEmpty) {
emit(ApiVolumesState(volumes, LoadingStatus.success));
} else {
emit(const ApiVolumesState([], LoadingStatus.error));
}
}
void attachVolume(final ServerVolume volume) async {
final ServerHostingDetails server = getIt<ApiConfigModel>().serverDetails!;
await providerApi.getVolumeProvider().attachVolume(volume.id, server.id);
refresh();
}
void detachVolume(final ServerVolume volume) async {
await providerApi.getVolumeProvider().detachVolume(volume.id);
refresh();
}
void resizeVolume(final ServerVolume volume, final int newSizeGb) async {
//if (volume.sizeByte < newSizeGb) {
await providerApi.getVolumeProvider().resizeVolume(volume.id, newSizeGb);
refresh();
//}
}
void createVolume() async {
await providerApi.getVolumeProvider().createVolume();
refresh();
}
void deleteVolume(final ServerVolume volume) async {
await providerApi.getVolumeProvider().deleteVolume(volume.id);
refresh();
}
@override
void clear() {
emit(const ApiVolumesState.initial());
}
}

View File

@ -0,0 +1,23 @@
part of 'volumes_cubit.dart';
class ApiVolumesState extends ServerInstallationDependendState {
const ApiVolumesState(this._volumes, this.status);
const ApiVolumesState.initial() : this(const [], LoadingStatus.uninitialized);
final List<ServerVolume> _volumes;
final LoadingStatus status;
List<ServerVolume> get volumes => _volumes;
ApiVolumesState copyWith({
final List<ServerVolume>? volumes,
final LoadingStatus? status,
}) =>
ApiVolumesState(
volumes ?? _volumes,
status ?? this.status,
);
@override
List<Object?> get props => [_volumes];
}

View File

@ -34,7 +34,6 @@ class ApiConfigModel {
Future<void> storeBackblazeCredential(final BackblazeCredential value) async { Future<void> storeBackblazeCredential(final BackblazeCredential value) async {
await _box.put(BNames.backblazeCredential, value); await _box.put(BNames.backblazeCredential, value);
_backblazeCredential = value; _backblazeCredential = value;
} }
@ -64,7 +63,6 @@ class ApiConfigModel {
void init() { void init() {
_hetznerKey = _box.get(BNames.hetznerKey); _hetznerKey = _box.get(BNames.hetznerKey);
_cloudFlareKey = _box.get(BNames.cloudFlareKey); _cloudFlareKey = _box.get(BNames.cloudFlareKey);
_backblazeCredential = _box.get(BNames.backblazeCredential); _backblazeCredential = _box.get(BNames.backblazeCredential);
_serverDomain = _box.get(BNames.serverDomain); _serverDomain = _box.get(BNames.serverDomain);

View File

@ -55,12 +55,18 @@ class ServerVolume {
ServerVolume({ ServerVolume({
required this.id, required this.id,
required this.name, required this.name,
required this.sizeByte,
required this.serverId,
}); });
@HiveField(1) @HiveField(1)
int id; int id;
@HiveField(2) @HiveField(2)
String name; String name;
@HiveField(3, defaultValue: 10737418240) // 10 Gb
int sizeByte;
@HiveField(4, defaultValue: null)
int? serverId;
} }
@HiveType(typeId: 101) @HiveType(typeId: 101)

View File

@ -73,17 +73,23 @@ class ServerVolumeAdapter extends TypeAdapter<ServerVolume> {
return ServerVolume( return ServerVolume(
id: fields[1] as int, id: fields[1] as int,
name: fields[2] as String, name: fields[2] as String,
sizeByte: fields[3] == null ? 10737418240 : fields[3] as int,
serverId: fields[4] as int?,
); );
} }
@override @override
void write(BinaryWriter writer, ServerVolume obj) { void write(BinaryWriter writer, ServerVolume obj) {
writer writer
..writeByte(2) ..writeByte(4)
..writeByte(1) ..writeByte(1)
..write(obj.id) ..write(obj.id)
..writeByte(2) ..writeByte(2)
..write(obj.name); ..write(obj.name)
..writeByte(3)
..write(obj.sizeByte)
..writeByte(4)
..write(obj.serverId);
} }
@override @override

View File

@ -11,9 +11,9 @@ class UserAdapter extends TypeAdapter<User> {
final int typeId = 1; final int typeId = 1;
@override @override
User read(final BinaryReader reader) { User read(BinaryReader reader) {
final int numOfFields = reader.readByte(); final numOfFields = reader.readByte();
final Map<int, dynamic> fields = <int, dynamic>{ final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return User( return User(
@ -26,7 +26,7 @@ class UserAdapter extends TypeAdapter<User> {
} }
@override @override
void write(final BinaryWriter writer, final User obj) { void write(BinaryWriter writer, User obj) {
writer writer
..writeByte(5) ..writeByte(5)
..writeByte(0) ..writeByte(0)
@ -45,7 +45,7 @@ class UserAdapter extends TypeAdapter<User> {
int get hashCode => typeId.hashCode; int get hashCode => typeId.hashCode;
@override @override
bool operator ==(final Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is UserAdapter && other is UserAdapter &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&

View File

@ -104,7 +104,7 @@ class _Card extends StatelessWidget {
context.watch<ServerInstallationCubit>().state; context.watch<ServerInstallationCubit>().state;
final String domainName = final String domainName =
appConfig.isDomainFilled ? appConfig.serverDomain!.domainName : ''; appConfig.isDomainSelected ? appConfig.serverDomain!.domainName : '';
switch (provider.type) { switch (provider.type) {
case ProviderType.server: case ProviderType.server:

View File

@ -5,9 +5,9 @@ import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/cloudflare_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_cloudflare.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -21,7 +21,9 @@ import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
class InitializingPage extends StatelessWidget { class InitializingPage extends StatelessWidget {
const InitializingPage({final super.key}); const InitializingPage({
final super.key,
});
@override @override
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
@ -40,7 +42,7 @@ class InitializingPage extends StatelessWidget {
() => _stepCheck(cubit), () => _stepCheck(cubit),
() => _stepCheck(cubit), () => _stepCheck(cubit),
() => _stepCheck(cubit), () => _stepCheck(cubit),
() => Center(child: Text('initializing.finish'.tr())) () => _stepCheck(cubit)
][cubit.state.progress.index](); ][cubit.state.progress.index]();
return BlocListener<ServerInstallationCubit, ServerInstallationState>( return BlocListener<ServerInstallationCubit, ServerInstallationState>(
@ -58,7 +60,7 @@ class InitializingPage extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: paddingH15V0.copyWith(top: 10, bottom: 10), padding: paddingH15V0.copyWith(top: 10, bottom: 10),
child: cubit.state.isFullyInitilized child: cubit.state is ServerInstallationFinished
? const SizedBox( ? const SizedBox(
height: 80, height: 80,
) )
@ -135,10 +137,12 @@ class InitializingPage extends StatelessWidget {
Widget _stepHetzner(final ServerInstallationCubit serverInstallationCubit) => Widget _stepHetzner(final ServerInstallationCubit serverInstallationCubit) =>
BlocProvider( BlocProvider(
create: (final context) => HetznerFormCubit(serverInstallationCubit), create: (final context) => ProviderFormCubit(
serverInstallationCubit,
),
child: Builder( child: Builder(
builder: (final context) { builder: (final context) {
final formCubitState = context.watch<HetznerFormCubit>().state; final formCubitState = context.watch<ProviderFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -152,7 +156,7 @@ class InitializingPage extends StatelessWidget {
BrandText.body2('initializing.2'.tr()), BrandText.body2('initializing.2'.tr()),
const Spacer(), const Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<HetznerFormCubit>().apiKey, formFieldCubit: context.read<ProviderFormCubit>().apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: const EdgeInsets.only(bottom: 70), scrollPadding: const EdgeInsets.only(bottom: 70),
decoration: const InputDecoration( decoration: const InputDecoration(
@ -163,7 +167,7 @@ class InitializingPage extends StatelessWidget {
BrandButton.rised( BrandButton.rised(
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () => context.read<HetznerFormCubit>().trySubmit(), : () => context.read<ProviderFormCubit>().trySubmit(),
text: 'basis.connect'.tr(), text: 'basis.connect'.tr(),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -191,10 +195,10 @@ class InitializingPage extends StatelessWidget {
Widget _stepCloudflare(final ServerInstallationCubit initializingCubit) => Widget _stepCloudflare(final ServerInstallationCubit initializingCubit) =>
BlocProvider( BlocProvider(
create: (final context) => CloudFlareFormCubit(initializingCubit), create: (final context) => DnsProviderFormCubit(initializingCubit),
child: Builder( child: Builder(
builder: (final context) { builder: (final context) {
final formCubitState = context.watch<CloudFlareFormCubit>().state; final formCubitState = context.watch<DnsProviderFormCubit>().state;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -209,7 +213,7 @@ class InitializingPage extends StatelessWidget {
BrandText.body2('initializing.4'.tr()), BrandText.body2('initializing.4'.tr()),
const Spacer(), const Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<CloudFlareFormCubit>().apiKey, formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
scrollPadding: const EdgeInsets.only(bottom: 70), scrollPadding: const EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
@ -220,7 +224,7 @@ class InitializingPage extends StatelessWidget {
BrandButton.rised( BrandButton.rised(
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () => context.read<CloudFlareFormCubit>().trySubmit(), : () => context.read<DnsProviderFormCubit>().trySubmit(),
text: 'basis.connect'.tr(), text: 'basis.connect'.tr(),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),

View File

@ -28,6 +28,9 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
heroTitle: 'recovering.confirm_backblaze'.tr(), heroTitle: 'recovering.confirm_backblaze'.tr(),
heroSubtitle: 'recovering.confirm_backblaze_description'.tr(), heroSubtitle: 'recovering.confirm_backblaze_description'.tr(),
hasBackButton: true, hasBackButton: true,
onBackButtonPressed: () {
Navigator.of(context).popUntil((final route) => route.isFirst);
},
hasFlashButton: false, hasFlashButton: false,
children: [ children: [
CubitFormTextField( CubitFormTextField(

View File

@ -2,7 +2,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/cloudflare_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -18,11 +18,11 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
context.watch<ServerInstallationCubit>(); context.watch<ServerInstallationCubit>();
return BlocProvider( return BlocProvider(
create: (final BuildContext context) => CloudFlareFormCubit(appConfig), create: (final BuildContext context) => DnsProviderFormCubit(appConfig),
child: Builder( child: Builder(
builder: (final BuildContext context) { builder: (final BuildContext context) {
final FormCubitState formCubitState = final FormCubitState formCubitState =
context.watch<CloudFlareFormCubit>().state; context.watch<DnsProviderFormCubit>().state;
return BrandHeroScreen( return BrandHeroScreen(
heroTitle: 'recovering.confirm_cloudflare'.tr(), heroTitle: 'recovering.confirm_cloudflare'.tr(),
@ -31,9 +31,11 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
), ),
hasBackButton: true, hasBackButton: true,
hasFlashButton: false, hasFlashButton: false,
onBackButtonPressed:
context.read<ServerInstallationCubit>().revertRecoveryStep,
children: [ children: [
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<CloudFlareFormCubit>().apiKey, formFieldCubit: context.read<DnsProviderFormCubit>().apiKey,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'initializing.5'.tr(), labelText: 'initializing.5'.tr(),
@ -43,7 +45,7 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
BrandButton.rised( BrandButton.rised(
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () => context.read<CloudFlareFormCubit>().trySubmit(), : () => context.read<DnsProviderFormCubit>().trySubmit(),
text: 'basis.connect'.tr(), text: 'basis.connect'.tr(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),

View File

@ -39,6 +39,9 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
? 'recovering.choose_server_description'.tr() ? 'recovering.choose_server_description'.tr()
: 'recovering.confirm_server_description'.tr(), : 'recovering.confirm_server_description'.tr(),
hasBackButton: true, hasBackButton: true,
onBackButtonPressed: () {
Navigator.of(context).popUntil((final route) => route.isFirst);
},
hasFlashButton: false, hasFlashButton: false,
children: [ children: [
FutureBuilder<List<ServerBasicInfoWithValidators>>( FutureBuilder<List<ServerBasicInfoWithValidators>>(

View File

@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/forms/setup/initializing/hetzner_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; import 'package:selfprivacy/ui/components/brand_button/filled_button.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -19,11 +19,11 @@ class RecoveryHetznerConnected extends StatelessWidget {
context.watch<ServerInstallationCubit>(); context.watch<ServerInstallationCubit>();
return BlocProvider( return BlocProvider(
create: (final BuildContext context) => HetznerFormCubit(appConfig), create: (final BuildContext context) => ProviderFormCubit(appConfig),
child: Builder( child: Builder(
builder: (final BuildContext context) { builder: (final BuildContext context) {
final FormCubitState formCubitState = final FormCubitState formCubitState =
context.watch<HetznerFormCubit>().state; context.watch<ProviderFormCubit>().state;
return BrandHeroScreen( return BrandHeroScreen(
heroTitle: 'recovering.hetzner_connected'.tr(), heroTitle: 'recovering.hetzner_connected'.tr(),
@ -32,9 +32,12 @@ class RecoveryHetznerConnected extends StatelessWidget {
), ),
hasBackButton: true, hasBackButton: true,
hasFlashButton: false, hasFlashButton: false,
onBackButtonPressed: () {
Navigator.of(context).popUntil((final route) => route.isFirst);
},
children: [ children: [
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<HetznerFormCubit>().apiKey, formFieldCubit: context.read<ProviderFormCubit>().apiKey,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'recovering.hetzner_connected_placeholder'.tr(), labelText: 'recovering.hetzner_connected_placeholder'.tr(),
@ -45,7 +48,7 @@ class RecoveryHetznerConnected extends StatelessWidget {
title: 'more.continue'.tr(), title: 'more.continue'.tr(),
onPressed: formCubitState.isSubmitting onPressed: formCubitState.isSubmitting
? null ? null
: () => context.read<HetznerFormCubit>().trySubmit(), : () => context.read<ProviderFormCubit>().trySubmit(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
BrandButton.text( BrandButton.text(

9
lib/utils/scalars.dart Normal file
View File

@ -0,0 +1,9 @@
// ignore_for_file: prefer_final_parameters, prefer_expression_function_bodies
String dateTimeToJson(DateTime data) {
return data.toIso8601String();
}
DateTime dateTimeFromJson(dynamic data) {
return DateTime.parse(data as String);
}

View File

@ -4,5 +4,5 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_
class UiHelpers { class UiHelpers {
static String getDomainName(final ServerInstallationState config) => static String getDomainName(final ServerInstallationState config) =>
config.isDomainFilled ? config.serverDomain!.domainName : 'example.com'; config.isDomainSelected ? config.serverDomain!.domainName : 'example.com';
} }

View File

@ -169,6 +169,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.16.0" version: "1.16.0"
connectivity_plus:
dependency: transitive
description:
name: connectivity_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.5"
connectivity_plus_linux:
dependency: transitive
description:
name: connectivity_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
connectivity_plus_macos:
dependency: transitive
description:
name: connectivity_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.4"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
connectivity_plus_web:
dependency: transitive
description:
name: connectivity_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
connectivity_plus_windows:
dependency: transitive
description:
name: connectivity_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -218,6 +260,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.3" version: "2.2.3"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.3"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -356,6 +405,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.0.1" version: "8.0.1"
flutter_hooks:
dependency: transitive
description:
name: flutter_hooks
url: "https://pub.dartlang.org"
source: hosted
version: "0.18.5+1"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -462,6 +518,90 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
gql:
dependency: "direct main"
description:
name: gql
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.1"
gql_code_builder:
dependency: transitive
description:
name: gql_code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1"
gql_dedupe_link:
dependency: transitive
description:
name: gql_dedupe_link
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
gql_error_link:
dependency: transitive
description:
name: gql_error_link
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.2"
gql_exec:
dependency: transitive
description:
name: gql_exec
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
gql_http_link:
dependency: transitive
description:
name: gql_http_link
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
gql_link:
dependency: transitive
description:
name: gql_link
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
gql_transform_link:
dependency: transitive
description:
name: gql_transform_link
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.2"
graphql:
dependency: "direct main"
description:
name: graphql
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
graphql_codegen:
dependency: "direct main"
description:
name: graphql_codegen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
graphql_codegen_config:
dependency: transitive
description:
name: graphql_codegen_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.6"
graphql_flutter:
dependency: "direct main"
description:
name: graphql_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -672,6 +812,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
node_preamble: node_preamble:
dependency: transitive dependency: transitive
description: description:
@ -679,6 +826,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
normalize:
dependency: transitive
description:
name: normalize
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.0+1"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -826,6 +980,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
recase:
dependency: transitive
description:
name: recase
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.4"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1153,6 +1321,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.1"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.6"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,7 +1,7 @@
name: selfprivacy name: selfprivacy
description: selfprivacy.org description: selfprivacy.org
publish_to: 'none' publish_to: 'none'
version: 0.6.0+15 version: 0.6.1+15
environment: environment:
sdk: '>=2.17.0 <3.0.0' sdk: '>=2.17.0 <3.0.0'
@ -26,6 +26,10 @@ dependencies:
flutter_markdown: ^0.6.9 flutter_markdown: ^0.6.9
flutter_secure_storage: ^5.0.2 flutter_secure_storage: ^5.0.2
get_it: ^7.2.0 get_it: ^7.2.0
gql: ^0.13.1
graphql: ^5.1.1
graphql_codegen: ^0.9.0
graphql_flutter: ^5.1.0
gtk_theme_fl: ^0.0.1 gtk_theme_fl: ^0.0.1
hive: ^2.0.5 hive: ^2.0.5
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0

View File

@ -6,11 +6,14 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <system_theme/system_theme_plugin.h> #include <system_theme/system_theme_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
SystemThemePluginRegisterWithRegistrar( SystemThemePluginRegisterWithRegistrar(

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus_windows
flutter_secure_storage_windows flutter_secure_storage_windows
system_theme system_theme
url_launcher_windows url_launcher_windows