diff --git a/b0esche_cloud/lib/repositories/http_file_repository.dart b/b0esche_cloud/lib/repositories/http_file_repository.dart index fad08d3..7ec0c37 100644 --- a/b0esche_cloud/lib/repositories/http_file_repository.dart +++ b/b0esche_cloud/lib/repositories/http_file_repository.dart @@ -49,7 +49,7 @@ class HttpFileRepository implements FileRepository { String sourcePath, String targetPath, ) async { - throw UnimplementedError(); + await _fileService.moveFile(orgId, sourcePath, targetPath); } @override diff --git a/b0esche_cloud/lib/services/file_service.dart b/b0esche_cloud/lib/services/file_service.dart index 362b20c..e60da98 100644 --- a/b0esche_cloud/lib/services/file_service.dart +++ b/b0esche_cloud/lib/services/file_service.dart @@ -161,7 +161,16 @@ class FileService { String sourcePath, String targetPath, ) async { - throw UnimplementedError(); + final response = await apiClient.post( + '/orgs/$orgId/files/move', + data: { + 'sourcePath': sourcePath, + 'targetPath': targetPath, + }, + ); + if (response.statusCode != 200) { + throw Exception('Failed to move file: ${response.statusCode}'); + } } Future renameFile(String orgId, String path, String newName) async { diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 3a7b67f..254cdaa 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -190,6 +190,10 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Delete("/files", func(w http.ResponseWriter, req *http.Request) { deleteOrgFileHandler(w, req, db, auditLogger, cfg) }) + // Move file/folder in org workspace (body: {"sourcePath":"/old", "targetPath":"/new"}) + r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Post("/files/move", func(w http.ResponseWriter, req *http.Request) { + moveOrgFileHandler(w, req, db, auditLogger, cfg) + }) r.Route("/files/{fileId}", func(r chi.Router) { r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) { viewerHandler(w, req, db, jwtManager, auditLogger) @@ -1275,6 +1279,83 @@ func deleteOrgFilePostHandler(w http.ResponseWriter, r *http.Request, db *databa deleteOrgFileHandler(w, r, db, auditLogger, cfg) } +// moveOrgFileHandler moves/renames a file in org workspace +func moveOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) { + orgID := r.Context().Value(middleware.OrgKey).(uuid.UUID) + userIDStr, _ := middleware.GetUserID(r.Context()) + userID, _ := uuid.Parse(userIDStr) + + var req struct { + SourcePath string `json:"sourcePath"` + TargetPath string `json:"targetPath"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + errors.WriteError(w, errors.CodeInvalidArgument, "Bad request", http.StatusBadRequest) + return + } + + // Get or create user's WebDAV client and move in Nextcloud + storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass) + if err != nil { + errors.LogError(r, err, "Failed to get user WebDAV client (continuing with database move)") + } else { + sourceRel := strings.TrimPrefix(req.SourcePath, "/") + sourcePath := path.Join("/orgs", orgID.String(), sourceRel) + targetRel := strings.TrimPrefix(req.TargetPath, "/") + targetPath := path.Join("/orgs", orgID.String(), targetRel) + if err := storageClient.Move(r.Context(), sourcePath, targetPath); err != nil { + errors.LogError(r, err, "Failed to move in Nextcloud (continuing with database move)") + } + } + + // Move in database: copy file with new path and delete old one + sourceFile, err := db.GetFileByPath(r.Context(), &orgID, nil, req.SourcePath) + if err != nil { + errors.LogError(r, err, "Failed to get source file") + errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + return + } + + // Determine new file name + var newPath string + if strings.HasSuffix(req.TargetPath, "/") { + // Moving into a folder + newPath = path.Join(req.TargetPath, sourceFile.Name) + } else { + // Moving/renaming to a specific path + newPath = req.TargetPath + } + + // Create new file entry with updated path + movedFile := &database.File{ + ID: sourceFile.ID, + OrgID: sourceFile.OrgID, + Path: newPath, + Name: path.Base(newPath), + Size: sourceFile.Size, + MimeType: sourceFile.MimeType, + IsFolder: sourceFile.IsFolder, + } + + // Update in database + if err := db.UpdateFile(r.Context(), movedFile); err != nil { + errors.LogError(r, err, "Failed to update file path") + errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + return + } + + auditLogger.Log(r.Context(), audit.Entry{ + UserID: &userID, + OrgID: &orgID, + Action: "move_file", + Success: true, + }) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status":"ok"}`)) +} + + // createUserFileHandler creates a file or folder record for the authenticated user's personal workspace. func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) { userIDStr, ok := middleware.GetUserID(r.Context())