shlink-manager/lib/API/server_manager.dart

272 lines
8.6 KiB
Dart
Raw Normal View History

2023-06-26 23:40:05 +02:00
import 'dart:async';
2024-07-25 19:11:24 +02:00
import 'dart:convert';
2023-06-26 23:40:05 +02:00
import 'package:dartz/dartz.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
2023-10-21 19:01:25 +02:00
import 'package:shared_preferences/shared_preferences.dart';
2024-01-27 23:07:06 +01:00
import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
2024-07-25 17:31:54 +02:00
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule.dart';
2024-01-27 23:07:06 +01:00
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
2023-07-09 23:00:00 +02:00
import 'package:shlink_app/API/Methods/connect.dart';
2024-01-27 23:07:06 +01:00
import 'package:shlink_app/API/Methods/get_recent_short_urls.dart';
2024-07-25 17:31:54 +02:00
import 'package:shlink_app/API/Methods/get_redirect_rules.dart';
2024-01-27 23:07:06 +01:00
import 'package:shlink_app/API/Methods/get_server_health.dart';
import 'package:shlink_app/API/Methods/get_shlink_stats.dart';
import 'package:shlink_app/API/Methods/get_short_urls.dart';
2024-07-25 17:31:54 +02:00
import 'package:shlink_app/API/Methods/set_redirect_rules.dart';
2024-01-28 22:55:28 +01:00
import 'package:shlink_app/API/Methods/update_short_url.dart';
2023-07-09 23:00:00 +02:00
2024-01-27 23:07:06 +01:00
import 'Methods/delete_short_url.dart';
import 'Methods/submit_short_url.dart';
2023-06-26 23:40:05 +02:00
class ServerManager {
2024-01-27 23:07:06 +01:00
/// The URL of the Shlink server
String? serverUrl;
2023-06-26 23:40:05 +02:00
2024-01-27 23:07:06 +01:00
/// The API key to access the server
String? apiKey;
/// Current Shlink API Version used by the app
2023-06-26 23:40:05 +02:00
static String apiVersion = "3";
String getServerUrl() {
2024-01-27 23:07:06 +01:00
return serverUrl ?? "";
2023-06-26 23:40:05 +02:00
}
2023-07-10 17:32:14 +02:00
String getApiVersion() {
return apiVersion;
}
2024-01-28 01:05:35 +01:00
/// Checks whether the user provided information about the server
/// (url and apikey)
2023-06-26 23:40:05 +02:00
Future<bool> checkLogin() async {
await _loadCredentials();
2024-01-27 23:07:06 +01:00
return (serverUrl != null);
2023-06-26 23:40:05 +02:00
}
2024-01-27 23:07:06 +01:00
/// Logs out the user and removes data about the Shlink server
2024-07-25 19:11:24 +02:00
Future<void> logOut(String url) async {
2023-07-09 23:15:15 +02:00
const storage = FlutterSecureStorage();
2024-07-25 19:11:24 +02:00
final prefs = await SharedPreferences.getInstance();
String? serverMapSerialized = await storage.read(key: "server_map");
if (serverMapSerialized != null) {
2024-07-25 19:23:21 +02:00
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
2024-07-25 19:11:24 +02:00
serverMap.remove(url);
if (serverMap.isEmpty) {
storage.delete(key: "server_map");
} else {
storage.write(key: "server_map", value: jsonEncode(serverMap));
}
if (serverUrl == url) {
serverUrl = null;
apiKey = null;
prefs.remove("lastusedserver");
}
}
}
/// Returns all servers saved in the app
Future<List<String>> getAvailableServers() async {
const storage = FlutterSecureStorage();
String? serverMapSerialized = await storage.read(key: "server_map");
if (serverMapSerialized != null) {
2024-07-25 19:23:21 +02:00
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
2024-07-25 19:11:24 +02:00
return serverMap.keys.toList();
} else {
return [];
}
2023-07-09 23:15:15 +02:00
}
2024-01-27 23:07:06 +01:00
/// Loads the server credentials from [FlutterSecureStorage]
2023-06-26 23:40:05 +02:00
Future<void> _loadCredentials() async {
const storage = FlutterSecureStorage();
2023-10-21 19:01:25 +02:00
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('first_run') ?? true) {
await storage.deleteAll();
prefs.setBool('first_run', false);
2024-03-31 21:08:23 +02:00
} else {
2024-07-25 19:11:24 +02:00
if (await _replaceDeprecatedStorageMethod()) {
_loadCredentials();
return;
}
String? serverMapSerialized = await storage.read(key: "server_map");
String? lastUsedServer = prefs.getString("lastusedserver");
if (serverMapSerialized != null) {
2024-07-25 19:23:21 +02:00
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
2024-07-25 19:11:24 +02:00
if (lastUsedServer != null) {
serverUrl = lastUsedServer;
apiKey = serverMap[lastUsedServer]!;
} else {
List<String> availableServers = serverMap.keys.toList();
2024-07-25 19:23:21 +02:00
if (availableServers.isNotEmpty) {
2024-07-25 19:11:24 +02:00
serverUrl = availableServers.first;
apiKey = serverMap[serverUrl];
prefs.setString("lastusedserver", serverUrl!);
}
}
}
}
}
Future<bool> _replaceDeprecatedStorageMethod() async {
const storage = FlutterSecureStorage();
// deprecated data storage method, replaced because of multi-server support
2024-07-25 19:23:21 +02:00
var v1DataServerurl = await storage.read(key: "shlink_url");
var v1DataApikey = await storage.read(key: "shlink_apikey");
2024-07-25 19:11:24 +02:00
2024-07-25 19:23:21 +02:00
if (v1DataServerurl != null && v1DataApikey != null) {
2024-07-25 19:11:24 +02:00
// conversion to new data storage method
Map<String, String> serverMap = {};
2024-07-25 19:23:21 +02:00
serverMap[v1DataServerurl] = v1DataApikey;
2024-07-25 19:11:24 +02:00
storage.write(key: "server_map", value: jsonEncode(serverMap));
storage.delete(key: "shlink_url");
storage.delete(key: "shlink_apikey");
return true;
} else {
return false;
2023-10-21 19:01:25 +02:00
}
2023-06-26 23:40:05 +02:00
}
2024-01-27 23:07:06 +01:00
/// Saves the provided server credentials to [FlutterSecureStorage]
2023-06-26 23:40:05 +02:00
void _saveCredentials(String url, String apiKey) async {
const storage = FlutterSecureStorage();
2024-07-25 19:11:24 +02:00
final prefs = await SharedPreferences.getInstance();
String? serverMapSerialized = await storage.read(key: "server_map");
Map<String, String> serverMap;
if (serverMapSerialized != null) {
serverMap = Map.castFrom(jsonDecode(serverMapSerialized));
} else {
serverMap = {};
}
serverMap[url] = apiKey;
storage.write(key: "server_map", value: jsonEncode(serverMap));
prefs.setString("lastusedserver", url);
2023-06-26 23:40:05 +02:00
}
2024-01-27 23:07:06 +01:00
/// Saves provided server credentials and tries to establish a connection
2024-01-28 00:32:09 +01:00
FutureOr<Either<String, Failure>> initAndConnect(
String url, String apiKey) async {
2023-06-26 23:40:05 +02:00
// TODO: convert url to correct format
2024-01-27 23:07:06 +01:00
serverUrl = url;
this.apiKey = apiKey;
2023-06-26 23:40:05 +02:00
_saveCredentials(url, apiKey);
final result = await connect();
result.fold((l) => null, (r) {
2024-07-25 19:11:24 +02:00
logOut(url);
2023-06-26 23:40:05 +02:00
});
return result;
}
2024-01-27 23:07:06 +01:00
/// Establishes a connection to the server
2023-06-26 23:40:05 +02:00
FutureOr<Either<String, Failure>> connect() async {
_loadCredentials();
2024-01-27 23:07:06 +01:00
return apiConnect(apiKey, serverUrl, apiVersion);
2023-06-26 23:40:05 +02:00
}
2024-01-27 23:07:06 +01:00
/// Gets all short URLs from the server
2023-06-26 23:40:05 +02:00
FutureOr<Either<List<ShortURL>, Failure>> getShortUrls() async {
2024-01-27 23:07:06 +01:00
return apiGetShortUrls(apiKey, serverUrl, apiVersion);
2023-07-09 23:00:00 +02:00
}
2024-01-27 23:07:06 +01:00
/// Gets statistics about the Shlink instance
2023-07-09 23:00:00 +02:00
FutureOr<Either<ShlinkStats, Failure>> getShlinkStats() async {
2024-01-27 23:07:06 +01:00
return apiGetShlinkStats(apiKey, serverUrl, apiVersion);
2023-07-09 23:00:00 +02:00
}
2024-01-27 23:07:06 +01:00
/// Saves a new short URL to the server
2024-01-28 22:55:28 +01:00
FutureOr<Either<ShortURL, Failure>> submitShortUrl(
2024-01-28 00:32:09 +01:00
ShortURLSubmission shortUrl) async {
2024-01-27 23:07:06 +01:00
return apiSubmitShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
2023-06-26 23:40:05 +02:00
}
2024-01-28 22:55:28 +01:00
FutureOr<Either<ShortURL, Failure>> updateShortUrl(
ShortURLSubmission shortUrl) async {
return apiUpdateShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
}
2024-01-27 23:07:06 +01:00
/// Deletes a short URL from the server, identified by its slug
2023-07-09 23:00:00 +02:00
FutureOr<Either<String, Failure>> deleteShortUrl(String shortCode) async {
2024-01-27 23:07:06 +01:00
return apiDeleteShortUrl(shortCode, apiKey, serverUrl, apiVersion);
2023-06-26 23:40:05 +02:00
}
2023-07-10 17:32:14 +02:00
2024-01-27 23:07:06 +01:00
/// Gets health data about the server
2023-07-10 17:32:14 +02:00
FutureOr<Either<ServerHealthResponse, Failure>> getServerHealth() async {
2024-01-27 23:07:06 +01:00
return apiGetServerHealth(apiKey, serverUrl, apiVersion);
2023-07-10 17:32:14 +02:00
}
2024-01-27 23:07:06 +01:00
/// Gets recently created/used short URLs from the server
2023-07-10 17:32:14 +02:00
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
2024-01-27 23:07:06 +01:00
return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion);
2023-07-10 17:32:14 +02:00
}
2024-07-25 17:37:17 +02:00
2024-07-25 17:31:54 +02:00
/// Gets redirect rules for a given short URL (code)
FutureOr<Either<List<RedirectRule>, Failure>> getRedirectRules(
String shortCode) async {
return apiGetRedirectRules(shortCode, apiKey, serverUrl, apiVersion);
}
/// Sets redirect rules for a given short URL (code)
FutureOr<Either<bool, Failure>> setRedirectRules(
String shortCode, List<RedirectRule> redirectRules) async {
2024-07-25 17:37:17 +02:00
return apiSetRedirectRules(
shortCode, redirectRules, apiKey, serverUrl, apiVersion);
2024-07-25 17:31:54 +02:00
}
2023-06-26 23:40:05 +02:00
}
2024-01-27 23:07:06 +01:00
/// Server response data type about a page of short URLs from the server
2023-06-26 23:40:05 +02:00
class ShortURLPageResponse {
List<ShortURL> urls;
int totalPages;
ShortURLPageResponse(this.urls, this.totalPages);
}
2024-01-27 23:07:06 +01:00
/// Server response data type about the health status of the server
2023-07-10 17:32:14 +02:00
class ServerHealthResponse {
String status;
String version;
ServerHealthResponse({required this.status, required this.version});
}
2024-01-27 23:07:06 +01:00
/// Failure class, used for the API
2023-06-26 23:40:05 +02:00
abstract class Failure {}
2024-01-28 00:32:09 +01:00
/// Used when a request to a server fails
/// (due to networking issues or an unexpected response)
2023-06-26 23:40:05 +02:00
class RequestFailure extends Failure {
int statusCode;
String description;
RequestFailure(this.statusCode, this.description);
}
2024-01-27 23:07:06 +01:00
/// Contains information about an error returned by the Shlink API
2023-06-26 23:40:05 +02:00
class ApiFailure extends Failure {
String type;
String detail;
String title;
int status;
2023-07-09 23:00:00 +02:00
List<dynamic>? invalidElements;
2023-06-26 23:40:05 +02:00
2024-01-28 00:32:09 +01:00
ApiFailure(
{required this.type,
required this.detail,
required this.title,
required this.status,
this.invalidElements});
}