diff --git a/build.gradle b/build.gradle index abbfed1..b5d91fe 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,10 @@ dependencies { implementation 'org.springframework.ai:spring-ai-zhipuai-spring-boot-starter:1.0.0-M6' implementation 'org.springframework:spring-mock:2.0.8' + + implementation 'com.google.guava:guava:33.4.8-jre' + implementation 'io.micrometer:micrometer-core:1.12.0' + implementation 'org.springframework.boot:spring-boot-starter-actuator' } apply plugin: 'io.spring.dependency-management' @@ -49,6 +53,7 @@ apply plugin: 'io.spring.dependency-management' apply plugin: 'java' + tasks.register("bootRun_Dev") { group = "_jambotron_build" description = "Runs the Spring Boot application with the dev profile" diff --git a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.html b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.html index 86111ea..ffa49c7 100644 --- a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.html +++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.html @@ -2,7 +2,7 @@

JSON Dump Management

-
+ + + @for (entity of entityTypes; track entity) { + + {{ entity | titlecase }} + + + + + + } +

Available JSON Files

diff --git a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.ts b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.ts index 11a1d0c..0cce18d 100644 --- a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.ts +++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.ts @@ -17,6 +17,8 @@ import {MatProgressSpinner} from '@angular/material/progress-spinner'; import {MatIcon} from '@angular/material/icon'; import {MatTooltip} from '@angular/material/tooltip'; import {ArchivesResponse, FileInfo, FilesResponse, JsonDumpService} from '../../json-dump.service'; +import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar'; +import {MatLabel} from '@angular/material/input'; @Component({ selector: 'app-json-dump', @@ -40,7 +42,10 @@ import {ArchivesResponse, FileInfo, FilesResponse, JsonDumpService} from '../../ MatTable, TitleCasePipe, NgIf, - NgForOf + NgForOf, + MatToolbar, + MatToolbarRow, + MatLabel ], styleUrls: ['./json-dump.component.scss'], schemas:[CUSTOM_ELEMENTS_SCHEMA] @@ -99,7 +104,9 @@ export class JsonDumpComponent implements OnInit { importEntity(entity: string, file: File): void { this.jsonDumpService.importEntity(entity, file).subscribe({ - next: () => this.showMessage(`Imported ${entity} successfully`), + next: () => { + this.showMessage(`Imported ${entity} successfully`) + }, error: (err: { message: any; }) => this.showMessage(`Import ${entity} failed: ${err.message}`) }); } @@ -132,4 +139,8 @@ export class JsonDumpComponent implements OnInit { private showMessage(msg: string): void { this.snackBar.open(msg, 'Close', { duration: 3000 }); } + + restore(fileName: string) { + + } } diff --git a/jambotron-ui/src/app/modules/admin-module/json-dump.service.ts b/jambotron-ui/src/app/modules/admin-module/json-dump.service.ts index 10db41f..82cbaec 100644 --- a/jambotron-ui/src/app/modules/admin-module/json-dump.service.ts +++ b/jambotron-ui/src/app/modules/admin-module/json-dump.service.ts @@ -58,7 +58,7 @@ export class JsonDumpService { importEntity(entity: string, file: File) { const formData = new FormData(); formData.append('file', file); - return this.http.post(`${this.baseUrl}/import/${entity}`, formData); + return this.http.post(`${this.baseUrl}/import/${entity}/file`, formData); } // ==================== EXPORT METHODS ==================== diff --git a/jambotron-ui/src/styles.scss b/jambotron-ui/src/styles.scss index 32972ed..79ff90e 100644 --- a/jambotron-ui/src/styles.scss +++ b/jambotron-ui/src/styles.scss @@ -1,4 +1,4 @@ -@use '@angular/material' as mat; +@use 'node_modules/@angular/material' as mat; //markdown styles @import 'bootstrap/dist/css/bootstrap.min.css'; @@ -58,11 +58,22 @@ body { background: #fafafbb3; } -html { - color-scheme: light dark; - @include mat.theme(( - color: mat.$azure-palette, - typography: Jersey 20 Charted, - density: 0 - )); -} +//// Include the common styles for Angular Material +//@include mat.core(); +// +//// Define your custom palette +//$my-primary: mat.define-palette(mat.$blue-palette, 700); +//$my-accent: mat.define-palette(mat.$orange-palette, A200, A100, A400); +//$my-warn: mat.define-palette(mat.$red-palette); +// +//// Create the theme +//$my-theme: mat.define-light-theme(( +// color: ( +// primary: $my-primary, +// accent: $my-accent, +// warn: $my-warn, +// ) +//)); +// +//// Include theme styles for core and each component used in your app +//@include mat.all-component-themes($my-theme); diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java index 6dab15f..6e4855b 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java @@ -74,27 +74,27 @@ public class JsonDumpController { // ==================== IMPORT ENDPOINTS ==================== - @PostMapping("/initData/users") - public ResponseEntity> importUsers() { + @PostMapping("/restore/users") + public ResponseEntity> restoreUsers() { return performImport(() -> _jsonDumpService.importUsers(), "users"); } - @PostMapping("/initData/tutorials") - public ResponseEntity> importTutorials() { + @PostMapping("/restore/tutorials") + public ResponseEntity> restoreTutorials() { return performImport(() -> _jsonDumpService.importTutorials(), "tutorials"); } - @PostMapping("/initData/roles") - public ResponseEntity> importRoles() { + @PostMapping("/restore/roles") + public ResponseEntity> restoreRoles() { return performImport(() -> _jsonDumpService.importRoles(), "roles"); } - @PostMapping("/initData/users/file") + @PostMapping("/import/users/file") public ResponseEntity> importUsersFromFile(@RequestParam("file") MultipartFile file) { return importFromFile(file, (path) -> _jsonDumpService.importUsers(path), "users"); } - @PostMapping("/initData/tutorials/file") + @PostMapping("/import/tutorials/file") public ResponseEntity> importTutorialsFromFile(@RequestParam("file") MultipartFile file) { return importFromFile(file, (path) -> _jsonDumpService.importTutorials(path), "tutorials"); } diff --git a/src/main/java/com/jambotronGroup/jambotron/initData/InitDataService.java b/src/main/java/com/jambotronGroup/jambotron/initData/InitDataService.java index 4f8ac94..0cdc641 100644 --- a/src/main/java/com/jambotronGroup/jambotron/initData/InitDataService.java +++ b/src/main/java/com/jambotronGroup/jambotron/initData/InitDataService.java @@ -124,42 +124,41 @@ public class InitDataService { List items = readList(jsonFile, new TypeReference>() {}); for (UserDto dto : items) { - User user = new User(); - if (dto.getId() != null) user.setId(dto.getId()); - user.setUsername(dto.getUsername()); - user.setEmail(dto.getEmail()); - // Expecting already-hashed values; do not import plain text secrets - user.setPassword(dto.getPassword()); + if(!_userRepository.existsByUsername(dto.getUsername())) { - // Resolve roles by enum name - Set roles = new HashSet<>(); - if (dto.getRoles() != null) { - for (RoleDto roleDto : dto.getRoles()) { - ERole erole = ERole.valueOf(roleDto.getName().name()); - Role role = rolesByName.get(erole); - if (role == null) { - // Optionally create missing roles on the fly - role = new Role(); - role.setName(erole); - role = _roleRepository.save(role); - rolesByName.put(erole, role); + User user = new User(); + if (dto.getId() != null) user.setId(dto.getId()); + user.setUsername(dto.getUsername()); + user.setEmail(dto.getEmail()); + // Expecting already-hashed values; do not import plain text secrets + user.setPassword(dto.getPassword()); + + // Resolve roles by enum name + Set roles = new HashSet<>(); + if (dto.getRoles() != null) { + for (RoleDto roleDto : dto.getRoles()) { + ERole erole = ERole.valueOf(roleDto.getName().name()); + Role role = rolesByName.get(erole); + if (role == null) { + // Optionally create missing roles on the fly + role = new Role(); + role.setName(erole); + role = _roleRepository.save(role); + rolesByName.put(erole, role); + } + roles.add(role); } - roles.add(role); } - } - user.setRoles(roles); + user.setRoles(roles); - if(!_userRepository.existsByUsername(user.getUsername())) { _userRepository.save(user); _logger.info("Imported user: {} with roles: {}", user.getUsername(), user.getRoles().stream() .map(Role::getName).collect(Collectors.toList()).stream().map(ERole::toString) .collect(Collectors.joining(", "))); } else { - _logger.warn("User with username {} already exists, SKIPPING import", user.getUsername()); - _logger.info("Imported user: {} with roles: {}", user.getUsername(), user.getRoles().stream() - .map(Role::getName).collect(Collectors.toList()).stream().map(ERole::toString) - .collect(Collectors.joining(", "))); + _logger.warn("User with username {} already exists, SKIPPING import", dto.getUsername()); + } } } diff --git a/src/main/java/com/jambotronGroup/jambotron/jsonDump/JsonDumpService.java b/src/main/java/com/jambotronGroup/jambotron/jsonDump/JsonDumpService.java index 58fcc55..7650aca 100644 --- a/src/main/java/com/jambotronGroup/jambotron/jsonDump/JsonDumpService.java +++ b/src/main/java/com/jambotronGroup/jambotron/jsonDump/JsonDumpService.java @@ -167,6 +167,12 @@ public class JsonDumpService extends InitDataService { List items = super.readList(jsonFile, new TypeReference>() {}); for (TutorialDto dto : items) { + + if(_tutorialRepository.findByUserIdAndTitle(dto.getUserID(), dto.getTitle()).size()> 0) { + _logger.warn("Tutorial with title [{}] already exists for userID [{}], SKIPPING import", dto.getTitle(), dto.getUserID()); + continue; + } + Tutorial t = new Tutorial(); t.setId(dto.getId()); t.setTitle(dto.getTitle()); @@ -193,16 +199,17 @@ public class JsonDumpService extends InitDataService { } else { throw new IllegalArgumentException("Missing user with ROLE_ADMIN to assign tutorial: " + dto.getTitle()); } + } else { + User user = usersById.get(dto.getUserID()); + //user = _entityManager.merge(user); // Ensure user is managed by EntityManager + //user.setRoles(null); // because we don't want to load roles to user object + t.setUser(user); } - User user = usersById.get(dto.getUserID()); - //user = _entityManager.merge(user); // Ensure user is managed by EntityManager - //user.setRoles(null); // because we don't want to load roles to user object - t.setUser(user); - if(_tutorialRepository.findByUserIdAndTitle(t.getUser().getId(), t.getTitle()).size()> 0) { - _logger.warn("Tutorial with title [{}] already exists for user [{}], SKIPPING import", t.getTitle(), t.getUser().getUsername()); - continue; + if(_tutorialRepository.findByTitle(t.getTitle()).size() > 0) { + t.setTitle(t.getTitle() + " (imported) " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } + _tutorialRepository.saveAndFlush(t); //_tutorialRepository.save(t); _logger.info("Imported tutorial: {} with user: {}", t.getTitle(), t.getUser().getUsername()); diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java index 0709372..fd0672e 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java @@ -17,4 +17,6 @@ public interface TutorialRepository extends JpaRepository { List findBytobepublished(boolean tobepublished); List findByIdAndTobepublished(Long id,boolean tobepublished); List findByTitleContaining(String title); + + List findByTitle(String title); } \ No newline at end of file