full stack second commit

This commit is contained in:
Leon Bösche
2025-12-18 00:11:30 +01:00
parent b35adc3d06
commit 87ee5f2ae3
16 changed files with 472 additions and 99 deletions

View File

@@ -26,41 +26,40 @@ class DocumentViewerBloc
event.orgId,
event.fileId,
);
if (session.expiresAt.isBefore(DateTime.now())) {
emit(DocumentViewerSessionExpired());
return;
}
emit(
DocumentViewerReady(
viewUrl: session.viewUrl,
caps: session.capabilities,
),
);
if (session.expiresAt.isAfter(DateTime.now())) {
_expiryTimer = Timer(
session.expiresAt.difference(DateTime.now()),
() => emit(DocumentViewerSessionExpired()),
);
}
_expiryTimer = Timer(
session.expiresAt.difference(DateTime.now()),
() => emit(DocumentViewerSessionExpired()),
);
} catch (e) {
if (e is ApiError) {
switch (e.code) {
case 'unauthorized':
case 'UNAUTHENTICATED':
// Already handled by ApiClient
break;
case 'permission_denied':
case 'PERMISSION_DENIED':
emit(
DocumentViewerError(
message: 'You don\'t have access to this document.',
),
);
break;
case 'not_found':
case 'NOT_FOUND':
emit(
DocumentViewerError(
message: 'This document no longer exists or was moved.',
),
);
break;
case 'not_found':
emit(DocumentViewerError(message: 'Document not found'));
break;
default:
emit(
DocumentViewerError(

View File

@@ -3,6 +3,7 @@ import 'package:bloc/bloc.dart';
import 'editor_session_event.dart';
import 'editor_session_state.dart';
import '../../services/file_service.dart';
import '../../models/api_error.dart';
class EditorSessionBloc extends Bloc<EditorSessionEvent, EditorSessionState> {
final FileService _fileService;
@@ -15,6 +16,27 @@ class EditorSessionBloc extends Bloc<EditorSessionEvent, EditorSessionState> {
on<EditorSessionEnded>(_onEditorSessionEnded);
}
String _getErrorMessage(dynamic error) {
if (error is ApiError) {
switch (error.code) {
case 'UNAUTHENTICATED':
return 'Session expired. Please log in again.';
case 'PERMISSION_DENIED':
return 'You do not have permission to edit this file.';
case 'NOT_FOUND':
return 'The file was not found.';
case 'CONFLICT':
return 'The file has been modified. Please refresh and try again.';
case 'INVALID_ARGUMENT':
return 'Invalid request.';
case 'INTERNAL':
default:
return 'An error occurred while opening the editor.';
}
}
return error.toString();
}
void _onEditorSessionStarted(
EditorSessionStarted event,
Emitter<EditorSessionState> emit,
@@ -25,19 +47,21 @@ class EditorSessionBloc extends Bloc<EditorSessionEvent, EditorSessionState> {
event.orgId,
event.fileId,
);
if (session.expiresAt.isBefore(DateTime.now())) {
emit(EditorSessionExpired());
return;
}
if (!session.readOnly) {
emit(EditorSessionActive(editUrl: session.editUrl));
} else {
emit(EditorSessionReadOnly(viewUrl: session.editUrl));
}
if (session.expiresAt.isAfter(DateTime.now())) {
_expiryTimer = Timer(
session.expiresAt.difference(DateTime.now()),
() => emit(EditorSessionExpired()),
);
}
_expiryTimer = Timer(
session.expiresAt.difference(DateTime.now()),
() => add(EditorSessionEnded()), // Or emit expired
);
} catch (e) {
emit(EditorSessionFailed(message: e.toString()));
emit(EditorSessionFailed(message: _getErrorMessage(e)));
}
}

View File

@@ -3,6 +3,7 @@ import 'file_browser_event.dart';
import 'file_browser_state.dart';
import '../../services/file_service.dart';
import '../../models/file_item.dart';
import '../../models/api_error.dart';
import 'package:path/path.dart' as p;
class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
@@ -33,6 +34,27 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
on<SearchFiles>(_onSearchFiles);
}
String _getErrorMessage(dynamic error) {
if (error is ApiError) {
switch (error.code) {
case 'UNAUTHENTICATED':
return 'Session expired. Please log in again.';
case 'PERMISSION_DENIED':
return 'You do not have access to this folder.';
case 'NOT_FOUND':
return 'The requested folder or file was not found.';
case 'CONFLICT':
return 'A conflict occurred. Please try again.';
case 'INVALID_ARGUMENT':
return 'Invalid input provided.';
case 'INTERNAL':
default:
return 'An internal error occurred. Please try again later.';
}
}
return error.toString();
}
void _onLoadDirectory(
LoadDirectory event,
Emitter<FileBrowserState> emit,
@@ -52,7 +74,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
_emitLoadedState(emit);
}
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}
@@ -120,7 +142,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
}
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}
@@ -143,7 +165,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
_emitLoadedState(emit);
}
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}
@@ -163,7 +185,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
_emitLoadedState(emit);
}
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}
@@ -176,7 +198,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
.toList();
_emitLoadedState(emit);
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}
@@ -219,7 +241,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
_emitLoadedState(emit);
}
} catch (e) {
emit(DirectoryError(e.toString()));
emit(DirectoryError(_getErrorMessage(e)));
}
}

View File

