diff --git a/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.html b/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.html index c43553b..ce91847 100644 --- a/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.html +++ b/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.html @@ -9,6 +9,7 @@ diff --git a/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.ts b/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.ts index 51da533..6ca6800 100644 --- a/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.ts +++ b/jambotron-ui/src/app/components/file-browser-with-tree-component/file-browser-with-tree-component.ts @@ -1,4 +1,4 @@ -import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit, ViewChild} from '@angular/core'; +import {Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit, ViewChild} from '@angular/core'; import {DatePipe, NgForOf, NgIf} from '@angular/common'; import { MatCell, @@ -83,11 +83,18 @@ export class FileBrowserWithTreeComponent implements OnInit { searchQuery = ''; filteredFiles: FileItem[] = []; + @Input() baseUrl: string = ''; + constructor( private fileService: FileBrowserTreeService, private dialog: MatDialog, private snackBar: MatSnackBar - ) {} + ) { + if(this.baseUrl.length>0){ + fileService.baseUrl = this.baseUrl; + } + + } ngOnInit(): void { this.loadFiles(this.currentPath); diff --git a/jambotron-ui/src/app/components/file-system-tree-component/file-system-tree-component.ts b/jambotron-ui/src/app/components/file-system-tree-component/file-system-tree-component.ts index 550d7f7..9850b32 100644 --- a/jambotron-ui/src/app/components/file-system-tree-component/file-system-tree-component.ts +++ b/jambotron-ui/src/app/components/file-system-tree-component/file-system-tree-component.ts @@ -7,8 +7,8 @@ import { inject, signal, Output, - EventEmitter - } from '@angular/core'; + EventEmitter, Input, OnInit +} from '@angular/core'; import {BehaviorSubject, merge, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {MatProgressBarModule} from '@angular/material/progress-bar'; @@ -38,19 +38,24 @@ class DynamicFlatNode { @Injectable({providedIn: 'root'}) export class DynamicDatabase { - rootNodes:FileItem[] = []; constructor(private fileBrowserService: FileBrowserTreeService) { - this.getFileItems('/').subscribe( - value => { - console.log('value', value); - this.rootNodes = value; - }, - error => { - console.error('Error loading files:', error); - } - ) + // this.getFileItems('/').subscribe( + // value => { + // console.log('value', value); + // this.rootNodes = value; + // }, + // error => { + // console.error('Error loading files:', error); + // } + // ) + + //this.fileBrowserService.baseUrl + } + + setBaseUrl(baseUrl: string) { + this.fileBrowserService.baseUrl = baseUrl; } getFileItems(path:string):Observable { @@ -182,7 +187,7 @@ export class DynamicDataSource implements DataSource { ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class FileSystemTreeComponent { +export class FileSystemTreeComponent implements OnInit{ database = inject(DynamicDatabase); treeControl: FlatTreeControl; dataSource: DynamicDataSource; @@ -190,12 +195,23 @@ export class FileSystemTreeComponent { @Output() folderSelected = new EventEmitter(); + @Input() baseUrl: string = ''; + constructor() { + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); this.dataSource = new DynamicDataSource(this.treeControl, this.database); + + } + + ngOnInit(): void { + if(this.baseUrl.length>0){ + this.database.setBaseUrl(this.baseUrl); + } + // Initialize root data this.refresh(); } diff --git a/jambotron-ui/src/app/modules/admin-module/admin.routing.ts b/jambotron-ui/src/app/modules/admin-module/admin.routing.ts index 68bb0ac..22d8173 100644 --- a/jambotron-ui/src/app/modules/admin-module/admin.routing.ts +++ b/jambotron-ui/src/app/modules/admin-module/admin.routing.ts @@ -26,7 +26,7 @@ export const ADMIN_ROUTES: Routes = [ { path: 'users', loadComponent: () => import('../admin-module/users.component/users.component').then((c) => c.UsersComponent), - data:{title: 'Users', icon:'', nav:true} + data:{title: 'Users', icon:'manage_accounts', nav:true} }, { path: 'hard-drive-browser', diff --git a/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.html b/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.html index cdc5540..9579420 100644 --- a/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.html +++ b/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.html @@ -1 +1,3 @@ - + diff --git a/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.ts b/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.ts index 5cadb59..0decc04 100644 --- a/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.ts +++ b/jambotron-ui/src/app/modules/admin-module/hard-drive-browser.component/hard-drive-browser.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { FileBrowserWithTreeComponent } from '../../../components/file-browser-with-tree-component/file-browser-with-tree-component'; +import {GlobalConstants} from '../../../global-constants'; @Component({ selector: 'app-hard-drive-browser.component', @@ -12,5 +13,6 @@ import { styleUrl: './hard-drive-browser.component.scss' }) export class HardDriveBrowserComponent { + baseUrl = `${GlobalConstants.API_URL}/admin/file-browser-tree`; } diff --git a/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.html b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.html new file mode 100644 index 0000000..9579420 --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.html @@ -0,0 +1,3 @@ + diff --git a/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.scss b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.spec.ts b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.spec.ts new file mode 100644 index 0000000..ca87df2 --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilesBrowserComponent } from './files-browser.component'; + +describe('FilesBrowserComponent', () => { + let component: FilesBrowserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FilesBrowserComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FilesBrowserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.ts b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.ts new file mode 100644 index 0000000..5d1bcc6 --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/files-browser.component/files-browser.component.ts @@ -0,0 +1,21 @@ +import {Component, OnInit} from '@angular/core'; +import { + FileBrowserWithTreeComponent +} from '../../../components/file-browser-with-tree-component/file-browser-with-tree-component'; +import {GlobalConstants} from '../../../global-constants'; + +@Component({ + selector: 'app-files-browser.component', + imports: [ + FileBrowserWithTreeComponent + ], + templateUrl: './files-browser.component.html', + styleUrl: './files-browser.component.scss' +}) +export class FilesBrowserComponent implements OnInit{ + baseUrl: string =''; + ngOnInit(): void { + this.baseUrl= `${GlobalConstants.API_URL}/user/file-browser-tree`; + } + +} diff --git a/jambotron-ui/src/app/modules/user-module/user.routing.ts b/jambotron-ui/src/app/modules/user-module/user.routing.ts index 52fdf7f..7117fc4 100644 --- a/jambotron-ui/src/app/modules/user-module/user.routing.ts +++ b/jambotron-ui/src/app/modules/user-module/user.routing.ts @@ -31,6 +31,11 @@ export const USER_ROUTS: Routes = [ loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent), data:{title: 'Edit Tutorial'} }, + { + path: 'files-browser', + loadComponent: ()=>import('../user-module/files-browser.component/files-browser.component').then((c)=>c.FilesBrowserComponent), + data:{title: 'Files Browser', icon:'folder_data', nav:true} + }, { path: 'ai-models', loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent), diff --git a/jambotron-ui/src/app/services/file-browser-tree.service.ts b/jambotron-ui/src/app/services/file-browser-tree.service.ts index cb9763f..eba9298 100644 --- a/jambotron-ui/src/app/services/file-browser-tree.service.ts +++ b/jambotron-ui/src/app/services/file-browser-tree.service.ts @@ -16,23 +16,16 @@ export interface FileItem { providedIn: 'root' }) export class FileBrowserTreeService { - private baseUrl = `${GlobalConstants.API_URL}/file-browser-tree/files`; + public baseUrl = `${GlobalConstants.API_URL}/user/file-browser-tree`; constructor(private http: HttpClient) {} getFiles(path: string): Observable { const encodedPath = encodeURIComponent(path); - return this.http.get(`${this.baseUrl}?path=${encodedPath}`); + return this.http.get(`${this.baseUrl}/files?path=${encodedPath}`); } - // New method to get only folders for tree view - getFolders(path: string): Observable { - const encodedPath = encodeURIComponent(path); - return this.http.get(`${this.baseUrl}?path=${encodedPath}`) - .pipe( - map(files => files.filter(file => file.type === 'directory')) - ); - } + // Alternative: dedicated backend endpoint for folders only getFoldersOnly(path: string): Observable { diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeController.java index 898c040..3f79271 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeController.java @@ -1,9 +1,12 @@ package com.jambotronGroup.jambotron.controllers; import com.jambotronGroup.jambotron.fileBrowserTree.CreateFolderRequest; +import com.jambotronGroup.jambotron.fileBrowserTree.FileBrowserTreeExtendedService; import com.jambotronGroup.jambotron.fileBrowserTree.FileItemDto; import com.jambotronGroup.jambotron.fileBrowserTree.FileBrowserTreeService; import com.jambotronGroup.jambotron.fileUpload.ResponseMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; @@ -15,19 +18,24 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.List; + @RestController -@RequestMapping("/api/file-browser-tree/files") +@RequestMapping("/api/user/file-browser-tree") public class FileBrowserTreeController { + private final Logger _logger = LoggerFactory.getLogger(FileBrowserTreeController.class); @Autowired private FileBrowserTreeService fileBrowserTreeService; + @GetMapping + @RequestMapping("/files") public ResponseEntity> getFiles(@RequestParam String path) { try { List files = fileBrowserTreeService.listFiles(path); return ResponseEntity.ok(files); } catch (Exception e) { + _logger.error("Error retrieving files from path: {}", path, e.getMessage()); return ResponseEntity.badRequest().build(); } } @@ -39,10 +47,12 @@ public class FileBrowserTreeController { List folders = fileBrowserTreeService.listFolders(path); return ResponseEntity.ok(folders); } catch (Exception e) { + _logger.error("Error retrieving folders from path: {}", path, e.getMessage()); return ResponseEntity.badRequest().build(); } } + // Search endpoint @GetMapping("/search") public ResponseEntity> searchFiles( @@ -52,6 +62,7 @@ public class FileBrowserTreeController { List files = fileBrowserTreeService.searchFiles(path, query); return ResponseEntity.ok(files); } catch (Exception e) { + _logger.error("Error searching files in path: {} with query: {}", path, query, e.getMessage()); return ResponseEntity.badRequest().build(); } } @@ -68,6 +79,7 @@ public class FileBrowserTreeController { "attachment; filename=\"" + filename + "\"") .body(resource); } catch (Exception e) { + _logger.error("Error downloading file from path: {}", path, e.getMessage()); return ResponseEntity.notFound().build(); } } @@ -80,6 +92,7 @@ public class FileBrowserTreeController { fileBrowserTreeService.storeFile(file, path); return ResponseEntity.ok(new ResponseMessage("File uploaded successfully: " + file.getOriginalFilename())); } catch (IOException e) { + _logger.error("Error uploading file: {}", file.getOriginalFilename(), e.getMessage()); return ResponseEntity.badRequest() .body(new ResponseMessage("Failed to upload file: " + e.getMessage())); } diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeExtendedController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeExtendedController.java new file mode 100644 index 0000000..4083686 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/FileBrowserTreeExtendedController.java @@ -0,0 +1,146 @@ +package com.jambotronGroup.jambotron.controllers; + +import com.jambotronGroup.jambotron.fileBrowserTree.CreateFolderRequest; +import com.jambotronGroup.jambotron.fileBrowserTree.FileBrowserTreeExtendedService; +import com.jambotronGroup.jambotron.fileBrowserTree.FileItemDto; +import com.jambotronGroup.jambotron.fileUpload.ResponseMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("/api/admin/file-browser-tree") +public class FileBrowserTreeExtendedController { + + private final Logger _logger = LoggerFactory.getLogger(FileBrowserTreeExtendedService.class); + + @Autowired + private FileBrowserTreeExtendedService fileBrowserTreeService; + + + @GetMapping + @RequestMapping("/files") + public ResponseEntity> getFiles(@RequestParam String path) { + try { + List files = fileBrowserTreeService.listFiles(path); + return ResponseEntity.ok(files); + } catch (Exception e) { + _logger.error("Error retrieving files from path: {}", path, e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + // New endpoint specifically for folders only (for tree view) + @GetMapping("/folders") + public ResponseEntity> getFolders(@RequestParam String path) { + try { + List folders = fileBrowserTreeService.listFolders(path); + return ResponseEntity.ok(folders); + } catch (Exception e) { + _logger.error("Error retrieving folders from path: {}", path, e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + + // Search endpoint + @GetMapping("/search") + public ResponseEntity> searchFiles( + @RequestParam String path, + @RequestParam String query) { + try { + List files = fileBrowserTreeService.searchFiles(path, query); + return ResponseEntity.ok(files); + } catch (Exception e) { + _logger.error("Error searching files in path: {} with query: {}", path, query, e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping("/download") + public ResponseEntity downloadFile(@RequestParam String path) { + try { + Resource resource = fileBrowserTreeService.loadFileAsResource(path); + String filename = resource.getFilename(); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\"") + .body(resource); + } catch (Exception e) { + _logger.error("Error downloading file from path: {}", path, e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + @PostMapping("/upload") + public ResponseEntity uploadFile( + @RequestParam("file") MultipartFile file, + @RequestParam("path") String path) { + try { + fileBrowserTreeService.storeFile(file, path); + return ResponseEntity.ok(new ResponseMessage("File uploaded successfully: " + file.getOriginalFilename())); + } catch (IOException e) { + return ResponseEntity.badRequest() + .body(new ResponseMessage("Failed to upload file: " + e.getMessage())); + } + } + + @PostMapping("/folder") + public ResponseEntity createFolder(@RequestBody CreateFolderRequest request) { + try { + fileBrowserTreeService.createFolder(request.getPath(), request.getName()); + return ResponseEntity.ok(new ResponseMessage("Folder created successfully: " + request.getName())); + } catch (IOException e) { + return ResponseEntity.badRequest() + .body(new ResponseMessage("Failed to create folder: " + e.getMessage())); + } + } + + @DeleteMapping + public ResponseEntity deleteFile(@RequestParam String path) { + try { + fileBrowserTreeService.deleteFile(path); + return ResponseEntity.ok(new ResponseMessage("File deleted successfully: " + path)); + } catch (IOException e) { + return ResponseEntity.badRequest() + .body(new ResponseMessage("Failed to delete file/folder: " + e.getMessage())); + } + } + + // Move/rename endpoint + @PostMapping("/move") + public ResponseEntity moveFile(@RequestBody FileBrowserTreeController.MoveRequest request) { + try { + fileBrowserTreeService.moveFile(request.getSourcePath(), request.getTargetPath()); + return ResponseEntity.ok(new ResponseMessage("File moved successfully from " + request.getSourcePath() + " to " + request.getTargetPath())); + } catch (IOException e) { + return ResponseEntity.badRequest() + .body(new ResponseMessage("Failed to move file: " + e.getMessage())); + } + } + + // Copy endpoint + @PostMapping("/copy") + public ResponseEntity copyFile(@RequestBody FileBrowserTreeController.CopyRequest request) { + try { + fileBrowserTreeService.copyFile(request.getSourcePath(), request.getTargetPath()); + return ResponseEntity.ok(new ResponseMessage("File copied successfully from " + request.getSourcePath() + " to " + request.getTargetPath())); + } catch (IOException e) { + return ResponseEntity.badRequest() + .body(new ResponseMessage("Failed to copy file: " + e.getMessage())); + } + } + + +} diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java index aec30d6..a81d7ca 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java @@ -78,9 +78,9 @@ public class FilesController { response.getBody() // File content ); - User user = authenticationFacade.getUser(); - storageService.save(user.getId().toString(), multipartFile); + + storageService.save(multipartFile); message = "Uploaded the file successfully: " + fileName; @@ -104,7 +104,7 @@ public class FilesController { try { User user = authenticationFacade.getUser(); - String localFullFileName = storageService.save(user.getId().toString(), file); + String localFullFileName = storageService.save(file); FileInfo fileInfo = new FileInfo( file.getOriginalFilename(), @@ -122,9 +122,8 @@ public class FilesController { @GetMapping("/api/user/getImages") public ResponseEntity> getImages() { - User user = authenticationFacade.getUser(); - List fileInfos = storageService.loadUserImages(user.getId().toString()).map(path -> { + List fileInfos = storageService.loadUserImages().map(path -> { String filename = path.getFileName().toString(); String url = FilesRoutingHelper.getUserImageUrl(filename); @@ -158,7 +157,7 @@ public class FilesController { @ResponseBody public ResponseEntity getUserImage(@PathVariable String filename) { User user = authenticationFacade.getUser(); - Resource file = storageService.loadUserImage(user.getId().toString(),filename); + Resource file = storageService.loadUserImage(filename); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file); } @@ -169,7 +168,7 @@ public class FilesController { String message = ""; try { String filename = storageService.getFileNameFromUrl(imageUrl); - Base64ImageResponse base64Image = storageService.getUserImageAsBase64(user.getId().toString(),filename); + Base64ImageResponse base64Image = storageService.getUserImageAsBase64(filename); return ResponseEntity.ok(base64Image); @@ -177,6 +176,10 @@ public class FilesController { logger.error("Error retrieving base64 image for user {}: {}", user.getId(), e.getMessage()); message = "Error retrieving base64 image for fileUrl" + imageUrl + ". \tError: " + e.getMessage(); return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new Base64ImageResponse(message)); + } catch (IOException e) { + logger.error("IO Error retrieving base64 image for user {}: {}", user.getId(), e.getMessage()); + message = "IO Error retrieving base64 image for fileUrl" + imageUrl + ". \tError: " + e.getMessage(); + return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new Base64ImageResponse(message)); } } @@ -190,7 +193,7 @@ public class FilesController { try { - String fullFileName = storageService.saveUserBase64Image(user.getId().toString(),base64ImageRequest.getData(), base64ImageRequest.getMime()); + String fullFileName = storageService.saveUserBase64Image(base64ImageRequest.getData(), base64ImageRequest.getMime()); String url = FilesRoutingHelper.getUserImageUrl(fullFileName); diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index bad1299..1209641 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -118,7 +118,6 @@ public class TutorialController { String newFilename = filesStorageService.getFileNameFromUrl(tutorial.getTitleimage()); Path path= filesStorageService.moveFile( - user.getId().toString(), tutorial.getTitleimage(), String.format("Tutorial_%s",newFilename ) ); @@ -188,7 +187,6 @@ public class TutorialController { filesStorageService.deletePublicFile(servImageFileName); Path path= filesStorageService.moveFile( - user.getId().toString(), tutorial.getTitleimage(), String.format("Tutorial_%s",imageFileName ) ); diff --git a/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeExtendedService.java b/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeExtendedService.java new file mode 100644 index 0000000..b71a981 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeExtendedService.java @@ -0,0 +1,29 @@ +package com.jambotronGroup.jambotron.fileBrowserTree; + +import com.jambotronGroup.jambotron.globalConstants.GlobalConstants; +import com.jambotronGroup.jambotron.model.ERole; +import com.jambotronGroup.jambotron.security.AuthenticationFacade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.nio.file.Path; + +@Service + +public class FileBrowserTreeExtendedService extends FileBrowserTreeService { + + @Override + protected Path getRootPath() { + + boolean isAdmin = super.authenticationFacade.getUser() + .getRoles().stream() + .anyMatch(role -> role.getName().equals(ERole.ROLE_ADMIN)); + + if( isAdmin ) { + return GlobalConstants.PATH_ROOT.toAbsolutePath(); + } else { + + return super.getRootPath(); + } + } +} diff --git a/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeService.java b/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeService.java index 9c46266..46f0a7f 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeService.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileBrowserTree/FileBrowserTreeService.java @@ -1,5 +1,6 @@ package com.jambotronGroup.jambotron.fileBrowserTree; +import com.jambotronGroup.jambotron.globalConstants.GlobalConstants; import com.jambotronGroup.jambotron.model.ERole; import com.jambotronGroup.jambotron.security.AuthenticationFacade; import org.springframework.beans.factory.annotation.Autowired; @@ -20,24 +21,20 @@ import java.util.stream.Stream; public class FileBrowserTreeService { @Autowired - private AuthenticationFacade authenticationFacade; + protected AuthenticationFacade authenticationFacade; - private final Path root = Paths.get("/jambotron_data"); - private Path getRootPath() { - boolean isAdmin = authenticationFacade.getUser() - .getRoles().stream() - .anyMatch(role -> role.getName().equals(ERole.ROLE_ADMIN)); - - if(isAdmin){ - // Admins can access the root directory - return root.normalize().toAbsolutePath(); - } - return root.resolve(authenticationFacade.getUser().getUsername()).normalize().toAbsolutePath(); + //private final Path root = Paths.get("/jambotron_data"); + //private final Path rootUsers = root.resolve("users"); + protected Path getRootPath() { + GlobalConstants.PATH_USERS.toFile().mkdir(); + Path userFolder = GlobalConstants.PATH_USERS.resolve(authenticationFacade.getUser().getUsername()).toAbsolutePath(); + userFolder.toFile().mkdir(); + return userFolder; } private Path resolvePath(String path) throws IOException { Path root = getRootPath(); Path resolvedPath = root.resolve(path.startsWith("/") ? path.substring(1) : path) - .normalize().toAbsolutePath(); + .normalize(); // Security check: ensure the resolved path is within the root directory if (!resolvedPath.startsWith(root)) { @@ -47,6 +44,7 @@ public class FileBrowserTreeService { return resolvedPath; } + public List listFiles(String path) throws IOException { Path targetPath = resolvePath(path); diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java index 764d26f..573e079 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java @@ -5,6 +5,7 @@ import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.stream.Stream; @@ -13,25 +14,24 @@ public interface FilesStorageService { public String getFileNameFromUrl(String urlString); - public String save(String userID,MultipartFile file); + public String save(MultipartFile file); +// public void saveZhipuAiImage(MultipartFile file); - public String saveUserBase64Image(String userID, String base64Image, String mimeType); - - public void save(MultipartFile file); + public String saveUserBase64Image(String base64Image, String mimeType); public Resource load(String filename); - public Resource loadUserImage(String userID, String filename); + public Resource loadUserImage(String filename); - public Base64ImageResponse getUserImageAsBase64(String userID, String filename); + public Base64ImageResponse getUserImageAsBase64(String filename) throws IOException; - public void deleteAll(); + public void deleteAll() throws IOException; public Stream loadAll(); - public Stream loadUserImages(String userID); + public Stream loadUserImages(); - public Path moveFile(String userID, String url, String newFilename) throws Exception; + public Path moveFile(String url, String newFilename) throws Exception; public void deletePublicFile(String filename); } \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java index 3ee23c1..6329c99 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java @@ -2,9 +2,12 @@ package com.jambotronGroup.jambotron.fileUpload; import com.jambotronGroup.jambotron.controllers.AuthController; +import com.jambotronGroup.jambotron.globalConstants.GlobalConstants; import com.jambotronGroup.jambotron.payload.request.UploadImageResponse; +import com.jambotronGroup.jambotron.security.AuthenticationFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; @@ -26,6 +29,9 @@ public class FilesStorageServiceImpl implements FilesStorageService { private static final Logger _logger = LoggerFactory.getLogger(AuthController.class); + @Autowired + private AuthenticationFacade _authenticationFacade; + private static final Set ALLOWED_MIME = Set.of( "image/png", "image/jpeg", "image/webp", "image/gif" ); @@ -38,22 +44,32 @@ public class FilesStorageServiceImpl implements FilesStorageService { ); - // Update paths to use the Docker volume - private final Path root = Paths.get("/jambotron_data/uploads/user-images/"); - private final Path rootPublic = Paths.get("/jambotron_data/uploads/public-images/"); - // private final Path rootInitData = Pa + private final Path _rootPublic = GlobalConstants.PATH_PUBLIC; + + @Override public void init() { + try { - Files.createDirectories(root); - Files.createDirectories(rootPublic); + Files.createDirectories(GlobalConstants.PATH_USERS); + Files.createDirectories(_rootPublic); } catch (IOException e) { throw new RuntimeException("Could not initialize folder for upload!"); } } + private Path getUserUploadsImagesPath() throws IOException { + String username = _authenticationFacade.getUser().getUsername(); + Path rootUserUploadsImages = GlobalConstants.PATH_USERS + .resolve(username) + .resolve("uploads") + .resolve("images"); + Files.createDirectories(rootUserUploadsImages); + return rootUserUploadsImages; + } + @Override public String getFileNameFromUrl(String urlString){ try { @@ -69,10 +85,10 @@ public class FilesStorageServiceImpl implements FilesStorageService { } @Override - public Base64ImageResponse getUserImageAsBase64(String userID,String filename){ + public Base64ImageResponse getUserImageAsBase64(String filename) throws IOException { - Path userPath = this.root.resolve(userID); - Path file = userPath.resolve(filename); + + Path file = getUserUploadsImagesPath().resolve(filename); try { @@ -95,32 +111,32 @@ public class FilesStorageServiceImpl implements FilesStorageService { * Moves a file from a user's directory to the public directory with a new name. * StandardCopyOption.REPLACE_EXISTING * - * @param userID The ID of the user owning the file. + * @param url The URL of the file to move. * @param newFilename The new name for the file in the public directory. * @return The path to the moved file in the public directory. */ @Override - public Path moveFile(String userID, String url, String newFilename) { + public Path moveFile(String url, String newFilename) { String filename = this.getFileNameFromUrl(url); try { - Path sourcePath = this.root.resolve(userID).resolve(filename); - Path targetPath = this.rootPublic.resolve(newFilename); + Path sourcePath = getUserUploadsImagesPath().resolve(filename); + Path targetPath = _rootPublic.resolve(newFilename); Files.copy(sourcePath, targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException("Could not move the file: " + e.getMessage()); } - return this.rootPublic.resolve(newFilename); + return _rootPublic.resolve(newFilename); } @Override public void deletePublicFile(String filename) { try { - Path filePath = this.rootPublic.resolve(filename); + Path filePath = _rootPublic.resolve(filename); Files.deleteIfExists(filePath); } catch (IOException e) { throw new RuntimeException("Could not delete the file: " + e.getMessage()); @@ -131,17 +147,16 @@ public class FilesStorageServiceImpl implements FilesStorageService { * Saves a file to a user's directory. * uploads/user-images/{userID}/{filename} * - * @param userID The ID of the user. + * @param file The file to save. */ @Override - public String save(String userID,MultipartFile file) { + public String save(MultipartFile file) { Path targetPath = null; try { - Path path = this.root.resolve(userID); - path.toFile().mkdirs(); // Ensure user directory exists - targetPath = path.resolve(file.getOriginalFilename()); + String fixedFileName = getFixedFileName(file.getOriginalFilename()); + targetPath = getUserUploadsImagesPath().resolve(fixedFileName); Files.copy(file.getInputStream(), targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); @@ -157,24 +172,29 @@ public class FilesStorageServiceImpl implements FilesStorageService { return targetPath.getFileName().toString(); } - @Override - public void save(MultipartFile file) { - try { - Files.copy(file.getInputStream(), this.root.resolve(file.getOriginalFilename()), - java.nio.file.StandardCopyOption.REPLACE_EXISTING); - } catch (Exception e) { - if (e instanceof FileAlreadyExistsException) { - throw new RuntimeException("A file of that name already exists."); - } + private String getFixedFileName(String filename) { - throw new RuntimeException(e.getMessage()); - } + return filename.indexOf('?') != -1 ? filename.substring(0, filename.indexOf('?')) : filename; } +// @Override +// public void saveZhipuAiImage(MultipartFile file) { +// try { +// Files.copy(file.getInputStream(), getUserRootPath().resolve(file.getOriginalFilename()), +// java.nio.file.StandardCopyOption.REPLACE_EXISTING); +// } catch (Exception e) { +// if (e instanceof FileAlreadyExistsException) { +// throw new RuntimeException("A file of that name already exists."); +// } +// +// throw new RuntimeException(e.getMessage()); +// } +// } + @Override public Resource load(String filename) { try { - Path file = rootPublic.resolve(filename); + Path file = _rootPublic.resolve(filename); Resource resource = new UrlResource(file.toUri()); if (resource.exists() || resource.isReadable()) { @@ -188,11 +208,10 @@ public class FilesStorageServiceImpl implements FilesStorageService { } @Override - public Resource loadUserImage(String userID,String filename) { + public Resource loadUserImage(String filename) { try { - Path file = root.resolve(filename); - Path userPath = this.root.resolve(userID); - file = userPath.resolve(filename); + Path file = getUserUploadsImagesPath().resolve(filename); + Resource resource = new UrlResource(file.toUri()); if (resource.exists() || resource.isReadable()) { @@ -202,36 +221,49 @@ public class FilesStorageServiceImpl implements FilesStorageService { } } catch (MalformedURLException e) { throw new RuntimeException("Error: " + e.getMessage()); + } catch (IOException e) { + throw new RuntimeException(e); } } @Override - public void deleteAll() { - FileSystemUtils.deleteRecursively(root.toFile()); + public void deleteAll() throws IOException { + FileSystemUtils.deleteRecursively(getUserUploadsImagesPath().toFile()); } @Override public Stream loadAll() { try { - return Files.walk(this.root, 1).filter(path -> !path.equals(this.root)).map(this.root::relativize); + return Files.walk(getUserUploadsImagesPath(), 1).filter(path -> { + try { + return !path.equals(getUserUploadsImagesPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).map(getUserUploadsImagesPath()::relativize); } catch (IOException e) { throw new RuntimeException("Could not load the files!"); } } @Override - public Stream loadUserImages(String userID) { - Path userPath = this.root.resolve(userID); - userPath.toFile().mkdirs(); + public Stream loadUserImages() { + try { - return Files.walk(userPath, 1).filter(path -> !path.equals(userPath)).map(userPath::relativize); + return Files.walk(getUserUploadsImagesPath(), 1).filter(path -> { + try { + return !path.equals(getUserUploadsImagesPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).map(getUserUploadsImagesPath()::relativize); } catch (IOException e) { throw new RuntimeException("Could not load the files!"); } } @Override - public String saveUserBase64Image(String userID, String base64Image, String mimeType) { + public String saveUserBase64Image(String base64Image, String mimeType) { if (!ALLOWED_MIME.contains(mimeType)) { _logger.error("Unsupported MIME type: " + mimeType); @@ -256,17 +288,14 @@ public class FilesStorageServiceImpl implements FilesStorageService { String id = UUID.randomUUID().toString().replace("-", ""); - String safeBaseName = "user-image-" + userID; - String storedName = safeBaseName + "-" + id + "." + ext; + String safeBaseName = "user-image-"; + String storedName = safeBaseName + id + "." + ext; try { - Path userPath = this.root.resolve(userID); - userPath.toFile().mkdirs(); // Ensure user directory exists + Path targetPath = getUserUploadsImagesPath().resolve(storedName); - Path targetPath = userPath.resolve(storedName); - - Files.write(targetPath, bytes); + Files.write(targetPath, bytes); return targetPath.getFileName().toString(); diff --git a/src/main/java/com/jambotronGroup/jambotron/globalConstants/GlobalConstants.java b/src/main/java/com/jambotronGroup/jambotron/globalConstants/GlobalConstants.java new file mode 100644 index 0000000..6613808 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/globalConstants/GlobalConstants.java @@ -0,0 +1,11 @@ +package com.jambotronGroup.jambotron.globalConstants; + +import java.nio.file.Path; + +public class GlobalConstants { + public static Path PATH_ROOT = Path.of("/jambotron_data/"); + public static Path PATH_PUBLIC = PATH_ROOT.resolve("public"); + public static Path PATH_USERS = PATH_ROOT.resolve("users"); + public static Path PATH_INIT_DATA = PATH_ROOT.resolve("init_data"); + public static Path PATH_JSON_DUMP = PATH_ROOT.resolve("json_dump"); +} diff --git a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java index cb5adc0..5d14135 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java @@ -119,15 +119,17 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri // Access permitted for specific roles .requestMatchers("/api/user/**").hasRole("USER") + .requestMatchers("/api/user/file-browser-tree/**").hasRole("USER") + .requestMatchers("/api/moderator/**").hasRole("MODERATOR") - //.requestMatchers("/api/admin/**").hasRole("ADMIN") + .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/admin/json-dump/**").hasRole("ADMIN") .requestMatchers("/api/admin/json-dump/import/**").hasRole("ADMIN") + .requestMatchers("/api/admin/file-browser-tree/**").hasRole("ADMIN") + - .requestMatchers("/api/file-browser/files/**").hasAnyRole("USER", "MODERATOR", "ADMIN") - .requestMatchers("/api/file-browser-tree/files/**").hasAnyRole("USER", "MODERATOR", "ADMIN") .anyRequest().authenticated() );