Externalized API and models as own package

This commit is contained in:
Anton Stubenbord
2022-12-02 01:48:13 +01:00
parent 60d1a2e62a
commit ec7707e4a4
143 changed files with 1496 additions and 1339 deletions

View File

@@ -1,17 +1,21 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/model/paperless_server_information.dart';
import 'package:paperless_mobile/core/service/paperless_server_information_service.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
@singleton
class PaperlessServerInformationCubit
extends Cubit<PaperlessServerInformation> {
final PaperlessServerInformationService service;
extends Cubit<PaperlessServerInformationState> {
final PaperlessServerStatsApi service;
PaperlessServerInformationCubit(this.service)
: super(PaperlessServerInformation());
: super(PaperlessServerInformationState());
Future<void> updateInformtion() async {
emit(await service.getInformation());
final information = await service.getServerInformation();
emit(PaperlessServerInformationState(
isLoaded: true,
information: information,
));
}
}

View File

@@ -0,0 +1,11 @@
import 'package:paperless_api/paperless_api.dart';
class PaperlessServerInformationState {
final bool isLoaded;
final PaperlessServerInformationModel? information;
PaperlessServerInformationState({
this.isLoaded = false,
this.information,
});
}

View File

@@ -1,35 +1,32 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/store/local_vault.dart';
import 'package:http_interceptor/http_interceptor.dart';
import 'package:injectable/injectable.dart';
@injectable
class AuthenticationInterceptor implements InterceptorContract {
AuthenticationCubit authenticationCubit;
AuthenticationInterceptor(this.authenticationCubit);
final LocalVault _localVault;
AuthenticationInterceptor(this._localVault);
@override
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
final authState = authenticationCubit.state;
final auth = await _localVault.loadAuthenticationInformation();
if (kDebugMode) {
log("Intercepted ${request.method} request to ${request.url.toString()}");
}
if (authState.authentication == null) {
throw const ErrorMessage(ErrorCode.notAuthenticated);
if (auth == null) {
throw const PaperlessServerException(ErrorCode.notAuthenticated);
}
return request.copyWith(
//Append server Url
url: Uri.parse(
authState.authentication!.serverUrl + request.url.toString()),
headers: authState.authentication!.token.isEmpty
url: Uri.parse(auth.serverUrl + request.url.toString()),
headers: auth.token.isEmpty
? request.headers
: {
...request.headers,
'Authorization': 'Token ${authState.authentication!.token}'
},
: {...request.headers, 'Authorization': 'Token ${auth.token}'},
);
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/generated/l10n.dart';
String translateError(BuildContext context, ErrorCode code) {

View File

@@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'dart:convert';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/di_initializer.dart';
@@ -24,8 +24,8 @@ class TimeoutClient implements BaseClient {
Future<StreamedResponse> send(BaseRequest request) async {
return getIt<BaseClient>().send(request).timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
);
}
@@ -47,8 +47,8 @@ class TimeoutClient implements BaseClient {
.delete(url, headers: headers, body: body, encoding: encoding)
.timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -62,8 +62,8 @@ class TimeoutClient implements BaseClient {
return _handle400Error(
await getIt<BaseClient>().get(url, headers: headers).timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -77,8 +77,8 @@ class TimeoutClient implements BaseClient {
return _handle400Error(
await getIt<BaseClient>().head(url, headers: headers).timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -96,8 +96,8 @@ class TimeoutClient implements BaseClient {
.patch(url, headers: headers, body: body, encoding: encoding)
.timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -115,8 +115,8 @@ class TimeoutClient implements BaseClient {
.post(url, headers: headers, body: body, encoding: encoding)
.timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -134,8 +134,8 @@ class TimeoutClient implements BaseClient {
.put(url, headers: headers, body: body, encoding: encoding)
.timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
),
);
}
@@ -148,8 +148,8 @@ class TimeoutClient implements BaseClient {
await _handleOfflineState();
return getIt<BaseClient>().read(url, headers: headers).timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
);
}
@@ -161,8 +161,8 @@ class TimeoutClient implements BaseClient {
await _handleOfflineState();
return getIt<BaseClient>().readBytes(url, headers: headers).timeout(
requestTimeout,
onTimeout: () =>
Future.error(const ErrorMessage(ErrorCode.requestTimedOut)),
onTimeout: () => Future.error(
const PaperlessServerException(ErrorCode.requestTimedOut)),
);
}
@@ -188,7 +188,7 @@ class TimeoutClient implements BaseClient {
Future<void> _handleOfflineState() async {
if (!(await connectivityStatusService.isConnectedToInternet())) {
throw const ErrorMessage(ErrorCode.deviceOffline);
throw const PaperlessServerException(ErrorCode.deviceOffline);
}
}
}

View File

@@ -1,55 +0,0 @@
class ErrorMessage implements Exception {
final ErrorCode code;
final String? details;
final StackTrace? stackTrace;
final int? httpStatusCode;
const ErrorMessage(
this.code, {
this.details,
this.stackTrace,
this.httpStatusCode,
});
const ErrorMessage.unknown() : this(ErrorCode.unknown);
@override
String toString() {
return "ErrorMessage(code: $code${stackTrace != null ? ', stackTrace: ${stackTrace.toString()}' : ''}${httpStatusCode != null ? ', httpStatusCode: $httpStatusCode' : ''})";
}
}
enum ErrorCode {
unknown,
authenticationFailed,
notAuthenticated,
documentUploadFailed,
documentUpdateFailed,
documentLoadFailed,
documentDeleteFailed,
documentBulkActionFailed,
documentPreviewFailed,
documentAsnQueryFailed,
tagCreateFailed,
tagLoadFailed,
documentTypeCreateFailed,
documentTypeLoadFailed,
correspondentCreateFailed,
correspondentLoadFailed,
scanRemoveFailed,
invalidClientCertificateConfiguration,
biometricsNotSupported,
biometricAuthenticationFailed,
deviceOffline,
serverUnreachable,
similarQueryError,
autocompleteQueryError,
storagePathLoadFailed,
storagePathCreateFailed,
loadSavedViewsError,
createSavedViewError,
deleteSavedViewError,
requestTimedOut,
unsupportedFileFormat,
missingClientCertificate;
}

View File

@@ -1,15 +0,0 @@
class PaperlessServerInformation {
static const String versionHeader = 'x-version';
static const String apiVersionHeader = 'x-api-version';
static const String hostHeader = 'x-served-by';
final String? version;
final int? apiVersion;
final String? username;
final String? host;
PaperlessServerInformation({
this.host,
this.username,
this.version = 'unknown',
this.apiVersion = 1,
});
}

View File

@@ -1,15 +0,0 @@
import 'package:paperless_mobile/core/type/types.dart';
class PaperlessStatistics {
final int documentsTotal;
final int documentsInInbox;
PaperlessStatistics({
required this.documentsTotal,
required this.documentsInInbox,
});
PaperlessStatistics.fromJson(JSON json)
: documentsTotal = json['documents_total'],
documentsInInbox = json['documents_inbox'];
}

View File

@@ -1,8 +1,8 @@
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
import 'package:paperless_api/paperless_api.dart';
class PaperlessStatisticsState {
final bool isLoaded;
final PaperlessStatistics? statistics;
final PaperlessServerStatisticsModel? statistics;
PaperlessStatisticsState({
required this.isLoaded,

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';
@@ -12,7 +12,7 @@ class FileService {
) async {
final dir = await documentsDirectory;
if (dir == null) {
throw const ErrorMessage.unknown(); //TODO: better handling
throw const PaperlessServerException.unknown(); //TODO: better handling
}
File file = File("${dir.path}/$filename");
return file..writeAsBytes(bytes);

View File

@@ -1,37 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/model/paperless_server_information.dart';
import 'package:paperless_mobile/core/store/local_vault.dart';
@injectable
class PaperlessServerInformationService {
final BaseClient client;
final LocalVault localStore;
PaperlessServerInformationService(
@Named("timeoutClient") this.client,
this.localStore,
);
Future<PaperlessServerInformation> getInformation() async {
final response = await client.get(Uri.parse("/api/ui_settings/"));
final version =
response.headers[PaperlessServerInformation.versionHeader] ?? 'unknown';
final apiVersion = int.tryParse(
response.headers[PaperlessServerInformation.apiVersionHeader] ?? '1');
final String username =
jsonDecode(utf8.decode(response.bodyBytes))['username'];
final String? host =
response.headers[PaperlessServerInformation.hostHeader] ??
response.request?.headers[PaperlessServerInformation.hostHeader] ??
('${response.request?.url.host}:${response.request?.url.port}');
return PaperlessServerInformation(
username: username,
version: version,
apiVersion: apiVersion,
host: host,
);
}
}

View File

@@ -1,29 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
import 'package:paperless_mobile/core/type/types.dart';
abstract class PaperlessStatisticsService {
Future<PaperlessStatistics> getStatistics();
}
@Injectable(as: PaperlessStatisticsService)
class PaperlessStatisticsServiceImpl extends PaperlessStatisticsService {
final BaseClient client;
PaperlessStatisticsServiceImpl(@Named('timeoutClient') this.client);
@override
Future<PaperlessStatistics> getStatistics() async {
final response = await client.get(Uri.parse('/api/statistics/'));
if (response.statusCode == 200) {
return PaperlessStatistics.fromJson(
jsonDecode(utf8.decode(response.bodyBytes)) as JSON,
);
}
throw const ErrorMessage.unknown();
}
}

View File

@@ -3,14 +3,13 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
import 'package:paperless_mobile/core/model/document_processing_status.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/paged_search_result.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
import 'package:paperless_mobile/util.dart';
import 'package:injectable/injectable.dart';
import 'package:web_socket_channel/io.dart';
abstract class StatusService {

View File

@@ -1,68 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:http/http.dart';
const requestTimeout = Duration(seconds: 5);
Future<T> getSingleResult<T>(
String url,
T Function(JSON) fromJson,
ErrorCode errorCode, {
int minRequiredApiVersion = 1,
}) async {
final httpClient = getIt<BaseClient>(instanceName: "timeoutClient");
final response = await httpClient.get(
Uri.parse(url),
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
);
if (response.statusCode == 200) {
return compute(
fromJson,
jsonDecode(utf8.decode(response.bodyBytes)) as JSON,
);
}
throw ErrorMessage(errorCode);
}
Future<List<T>> getCollection<T>(
String url,
T Function(JSON) fromJson,
ErrorCode errorCode, {
int minRequiredApiVersion = 1,
}) async {
final httpClient = getIt<BaseClient>(instanceName: "timeoutClient");
final response = await httpClient.get(
Uri.parse(url),
headers: {'accept': 'application/json; version=$minRequiredApiVersion'},
);
if (response.statusCode == 200) {
final JSON body = jsonDecode(utf8.decode(response.bodyBytes));
if (body.containsKey('count')) {
if (body['count'] == 0) {
return <T>[];
} else {
return compute(
_collectionFromJson,
_CollectionFromJsonSerializationParams(
fromJson, (body['results'] as List).cast<JSON>()),
);
}
}
}
throw ErrorMessage(errorCode);
}
List<T> _collectionFromJson<T>(
_CollectionFromJsonSerializationParams<T> params) {
return params.list.map<T>((result) => params.fromJson(result)).toList();
}
class _CollectionFromJsonSerializationParams<T> {
final T Function(JSON) fromJson;
final List<JSON> list;
_CollectionFromJsonSerializationParams(this.fromJson, this.list);
}