@@ -9,6 +9,7 @@ import '../file_browser/file_browser_event.dart';
import '../upload/upload_bloc.dart';
import '../upload/upload_event.dart';
import '../../services/org_api.dart';
import '../../models/api_error.dart';
class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
final PermissionBloc permissionBloc;
@@ -27,6 +28,27 @@ class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
on<CreateOrganization>(_onCreateOrganization);
}
String _getErrorMessage(dynamic error) {
if (error is ApiError) {
switch (error.code) {
case 'UNAUTHENTICATED':
return 'Session expired. Please log in again.';
case 'PERMISSION_DENIED':
return 'You do not have permission to perform this action.';
case 'NOT_FOUND':
return 'The requested resource was not found.';
case 'CONFLICT':
return 'A conflict occurred. Please try again.';
case 'INVALID_ARGUMENT':
return 'Invalid input provided.';
case 'INTERNAL':
default:
return 'An internal error occurred. Please try again later.';
}
}
return error.toString();
}
void _onLoadOrganizations(
LoadOrganizations event,
Emitter<OrganizationState> emit,
@@ -41,7 +63,7 @@ class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
),
);
} catch (e) {
emit(OrganizationError(e.toString()));
emit(OrganizationError(_getErrorMessage(e)));
}
}
@@ -124,7 +146,7 @@ class OrganizationBloc extends Bloc<OrganizationEvent, OrganizationState> {
organizations: currentState.organizations,
selectedOrg: currentState.selectedOrg,
isLoading: false,
error: e.toString(),
error: _getErrorMessage(e),
),
);
}

View File

@@ -3,6 +3,7 @@ import 'pdf_annotation_event.dart';
import 'pdf_annotation_state.dart';
import '../../services/file_service.dart';
import '../../models/annotation.dart';
import '../../models/api_error.dart';
class PdfAnnotationBloc extends Bloc<PdfAnnotationEvent, PdfAnnotationState> {
final FileService _fileService;
@@ -22,6 +23,20 @@ class PdfAnnotationBloc extends Bloc<PdfAnnotationEvent, PdfAnnotationState> {
on<AnnotationsSaved>(_onAnnotationsSaved);
}
String _getErrorMessage(dynamic error) {
if (error is ApiError) {
switch (error.code) {
case 'CONFLICT':
return 'The document has changed. Please reload to see the latest version.';
case 'PERMISSION_DENIED':
return 'You do not have permission to annotate this document.';
default:
return error.message;
}
}
return error.toString();
}
void _onAnnotationToolSelected(
AnnotationToolSelected event,
Emitter<PdfAnnotationState> emit,
@@ -107,7 +122,7 @@ class PdfAnnotationBloc extends Bloc<PdfAnnotationEvent, PdfAnnotationState> {
// Reset to idle or editing?
emit(PdfAnnotationIdle());
} catch (e) {
emit(PdfAnnotationError(message: e.toString()));
emit(PdfAnnotationError(message: _getErrorMessage(e)));
}
}
}

View File

@@ -1,3 +1,4 @@
import 'package:b0esche_cloud/services/api_client.dart';
import 'package:get_it/get_it.dart';
import 'repositories/auth_repository.dart';
import 'repositories/file_repository.dart';
@@ -17,7 +18,7 @@ void configureDependencies() {
// Register services
getIt.registerSingleton<AuthService>(AuthService(getIt<AuthRepository>()));
getIt.registerSingleton<FileService>(FileService(getIt<FileRepository>()));
getIt.registerSingleton<FileService>(FileService(getIt<ApiClient>()));
// Register viewmodels
getIt.registerSingleton<LoginViewModel>(LoginViewModel(getIt<AuthService>()));

View File

@@ -118,36 +118,21 @@ class ApiClient {
ApiError _handleError(DioException e) {
final status = e.response?.statusCode;
final data = e.response?.data;
if (status == 403) {
// Handle network errors
if (e.type == DioExceptionType.connectionError ||
e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.sendTimeout) {
return ApiError(
code: 'permission_denied',
message: 'Access denied',
status: status,
);
} else if (status == 404) {
return ApiError(
code: 'not_found',
message: 'Resource not found',
status: status,
);
} else if (status == 409) {
return ApiError(
code: 'conflict',
message: 'Version conflict',
status: status,
);
} else if (status == 401) {
return ApiError(
code: 'unauthorized',
message: 'Unauthorized',
status: status,
);
} else {
return ApiError(
code: 'server_error',
message: data?['message'] ?? 'Server error',
code: 'NETWORK_ERROR',
message: 'Network error. Please check your connection and try again.',
status: status,
);
}
String code = data?['code'] ?? 'UNKNOWN';
String message = data?['message'] ?? 'Unknown error';
return ApiError(code: code, message: message, status: status);
}
}

View File

@@ -20,15 +20,6 @@ class MockFileService extends Mock implements FileService {
_viewerResponse = null;
}
@override
Future<ViewerSession> requestViewerSession(String orgId, String fileId) {
return _viewerResponse ?? super.noSuchMethod(
Invocation.method(#requestViewerSession, [orgId, fileId]),
returnValue: Future.value(null),
);
}
}
@override
Future<ViewerSession> requestViewerSession(String orgId, String fileId) {
return _viewerResponse ??
@@ -39,6 +30,16 @@ class MockFileService extends Mock implements FileService {
}
}
// @override
// Future<ViewerSession> requestViewerSession(String orgId, String fileId) {
// return _viewerResponse ??
// super.noSuchMethod(
// Invocation.method(#requestViewerSession, [orgId, fileId]),
// returnValue: Future.value(null),
// );
// }
// }
void main() {
late MockFileService mockFileService;