From ca39b3dee47141fc4a1e0a4d05e7ac680acd5e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sat, 10 Jan 2026 00:26:34 +0100 Subject: [PATCH] Add file ID support to FileItem and update related components for consistency --- b0esche_cloud/lib/models/file_item.dart | 6 ++++- b0esche_cloud/lib/pages/file_explorer.dart | 20 ++++++++++++--- .../repositories/mock_file_repository.dart | 25 ++++++++++++++++++- b0esche_cloud/lib/services/file_service.dart | 17 ++----------- go_cloud/internal/http/routes.go | 2 ++ 5 files changed, 49 insertions(+), 21 deletions(-) diff --git a/b0esche_cloud/lib/models/file_item.dart b/b0esche_cloud/lib/models/file_item.dart index 2a160dd..76fac09 100644 --- a/b0esche_cloud/lib/models/file_item.dart +++ b/b0esche_cloud/lib/models/file_item.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; enum FileType { folder, file } class FileItem extends Equatable { + final String? id; final String name; final String path; final FileType type; @@ -13,6 +14,7 @@ class FileItem extends Equatable { final Uint8List? bytes; // optional file bytes for web/desktop uploads const FileItem({ + this.id, required this.name, required this.path, required this.type, @@ -23,9 +25,10 @@ class FileItem extends Equatable { }); @override - List get props => [name, path, type, size, lastModified]; + List get props => [id, name, path, type, size, lastModified]; FileItem copyWith({ + String? id, String? name, String? path, FileType? type, @@ -33,6 +36,7 @@ class FileItem extends Equatable { DateTime? lastModified, }) { return FileItem( + id: id ?? this.id, name: name ?? this.name, path: path ?? this.path, type: type ?? this.type, diff --git a/b0esche_cloud/lib/pages/file_explorer.dart b/b0esche_cloud/lib/pages/file_explorer.dart index 24df38d..c445fc7 100644 --- a/b0esche_cloud/lib/pages/file_explorer.dart +++ b/b0esche_cloud/lib/pages/file_explorer.dart @@ -560,10 +560,13 @@ class _FileExplorerState extends State { if (file.type == FileType.folder) { context.read().add(NavigateToFolder(file.path)); } else { - final fileId = file.path.startsWith('/') - ? file.path.substring(1) - : file.path; - _showDocumentViewer(widget.orgId, fileId); + if (file.id == null || file.id!.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Error: File ID is missing')), + ); + return; + } + _showDocumentViewer(widget.orgId, file.id!); } }, child: Container( @@ -728,9 +731,18 @@ class _FileExplorerState extends State { children: [ ModernGlassButton( onPressed: () async { + print( + '[FileExplorer-Empty] Upload clicked, currentPath=${state.currentPath}', + ); final result = await FilePicker.platform .pickFiles(withData: true); if (result != null && result.files.isNotEmpty) { + print( + '[FileExplorer-Empty] Selected ${result.files.length} files', + ); + print( + '[FileExplorer-Empty] Will upload to: ${state.currentPath}', + ); final files = result.files .map( (file) => FileItem( diff --git a/b0esche_cloud/lib/repositories/mock_file_repository.dart b/b0esche_cloud/lib/repositories/mock_file_repository.dart index c001de1..89d2559 100644 --- a/b0esche_cloud/lib/repositories/mock_file_repository.dart +++ b/b0esche_cloud/lib/repositories/mock_file_repository.dart @@ -6,9 +6,11 @@ import '../models/document_capabilities.dart'; import '../models/api_error.dart'; import '../repositories/file_repository.dart'; import 'package:path/path.dart' as p; +import 'package:uuid/uuid.dart'; class MockFileRepository implements FileRepository { final Map> _orgFiles = {}; + final _uuid = const Uuid(); List _getFilesForOrg(String orgId) { if (!_orgFiles.containsKey(orgId)) { @@ -16,18 +18,21 @@ class MockFileRepository implements FileRepository { if (orgId == 'org1') { _orgFiles[orgId] = [ FileItem( + id: _uuid.v4(), name: 'Personal Documents', path: '/Personal Documents', type: FileType.folder, lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'Photos', path: '/Photos', type: FileType.folder, lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'resume.pdf', path: '/resume.pdf', type: FileType.file, @@ -35,6 +40,7 @@ class MockFileRepository implements FileRepository { lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'notes.txt', path: '/notes.txt', type: FileType.file, @@ -45,12 +51,14 @@ class MockFileRepository implements FileRepository { } else if (orgId == 'org2') { _orgFiles[orgId] = [ FileItem( + id: _uuid.v4(), name: 'Company Reports', path: '/Company Reports', type: FileType.folder, lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'annual_report.pdf', path: '/annual_report.pdf', type: FileType.file, @@ -58,6 +66,7 @@ class MockFileRepository implements FileRepository { lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'presentation.pptx', path: '/presentation.pptx', type: FileType.file, @@ -68,12 +77,14 @@ class MockFileRepository implements FileRepository { } else if (orgId == 'org3') { _orgFiles[orgId] = [ FileItem( + id: _uuid.v4(), name: 'Project Code', path: '/Project Code', type: FileType.folder, lastModified: DateTime.now(), ), FileItem( + id: _uuid.v4(), name: 'side_project.dart', path: '/side_project.dart', type: FileType.file, @@ -153,6 +164,7 @@ class MockFileRepository implements FileRepository { final files = _getFilesForOrg(orgId); files.add( FileItem( + id: _uuid.v4(), name: normalizedName, path: newPath, type: FileType.folder, @@ -175,6 +187,7 @@ class MockFileRepository implements FileRepository { final newName = file.path.split('/').last; final newPath = targetPath == '/' ? '/$newName' : '$targetPath/$newName'; files[fileIndex] = FileItem( + id: file.id, name: file.name, path: newPath, type: file.type, @@ -194,6 +207,7 @@ class MockFileRepository implements FileRepository { final parentPath = p.dirname(path); final newPath = parentPath == '.' ? '/$newName' : '$parentPath/$newName'; files[fileIndex] = FileItem( + id: file.id, name: newName, path: newPath, type: file.type, @@ -216,7 +230,16 @@ class MockFileRepository implements FileRepository { Future uploadFile(String orgId, FileItem file) async { await Future.delayed(const Duration(seconds: 1)); final files = _getFilesForOrg(orgId); - files.add(file); + files.add( + FileItem( + id: _uuid.v4(), + name: file.name, + path: file.path, + type: file.type, + size: file.size, + lastModified: file.lastModified, + ), + ); } @override diff --git a/b0esche_cloud/lib/services/file_service.dart b/b0esche_cloud/lib/services/file_service.dart index a3ee43e..e4c454d 100644 --- a/b0esche_cloud/lib/services/file_service.dart +++ b/b0esche_cloud/lib/services/file_service.dart @@ -22,6 +22,7 @@ class FileService { '/user/files', queryParameters: pathParam, fromJson: (data) => FileItem( + id: data['id'], name: data['name'], path: data['path'], type: data['type'] == 'file' ? FileType.file : FileType.folder, @@ -35,6 +36,7 @@ class FileService { '/orgs/$orgId/files', queryParameters: pathParam, fromJson: (data) => FileItem( + id: data['id'], name: data['name'], path: data['path'], type: data['type'] == 'file' ? FileType.file : FileType.folder, @@ -52,32 +54,19 @@ class FileService { // If bytes or localPath available, send multipart upload with field 'file' final Map fields = {'path': file.path}; FormData formData; - print( - '[FileService] uploadFile: file=${file.name}, path=${file.path}, orgId=$orgId', - ); - print( - '[FileService] bytes=${file.bytes?.length ?? 0}, localPath=${file.localPath}', - ); if (file.bytes != null) { - print( - '[FileService] Using bytes for upload (${file.bytes!.length} bytes)', - ); formData = FormData.fromMap({ ...fields, 'file': MultipartFile.fromBytes(file.bytes!, filename: file.name), }); } else if (file.localPath != null) { - print('[FileService] Using localPath for upload: ${file.localPath}'); formData = FormData.fromMap({ ...fields, 'file': MultipartFile.fromFile(file.localPath!, filename: file.name), }); } else { // Fallback to metadata-only create (folders or client that can't send file content) - print( - '[FileService] No bytes or localPath; falling back to metadata-only', - ); final data = { 'name': file.name, 'path': file.path, @@ -97,9 +86,7 @@ class FileService { } final endpoint = orgId.isEmpty ? '/user/files' : '/orgs/$orgId/files'; - print('[FileService] Uploading to endpoint: $endpoint'); await _apiClient.post(endpoint, data: formData, fromJson: (d) => null); - print('[FileService] Upload completed for ${file.name}'); } Future deleteFile(String orgId, String path) async { diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 3cef112..6939e2d 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -338,6 +338,7 @@ func listFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) { out := make([]map[string]interface{}, 0, len(files)) for _, f := range files { out = append(out, map[string]interface{}{ + "id": f.ID.String(), "name": f.Name, "path": f.Path, "type": f.Type, @@ -884,6 +885,7 @@ func userFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) { out := make([]map[string]interface{}, 0, len(files)) for _, f := range files { out = append(out, map[string]interface{}{ + "id": f.ID.String(), "name": f.Name, "path": f.Path, "type": f.Type,