229 lines
6.2 KiB
Dart
229 lines
6.2 KiB
Dart
import '../models/file_item.dart';
|
|
import '../models/viewer_session.dart';
|
|
import '../models/editor_session.dart';
|
|
import '../models/annotation.dart';
|
|
import 'api_client.dart';
|
|
import 'package:dio/dio.dart';
|
|
|
|
class FileService {
|
|
final ApiClient _apiClient;
|
|
|
|
FileService(this._apiClient);
|
|
|
|
String get baseUrl => _apiClient.baseUrl;
|
|
|
|
Future<List<FileItem>> getFiles(String orgId, String path) async {
|
|
if (path.isEmpty) {
|
|
throw Exception('Path cannot be empty');
|
|
}
|
|
final pathParam = {'path': path};
|
|
if (orgId.isEmpty) {
|
|
return await _apiClient.getList(
|
|
'/user/files',
|
|
queryParameters: pathParam,
|
|
fromJson: (data) => FileItem(
|
|
id: data['id'],
|
|
name: data['name'],
|
|
path: data['path'],
|
|
type: data['type'] == 'file' ? FileType.file : FileType.folder,
|
|
size: data['size'],
|
|
lastModified: DateTime.parse(data['lastModified']),
|
|
),
|
|
);
|
|
}
|
|
|
|
return await _apiClient.getList(
|
|
'/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,
|
|
size: data['size'],
|
|
lastModified: DateTime.parse(data['lastModified']),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<FileItem?> getFile(String orgId, String path) async {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
Future<void> uploadFile(String orgId, FileItem file) async {
|
|
// If bytes or localPath available, send multipart upload with field 'file'
|
|
final Map<String, dynamic> fields = {'path': file.path};
|
|
FormData formData;
|
|
|
|
if (file.bytes != null) {
|
|
formData = FormData.fromMap({
|
|
...fields,
|
|
'file': MultipartFile.fromBytes(file.bytes!, filename: file.name),
|
|
});
|
|
} else if (file.localPath != null) {
|
|
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)
|
|
final data = {
|
|
'name': file.name,
|
|
'path': file.path,
|
|
'type': file.type == FileType.file ? 'file' : 'folder',
|
|
'size': file.size,
|
|
};
|
|
if (orgId.isEmpty) {
|
|
await _apiClient.post('/user/files', data: data, fromJson: (d) => null);
|
|
return;
|
|
}
|
|
await _apiClient.post(
|
|
'/orgs/$orgId/files',
|
|
data: data,
|
|
fromJson: (d) => null,
|
|
);
|
|
return;
|
|
}
|
|
|
|
final endpoint = orgId.isEmpty ? '/user/files' : '/orgs/$orgId/files';
|
|
await _apiClient.post(endpoint, data: formData, fromJson: (d) => null);
|
|
}
|
|
|
|
Future<void> deleteFile(String orgId, String path) async {
|
|
final data = {'path': path};
|
|
if (orgId.isEmpty) {
|
|
await _apiClient.post(
|
|
'/user/files/delete',
|
|
data: data,
|
|
fromJson: (d) => null,
|
|
);
|
|
return;
|
|
}
|
|
await _apiClient.post(
|
|
'/orgs/$orgId/files/delete',
|
|
data: data,
|
|
fromJson: (d) => null,
|
|
);
|
|
}
|
|
|
|
Future<String> getDownloadUrl(String orgId, String path) async {
|
|
// Return the download URL for the file
|
|
if (orgId.isEmpty) {
|
|
return '/user/files/download?path=${Uri.encodeComponent(path)}';
|
|
}
|
|
return '/orgs/$orgId/files/download?path=${Uri.encodeComponent(path)}';
|
|
}
|
|
|
|
Future<void> createFolder(
|
|
String orgId,
|
|
String parentPath,
|
|
String folderName,
|
|
) async {
|
|
// Normalize folder name to avoid accidental leading slashes creating double-slash paths
|
|
final normalizedName = folderName
|
|
.replaceAll(RegExp(r'^/+'), '')
|
|
.replaceAll(RegExp(r'/+$'), '');
|
|
if (normalizedName.isEmpty) {
|
|
throw Exception('Folder name cannot be empty');
|
|
}
|
|
|
|
// Construct proper path: /parent/folder or /folder for root
|
|
String path;
|
|
if (parentPath == '/') {
|
|
path = '/$normalizedName';
|
|
} else {
|
|
// Ensure parentPath doesn't end with / before appending
|
|
final cleanParent = parentPath.endsWith('/')
|
|
? parentPath.substring(0, parentPath.length - 1)
|
|
: parentPath;
|
|
path = '$cleanParent/$normalizedName';
|
|
}
|
|
|
|
final data = {
|
|
'name': normalizedName,
|
|
'path': path,
|
|
'type': 'folder',
|
|
'size': 0,
|
|
};
|
|
if (orgId.isEmpty) {
|
|
await _apiClient.post('/user/files', data: data, fromJson: (d) => null);
|
|
return;
|
|
}
|
|
await _apiClient.post(
|
|
'/orgs/$orgId/files',
|
|
data: data,
|
|
fromJson: (d) => null,
|
|
);
|
|
}
|
|
|
|
Future<void> moveFile(
|
|
String orgId,
|
|
String sourcePath,
|
|
String targetPath,
|
|
) async {
|
|
final endpoint = orgId.isEmpty
|
|
? '/user/files/move'
|
|
: '/orgs/$orgId/files/move';
|
|
await _apiClient.post(
|
|
endpoint,
|
|
data: {'sourcePath': sourcePath, 'targetPath': targetPath},
|
|
fromJson: (d) => null,
|
|
);
|
|
}
|
|
|
|
Future<void> renameFile(String orgId, String path, String newName) async {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
Future<List<FileItem>> searchFiles(String orgId, String query) async {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
Future<ViewerSession> requestViewerSession(
|
|
String orgId,
|
|
String fileId,
|
|
) async {
|
|
if (fileId.isEmpty) {
|
|
throw Exception('fileId cannot be empty');
|
|
}
|
|
final path = orgId.isEmpty
|
|
? '/user/files/$fileId/view'
|
|
: '/orgs/$orgId/files/$fileId/view';
|
|
return await _apiClient.get(
|
|
path,
|
|
fromJson: (data) => ViewerSession.fromJson(data),
|
|
);
|
|
}
|
|
|
|
Future<EditorSession> requestEditorSession(
|
|
String orgId,
|
|
String fileId,
|
|
) async {
|
|
if (orgId.isEmpty || fileId.isEmpty) {
|
|
throw Exception('OrgId and fileId cannot be empty');
|
|
}
|
|
return await _apiClient.get(
|
|
'/orgs/$orgId/files/$fileId/edit',
|
|
fromJson: (data) => EditorSession.fromJson(data),
|
|
);
|
|
}
|
|
|
|
Future<void> saveAnnotations(
|
|
String orgId,
|
|
String fileId,
|
|
List<Annotation> annotations,
|
|
) async {
|
|
if (orgId.isEmpty || fileId.isEmpty) {
|
|
throw Exception('OrgId and fileId cannot be empty');
|
|
}
|
|
await _apiClient.post(
|
|
'/orgs/$orgId/files/$fileId/annotations',
|
|
data: {
|
|
'annotations': annotations.map((a) => a.toJson()).toList(),
|
|
'baseVersionId': '1', // mock
|
|
},
|
|
fromJson: (data) => null,
|
|
);
|
|
}
|
|
}
|