FEATURE: Implement file drag-and-drop move functionality
This commit is contained in:
@@ -49,7 +49,7 @@ class HttpFileRepository implements FileRepository {
|
|||||||
String sourcePath,
|
String sourcePath,
|
||||||
String targetPath,
|
String targetPath,
|
||||||
) async {
|
) async {
|
||||||
throw UnimplementedError();
|
await _fileService.moveFile(orgId, sourcePath, targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -161,7 +161,16 @@ class FileService {
|
|||||||
String sourcePath,
|
String sourcePath,
|
||||||
String targetPath,
|
String targetPath,
|
||||||
) async {
|
) 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<void> renameFile(String orgId, String path, String newName) async {
|
Future<void> renameFile(String orgId, String path, String newName) async {
|
||||||
|
|||||||
@@ -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) {
|
r.With(middleware.Permission(db, auditLogger, permission.FileWrite)).Delete("/files", func(w http.ResponseWriter, req *http.Request) {
|
||||||
deleteOrgFileHandler(w, req, db, auditLogger, cfg)
|
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.Route("/files/{fileId}", func(r chi.Router) {
|
||||||
r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) {
|
r.With(middleware.Permission(db, auditLogger, permission.DocumentView)).Get("/view", func(w http.ResponseWriter, req *http.Request) {
|
||||||
viewerHandler(w, req, db, jwtManager, auditLogger)
|
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)
|
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.
|
// 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) {
|
func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, auditLogger *audit.Logger, cfg *config.Config) {
|
||||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||||
|
|||||||
Reference in New Issue
Block a user