-
-
+
+
+
+
+
+
+
diff --git a/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.scss b/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.scss
index 9a8a04f..3680de9 100644
--- a/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.scss
+++ b/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.scss
@@ -1,16 +1,36 @@
-//---------------
-.pc-sidebar{
- top: 65px;
- overflow-y: auto;
- background-color: rgba(153, 153, 153, 0.16);
- backdrop-filter: blur(8px);
-}
-.pc-container{
- top: 0px;
+.app-container{
+ top: -12px;
padding-left: 5px;
padding-right: 5px;
+ margin-left: 240px;
+ padding-top: 1px;
+ position: relative;
+ &.collapsed{
+ margin-left: 64px;
+ }
+}
+.app-breadcrumb{
+ position: fixed;
+ left: 240px;
+ width: 100%;
+ background-color: rgba(153, 153, 153, 0.16);
+ backdrop-filter: blur(8px);
+ z-index: 100;
+ &.collapsed{
+ left: 64px;
+ }
+}
+
+.page-component{
+ height: 100%;
+ margin-top: 65px;
+ margin-bottom: 50px;
+ margin-left: 10px;
+ margin-right: 10px;
+ background: aliceblue;
+ position: relative;
+ padding: 20px;
}
-
diff --git a/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.ts b/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.ts
index afcddb0..514c3dd 100644
--- a/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.ts
+++ b/jambotron-ui/src/app/modules/admin-module/admin.component/admin.component.ts
@@ -1,69 +1,31 @@
-import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
-import {Router, RouterOutlet} from "@angular/router";
-import {DOCUMENT} from '@angular/common';
-import {SideBarAdminComponent} from '../side-bar-admin.component/side-bar-admin.component';
+import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
+import {RouterOutlet} from "@angular/router";
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
+import {VerticalNavComponent} from '../../../components/vertical-nav.component/vertical-nav.component';
+import {ADMIN_ROUTES} from '../admin.routing';
@Component({
selector: 'app-admin.component',
imports: [
RouterOutlet,
- SideBarAdminComponent,
- BreadcrumbsComponent
+ BreadcrumbsComponent,
+ VerticalNavComponent
],
schemas:[CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './admin.component.html',
styleUrl: './admin.component.scss'
})
-export class AdminComponent implements OnInit {
- private document = inject
(DOCUMENT);
- height = 0;
+export class AdminComponent{
- constructor(private router: Router) {
- this.height = 0
+ //adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
+ adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
+ // If your admin module is mounted at '/admin' in the app routes, keep this as '/admin'.
+ // If it's at root, set this to ''.
+ basePath = '/main/admin';
+
+ isCollapsed = false;
+
+ navCollapsedChanged($event: boolean) {
+ this.isCollapsed = $event;
}
-
- // public props
- navCollapsed: boolean = false;
- navCollapsedMob: boolean = false;
-
- // public method
- navMobClick() {
- if (this.navCollapsedMob && !document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
- this.navCollapsedMob = !this.navCollapsedMob;
- setTimeout(() => {
- this.navCollapsedMob = !this.navCollapsedMob;
- }, 100);
- } else {
- this.navCollapsedMob = !this.navCollapsedMob;
- }
- if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('navbar-collapsed')) {
- document.querySelector('app-navigation.pc-sidebar')?.classList.remove('navbar-collapsed');
- }
- }
-
- handleKeyDown(event: KeyboardEvent): void {
- if (event.key === 'Escape') {
- this.closeMenu();
- }
- }
-
- closeMenu() {
- if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
- document.querySelector('app-navigation.pc-sidebar')?.classList.remove('mob-open');
- }
- }
-
- ngOnInit() {
-
- if(this.document.defaultView !== null || this.document.defaultView !== undefined) {
- // @ts-ignore
- this.height = this.document.defaultView.innerHeight;
- }
- else
- this.height = 0;
-
-
- }
-
}
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 01daa89..2b9dfb8 100644
--- a/jambotron-ui/src/app/modules/admin-module/admin.routing.ts
+++ b/jambotron-ui/src/app/modules/admin-module/admin.routing.ts
@@ -1,32 +1,37 @@
import {RouterModule, Routes} from '@angular/router';
import {AdminComponent} from './admin.component/admin.component';
-const ADMIN_ROUTES: Routes = [
+export const ADMIN_ROUTES: Routes = [
{
path: '',
- component: AdminComponent,
- data:{breadcrumb: 'Admin'},
+ loadComponent: () => import('../admin-module/admin.component/admin.component').then((c) => c.AdminComponent),
+ data:{title: 'Admin', icon:'', nav:true},
children: [
{ path: '', pathMatch: 'full', redirectTo: 'admin-welcome' },
{
path: 'admin-welcome',
loadComponent: () => import('../admin-module/admin-welcome.component/admin-welcome.component').then((c) => c.AdminWelcomeComponent),
- data:{breadcrumb: 'Welcome'}
+ data:{title: 'Welcome', icon:'dashboard', nav:true}
},
{
path: 'settings',
loadComponent: () => import('../admin-module/settings.component/settings.component').then((c) => c.SettingsComponent),
- data:{breadcrumb: 'Settings'}
+ data:{title: 'Settings', icon:'settings', nav:true}
},
{
path: 'system',
loadComponent: () => import('../admin-module/system.component/system.component').then((c) => c.SystemComponent),
- data:{breadcrumb: 'System'}
+ data:{title: 'System', icon:'component_exchange', nav:true}
},
{
path: 'users',
loadComponent: () => import('../admin-module/users.component/users.component').then((c) => c.UsersComponent),
- data:{breadcrumb: 'Users'}
+ data:{title: 'Users', icon:'', nav:true}
+ },
+ {
+ path:"data-base",
+ loadComponent: () => import('../admin-module/data-base.component/data-base.component').then((c) => c.DataBaseComponent),
+ data:{title: 'Data', icon:'data_table', nav:true}
}
]
}
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
new file mode 100644
index 0000000..86111ea
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.html
@@ -0,0 +1,59 @@
+
+
JSON Dump Management
+
+
+
+
{{ entity | titlecase }}
+
+
+
+
+
+
Available JSON Files
+
+
+ File Name
+ {{ file.fileName }}
+
+
+
+ Size
+ {{ jsonDumpService.formatFileSize(file.size) }}
+
+
+
+ Last Modified
+ {{ jsonDumpService.formatDate(file.lastModified) }}
+
+
+
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
Archives
+
+ {{ archive }}
+
+
+
+
+
+
diff --git a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.scss b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.scss
new file mode 100644
index 0000000..8619e47
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.scss
@@ -0,0 +1,27 @@
+.json-dump-container {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ padding: 16px;
+}
+
+.export-buttons,
+.import-section {
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.actions {
+ margin-bottom: 16px;
+
+ button {
+ margin-right: 8px;
+ }
+}
+table {
+ width: 100%;
+}
+mat-table {
+ margin-bottom: 24px;
+}
diff --git a/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.spec.ts b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.spec.ts
new file mode 100644
index 0000000..06d6308
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { JsonDumpComponent } from './json-dump.component';
+
+describe('JsonDumpComponent', () => {
+ let component: JsonDumpComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [JsonDumpComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(JsonDumpComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
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
new file mode 100644
index 0000000..11a1d0c
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/components/json-dump.component/json-dump.component.ts
@@ -0,0 +1,135 @@
+import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
+
+import { MatSnackBar } from '@angular/material/snack-bar';
+import {MatList, MatListItem} from '@angular/material/list';
+import {
+ MatCell, MatCellDef,
+ MatColumnDef,
+ MatHeaderCell, MatHeaderCellDef,
+ MatHeaderRow,
+ MatHeaderRowDef,
+ MatRow,
+ MatRowDef, MatTable
+} from '@angular/material/table';
+import {MatIconButton} from '@angular/material/button';
+import {NgForOf, NgIf, TitleCasePipe} from '@angular/common';
+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';
+
+@Component({
+ selector: 'app-json-dump',
+ templateUrl: './json-dump.component.html',
+ imports: [
+ MatProgressSpinner,
+ MatListItem,
+ MatList,
+ MatHeaderRow,
+ MatRow,
+ MatHeaderRowDef,
+ MatRowDef,
+ MatIcon,
+ MatHeaderCell,
+ MatCell,
+ MatIconButton,
+ MatTooltip,
+ MatColumnDef,
+ MatHeaderCellDef,
+ MatCellDef,
+ MatTable,
+ TitleCasePipe,
+ NgIf,
+ NgForOf
+ ],
+ styleUrls: ['./json-dump.component.scss'],
+ schemas:[CUSTOM_ELEMENTS_SCHEMA]
+})
+export class JsonDumpComponent implements OnInit {
+ files: FileInfo[] = [];
+ archives: string[] = [];
+ loading = false;
+
+ entityTypes = ['users', 'roles', 'tutorials'];
+
+ constructor(
+ public jsonDumpService: JsonDumpService,
+ private snackBar: MatSnackBar
+ ) {}
+
+ ngOnInit(): void {
+ this.loadFiles();
+ this.loadArchives();
+ }
+
+ loadFiles(): void {
+ this.loading = true;
+ this.jsonDumpService.getFiles().subscribe({
+ next: (res: FilesResponse) => {
+ this.files = res.files || [];
+ this.loading = false;
+ },
+ error: (err: { message: any; }) => {
+ this.loading = false;
+ this.showMessage(`Error loading files: ${err.message}`);
+ }
+ });
+ }
+
+ loadArchives(): void {
+ this.jsonDumpService.getArchivedFiles().subscribe({
+ next: (res: ArchivesResponse) => {
+ this.archives = res.archives || [];
+ },
+ error: (err: { message: any; }) => {
+ this.showMessage(`Error loading archives: ${err.message}`);
+ }
+ });
+ }
+
+ exportEntity(entity: string): void {
+ this.jsonDumpService.exportEntity(entity).subscribe({
+ next: () => {
+ this.showMessage(`Exported ${entity} successfully`);
+ this.loadFiles();
+ },
+ error: (err: { message: any; }) => this.showMessage(`Export ${entity} failed: ${err.message}`)
+ });
+ }
+
+ importEntity(entity: string, file: File): void {
+ this.jsonDumpService.importEntity(entity, file).subscribe({
+ next: () => this.showMessage(`Imported ${entity} successfully`),
+ error: (err: { message: any; }) => this.showMessage(`Import ${entity} failed: ${err.message}`)
+ });
+ }
+
+ onFileSelected(event: Event, entity: string): void {
+ const input = event.target as HTMLInputElement;
+ if (input.files && input.files.length > 0) {
+ this.importEntity(entity, input.files[0]);
+ input.value = ''; // reset input
+ }
+ }
+
+ deleteFile(fileName: string): void {
+ this.jsonDumpService.deleteFile(fileName).subscribe({
+ next: () => {
+ this.showMessage(`Deleted file: ${fileName}`);
+ this.loadFiles();
+ },
+ error: (err: { message: any; }) => this.showMessage(`Delete failed: ${err.message}`)
+ });
+ }
+
+ downloadFile(fileName: string): void {
+ this.jsonDumpService.downloadFile(fileName).subscribe({
+ next: (blob: any) => this.jsonDumpService.downloadBlob(blob, fileName),
+ error: (err: { message: any; }) => this.showMessage(`Download failed: ${err.message}`)
+ });
+ }
+
+ private showMessage(msg: string): void {
+ this.snackBar.open(msg, 'Close', { duration: 3000 });
+ }
+}
diff --git a/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.html b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.html
new file mode 100644
index 0000000..f16f125
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.html
@@ -0,0 +1,7 @@
+data-base.component works!
+
+ @for (table of tables; track table){
+ - {{ table.schema }}.{{ table.table }}
+ }
+
+
diff --git a/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.scss b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.spec.ts b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.spec.ts
new file mode 100644
index 0000000..93b2304
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DataBaseComponent } from './data-base.component';
+
+describe('DataBaseComponent', () => {
+ let component: DataBaseComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DataBaseComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(DataBaseComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.ts b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.ts
new file mode 100644
index 0000000..cb57610
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/data-base.component/data-base.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+import {Table} from '../../../models/table';
+import {AdminModuleService} from '../admin-module.service';
+
+@Component({
+ selector: 'app-data-base.component',
+ imports: [],
+ templateUrl: './data-base.component.html',
+ styleUrl: './data-base.component.scss'
+})
+export class DataBaseComponent {
+ tables: Table[] = [];
+
+ constructor(private adminService:AdminModuleService) {
+ this.getTables();
+ }
+
+ getTables(){
+ // this.adminService.getUserTables(/* optionally: 'public' */).subscribe({
+ // next: data => (this.tables = data),
+ // error: err => console.error('Failed to load tables', err)
+ // });
+
+ }
+}
diff --git a/jambotron-ui/src/app/modules/admin-module/json-dump.service.spec.ts b/jambotron-ui/src/app/modules/admin-module/json-dump.service.spec.ts
new file mode 100644
index 0000000..afbd120
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/json-dump.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { JsonDumpService } from './json-dump.service';
+
+describe('JsonDumpService', () => {
+ let service: JsonDumpService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(JsonDumpService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
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
new file mode 100644
index 0000000..10db41f
--- /dev/null
+++ b/jambotron-ui/src/app/modules/admin-module/json-dump.service.ts
@@ -0,0 +1,449 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
+import { Observable, throwError } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+import {GlobalConstants} from '../../global-constants';
+
+// Interfaces
+export interface ApiResponse {
+ success: boolean;
+ message: string;
+ timestamp?: string;
+ data?: T;
+}
+
+export interface ExportResponse extends ApiResponse {
+ timestamp: string;
+}
+
+export interface ImportResponse extends ApiResponse {
+ timestamp: string;
+ fileName?: string;
+}
+
+export interface FileInfo {
+ fileName: string;
+ size: number;
+ lastModified: string;
+ //path: string;
+}
+
+export interface FilesResponse extends ApiResponse {
+ files: FileInfo[];
+ count: number;
+}
+
+export interface ArchivesResponse extends ApiResponse {
+ archives: string[];
+ count: number;
+}
+
+export interface ExtractResponse extends ApiResponse {
+ fileName: string;
+ targetDirectory: string;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class JsonDumpService {
+ private readonly baseUrl = `${GlobalConstants.API_URL}/admin/json-dump`;
+
+ constructor(private http: HttpClient) {}
+
+ exportEntity(entity: string) {
+ return this.http.post<{message: string}>(`${this.baseUrl}/export/${entity}`, {});
+ }
+
+ importEntity(entity: string, file: File) {
+ const formData = new FormData();
+ formData.append('file', file);
+ return this.http.post(`${this.baseUrl}/import/${entity}`, formData);
+ }
+
+ // ==================== EXPORT METHODS ====================
+
+ /**
+ * Export all entities (users, tutorials, roles)
+ */
+ exportAll(): Observable {
+ return this.http.post(`${this.baseUrl}/export/all`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Export users only
+ */
+ exportUsers(): Observable {
+ return this.http.post(`${this.baseUrl}/export/users`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Export tutorials only
+ */
+ exportTutorials(): Observable {
+ return this.http.post(`${this.baseUrl}/export/tutorials`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Export roles only
+ */
+ exportRoles(): Observable {
+ return this.http.post(`${this.baseUrl}/export/roles`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ // ==================== IMPORT METHODS ====================
+
+ /**
+ * Import users from latest JSON file
+ */
+ importUsers(): Observable {
+ return this.http.post(`${this.baseUrl}/import/users`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Import tutorials from latest JSON file
+ */
+ importTutorials(): Observable {
+ return this.http.post(`${this.baseUrl}/import/tutorials`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Import roles from latest JSON file
+ */
+ importRoles(): Observable {
+ return this.http.post(`${this.baseUrl}/import/roles`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Import users from uploaded file
+ */
+ importUsersFromFile(file: File): Observable {
+ const formData = new FormData();
+ formData.append('file', file);
+
+ return this.http.post(`${this.baseUrl}/import/users/file`, formData)
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Import tutorials from uploaded file
+ */
+ importTutorialsFromFile(file: File): Observable {
+ const formData = new FormData();
+ formData.append('file', file);
+
+ return this.http.post(`${this.baseUrl}/import/tutorials/file`, formData)
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ // ==================== ARCHIVE METHODS ====================
+
+ /**
+ * Get list of all archived files
+ */
+ getArchivedFiles(): Observable {
+ return this.http.get(`${this.baseUrl}/archives`)
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ archiveOldFiles(): Observable {
+ return this.http.post(`${this.baseUrl}/archives/archive-old-files`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Extract archived file
+ */
+ extractArchive(fileName: string): Observable {
+ return this.http.post(`${this.baseUrl}/archives/${fileName}/extract`, {})
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Download archived file
+ */
+ downloadArchive(fileName: string): Observable {
+ return this.http.get(`${this.baseUrl}/archives/${fileName}/download`, {
+ responseType: 'blob'
+ }).pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ // ==================== FILE MANAGEMENT METHODS ====================
+
+ /**
+ * Get list of all JSON files with details
+ */
+ getFiles(): Observable {
+ return this.http.get(`${this.baseUrl}/files`)
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Download JSON file
+ */
+ downloadFile(fileName: string): Observable {
+ return this.http.get(`${this.baseUrl}/files/${fileName}/download`, {
+ responseType: 'blob'
+ }).pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ /**
+ * Delete JSON file
+ */
+ deleteFile(fileName: string): Observable {
+ return this.http.delete(`${this.baseUrl}/files/${fileName}`)
+ .pipe(
+ catchError(this.handleError)
+ );
+ }
+
+ // ==================== UTILITY METHODS ====================
+
+ /**
+ * Download blob as file
+ */
+ downloadBlob(blob: Blob, fileName: string): void {
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = fileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+ }
+
+ /**
+ * Format file size for display
+ */
+ formatFileSize(bytes: number): string {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ }
+
+ /**
+ * Format date for display
+ */
+ formatDate(dateString: string): string {
+ const date = new Date(dateString);
+ return date.toLocaleString();
+ }
+
+ /**
+ * Validate JSON file
+ */
+ isValidJsonFile(file: File): boolean {
+ return file.type === 'application/json' || file.name.toLowerCase().endsWith('.json');
+ }
+
+ /**
+ * Get entity type from file name
+ */
+ getEntityTypeFromFileName(fileName: string): string {
+ if (fileName.includes('users_')) return 'users';
+ if (fileName.includes('tutorials_')) return 'tutorials';
+ if (fileName.includes('roles_')) return 'roles';
+ return 'unknown';
+ }
+
+ /**
+ * Check if file is recent (less than 7 days old)
+ */
+ isRecentFile(lastModified: string): boolean {
+ const fileDate = new Date(lastModified);
+ const sevenDaysAgo = new Date();
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
+ return fileDate > sevenDaysAgo;
+ }
+
+ // ==================== BATCH OPERATIONS ====================
+
+ /**
+ * Export all entities and return combined status
+ */
+ // exportAllSeparately(): Observable<{ users: ExportResponse; tutorials: ExportResponse; roles: ExportResponse }> {
+ // const users$ = this.exportUsers();
+ // const tutorials$ = this.exportTutorials();
+ // const roles$ = this.exportRoles();
+ //
+ // return new Observable(observer => {
+ // const results: any = {};
+ // let completed = 0;
+ //
+ // const checkCompletion = () => {
+ // if (completed === 3) {
+ // observer.next(
+ // value:{users:users$,tutorial:tutorials$,roles:roles$},
+ // );
+ // observer.complete();
+ // }
+ // };
+ //
+ // users$.subscribe({
+ // next: (result) => {
+ // results.users = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.users = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ //
+ // tutorials$.subscribe({
+ // next: (result) => {
+ // results.tutorials = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.tutorials = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ //
+ // roles$.subscribe({
+ // next: (result) => {
+ // results.roles = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.roles = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ // });
+ // }
+
+ /**
+ * Import all entities from latest files
+ */
+ // importAll(): Observable<{ users: ImportResponse; tutorials: ImportResponse; roles: ImportResponse }> {
+ // const users$ = this.importUsers();
+ // const tutorials$ = this.importTutorials();
+ // const roles$ = this.importRoles();
+ //
+ // return new Observable(observer => {
+ // const results: any = {};
+ // let completed = 0;
+ //
+ // const checkCompletion = () => {
+ // if (completed === 3) {
+ // observer.next(
+ // value:{user:}
+ // );
+ // observer.complete();
+ // }
+ // };
+ //
+ // users$.subscribe({
+ // next: (result) => {
+ // results.users = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.users = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ //
+ // tutorials$.subscribe({
+ // next: (result) => {
+ // results.tutorials = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.tutorials = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ //
+ // roles$.subscribe({
+ // next: (result) => {
+ // results.roles = result;
+ // completed++;
+ // checkCompletion();
+ // },
+ // error: (error) => {
+ // results.roles = { success: false, message: error.message, timestamp: new Date().toISOString() };
+ // completed++;
+ // checkCompletion();
+ // }
+ // });
+ // });
+ // }
+
+ // ==================== ERROR HANDLING ====================
+
+ private handleError = (error: HttpErrorResponse): Observable => {
+ let errorMessage = 'An unknown error occurred';
+
+ if (error.error instanceof ErrorEvent) {
+ // Client-side error
+ errorMessage = error.error.message;
+ } else {
+ // Server-side error
+ if (error.error && error.error.message) {
+ errorMessage = error.error.message;
+ } else if (error.message) {
+ errorMessage = error.message;
+ } else {
+ errorMessage = `Server returned code ${error.status}: ${error.statusText}`;
+ }
+ }
+
+ console.error('JsonDumpService Error:', errorMessage, error);
+ return throwError(() => new Error(errorMessage));
+ };
+}
diff --git a/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.html b/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.html
index 26b0cd1..b3ea4d7 100644
--- a/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.html
+++ b/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.html
@@ -25,3 +25,5 @@
+
+
diff --git a/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.ts b/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.ts
index b728cab..19b7c3e 100644
--- a/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.ts
+++ b/jambotron-ui/src/app/modules/admin-module/settings.component/settings.component.ts
@@ -13,6 +13,7 @@ import {
} from '@angular/material/table';
import {SystemService} from '../../../services/system.service';
import {NameValueItem} from '../../../models/name-value-item';
+import {JsonDumpComponent} from '../components/json-dump.component/json-dump.component';
@Component({
selector: 'app-settings.component',
@@ -32,7 +33,8 @@ import {NameValueItem} from '../../../models/name-value-item';
MatHeaderRow,
MatHeaderRowDef,
MatRow,
- MatRowDef
+ MatRowDef,
+ JsonDumpComponent
],
templateUrl: './settings.component.html',
styleUrl: './settings.component.scss'
diff --git a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.html b/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.html
deleted file mode 100644
index 3d40615..0000000
--- a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- house
- @if (!isCollapsed) {
- Dashboard
- }
-
-
-
-
- newspaper
- @if (!isCollapsed) {
- Users
- }
-
-
-
-
- newspaper
- @if (!isCollapsed) {
- Settings
- }
-
-
-
-
- newspaper
- @if (!isCollapsed) {
- System
- }
-
-
-
-
diff --git a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.scss b/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.scss
deleted file mode 100644
index 62a5b67..0000000
--- a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-.entry{
- display: flex;
- align-items: center;
- gap: 1rem;
- padding:0.75rem;
- color: rgba(24, 255, 255, 0.96);
-
-}
-
-a.mdc-list-item
-{
-
- background-color: rgba(24,255,255,0.04);
-}
diff --git a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.ts b/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.ts
deleted file mode 100644
index 0940b6f..0000000
--- a/jambotron-ui/src/app/modules/admin-module/side-bar-admin.component/side-bar-admin.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
-import {MatListItem, MatNavList} from '@angular/material/list';
-import {RouterLink} from '@angular/router';
-import {MatIcon} from '@angular/material/icon';
-
-@Component({
- selector: 'app-side-bar-admin',
- imports: [
- MatIcon,
- MatListItem,
- MatNavList,
- RouterLink
- ],
- templateUrl: './side-bar-admin.component.html',
- styleUrl: './side-bar-admin.component.scss',
- schemas:[CUSTOM_ELEMENTS_SCHEMA]
-})
-export class SideBarAdminComponent {
- isCollapsed = false;
-}
diff --git a/jambotron-ui/src/app/modules/admin-module/users.component/users.component.ts b/jambotron-ui/src/app/modules/admin-module/users.component/users.component.ts
index b7ebcb4..1011b80 100644
--- a/jambotron-ui/src/app/modules/admin-module/users.component/users.component.ts
+++ b/jambotron-ui/src/app/modules/admin-module/users.component/users.component.ts
@@ -53,15 +53,15 @@ export class UsersComponent {
displayedColumns: string[] = UserColumns.map((col) => col.key)
columnsSchema: any = UserColumns
- constructor(private userService: UserService,
- private roleService: RolesService,
+ constructor(
+
private adminService:AdminModuleService) {
this.getAllRoles();
this.getUsers();
}
getAllRoles():any{
- this.roleService.getAllRoles().subscribe(
+ this.adminService.getAllRoles().subscribe(
(data : any) => {
this.allRoles = data;
console.log(data);
@@ -73,7 +73,7 @@ export class UsersComponent {
}
getUsers(){
- this.userService.getAdminBoard().subscribe(
+ this.adminService.getUsers().subscribe(
(data : any) => {
this.users = data;
console.log(data);
@@ -84,7 +84,7 @@ export class UsersComponent {
);
}
- saveUserRoles(user:User){
+ saveUserRoles(user:User){
this.adminService.updateUserRoles(user.id, user).subscribe(
(data:any) => {
console.log(data);
@@ -114,6 +114,8 @@ export class UsersComponent {
isDisabled(element:User, role: Role) {
return role.name?.includes(element.username?.toUpperCase());
}
+
+
}
export const UserColumns = [
diff --git a/jambotron-ui/src/app/modules/main-module/main.component/main.component.html b/jambotron-ui/src/app/modules/main-module/main.component/main.component.html
index d4c3725..054f7ed 100644
--- a/jambotron-ui/src/app/modules/main-module/main.component/main.component.html
+++ b/jambotron-ui/src/app/modules/main-module/main.component/main.component.html
@@ -1,60 +1,63 @@
-
-
-
-
-
-
- @if (!isLoggedIn) {
-
diff --git a/jambotron-ui/src/app/modules/main-module/main.component/main.component.scss b/jambotron-ui/src/app/modules/main-module/main.component/main.component.scss
index 893af8a..3f6da6d 100644
--- a/jambotron-ui/src/app/modules/main-module/main.component/main.component.scss
+++ b/jambotron-ui/src/app/modules/main-module/main.component/main.component.scss
@@ -30,6 +30,7 @@ mat-toolbar{
margin-top: 65px;
height: 100%;
min-height: fit-content;
+ width: 100%;
}
.component{
diff --git a/jambotron-ui/src/app/modules/main-module/main.routing.ts b/jambotron-ui/src/app/modules/main-module/main.routing.ts
index c80ef9b..5872280 100644
--- a/jambotron-ui/src/app/modules/main-module/main.routing.ts
+++ b/jambotron-ui/src/app/modules/main-module/main.routing.ts
@@ -1,10 +1,10 @@
import {RouterModule, Routes} from '@angular/router';
import {MainComponent} from './main.component/main.component';
-const MAIN_ROUTES: Routes =[
+export const MAIN_ROUTES: Routes =[
{
path: '',
- component: MainComponent,
+ loadComponent: () => import('./main.component/main.component').then((c) => c.MainComponent),
children: [
{
path: 'tutorials',
@@ -21,6 +21,7 @@ const MAIN_ROUTES: Routes =[
path: 'admin',
loadChildren: () =>
import('../admin-module/admin.module').then((m) => m.AdminModule),
+ data:{nav:true}
},
{
path: 'user',
diff --git a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.html b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.html
index 726bafa..479132c 100644
--- a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.html
+++ b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.html
@@ -1,5 +1,2 @@
-
-
+
+
diff --git a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.scss b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.scss
index 016384e..e69de29 100644
--- a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.scss
+++ b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.scss
@@ -1,13 +0,0 @@
-//---------------
-.pc-sidebar{
- top: 65px;
- overflow-y: auto;
- background-color: rgba(153, 153, 153, 0.16);
-
- backdrop-filter: blur(8px);
-}
-.pc-container{
- top: 0px;
- padding-left: 5px;
- padding-right: 5px;
-}
diff --git a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.ts b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.ts
index 4f663e2..c83efaa 100644
--- a/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.ts
+++ b/jambotron-ui/src/app/modules/moderator-module/moderator.component/moderator.component.ts
@@ -2,20 +2,21 @@ import { Component } from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {SideBarModeratorComponent} from '../side-bar-moderator.component/side-bar-moderator.component';
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
+import {InnerLayoutComponent} from '../../../components/inner-layout.component/inner-layout.component';
+import {MODERATOR_ROUTS} from '../moderator.routing';
@Component({
selector: 'app-moderator.component',
imports: [
- RouterOutlet,
- SideBarModeratorComponent,
- BreadcrumbsComponent,
+ InnerLayoutComponent,
],
templateUrl: './moderator.component.html',
styleUrl: './moderator.component.scss'
})
export class ModeratorComponent {
-
+ moderatorRouts = MODERATOR_ROUTS;
+ basePath = '/main/moderator';
}
diff --git a/jambotron-ui/src/app/modules/moderator-module/moderator.routing.ts b/jambotron-ui/src/app/modules/moderator-module/moderator.routing.ts
index dd22003..073b7f9 100644
--- a/jambotron-ui/src/app/modules/moderator-module/moderator.routing.ts
+++ b/jambotron-ui/src/app/modules/moderator-module/moderator.routing.ts
@@ -2,30 +2,30 @@ import {RouterModule, Routes} from '@angular/router';
import {ModeratorComponent} from './moderator.component/moderator.component';
-const MODERATOR_ROUTES: Routes = [
+export const MODERATOR_ROUTS: Routes = [
{
path: '',
component: ModeratorComponent,
- data:{breadcrumb: 'Moderator'},
+ data:{title: 'Moderator'},
children: [
{ path: '', pathMatch: 'full', redirectTo: 'moderator-welcome' }, // default redirect
{
path: 'moderator-welcome',
loadComponent: () => import('../moderator-module/moderator-welcome.component/moderator-welcome.component').then((c) => c.ModeratorWelcomeComponent),
- data:{breadcrumb: 'Welcome'}
+ data:{title: 'Welcome', icon:'dashboard', nav:true}
},
{
path: 'tutorials-list',
loadComponent: () => import('../moderator-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent),
- data:{breadcrumb: 'Tutorials List'}
+ data:{title: 'Tutorials List', icon:'list', nav:true}
},
{
path: 'tutorial-preview',
loadComponent: () => import('../moderator-module/tutorial-preview.component/tutorial-preview.component').then((c) => c.TutorialPreviewComponent),
- data:{breadcrumb: 'Tutorial Preview'}
+ data:{title: 'Tutorial Preview'}
}
]
}
];
-export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTES);
+export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTS);
diff --git a/jambotron-ui/src/app/modules/user-module/user.component/user.component.html b/jambotron-ui/src/app/modules/user-module/user.component/user.component.html
index 85123bf..04c4e93 100644
--- a/jambotron-ui/src/app/modules/user-module/user.component/user.component.html
+++ b/jambotron-ui/src/app/modules/user-module/user.component/user.component.html
@@ -1,10 +1,2 @@
-
-
-
+
diff --git a/jambotron-ui/src/app/modules/user-module/user.component/user.component.ts b/jambotron-ui/src/app/modules/user-module/user.component/user.component.ts
index 54c1832..28471e5 100644
--- a/jambotron-ui/src/app/modules/user-module/user.component/user.component.ts
+++ b/jambotron-ui/src/app/modules/user-module/user.component/user.component.ts
@@ -4,43 +4,24 @@ import {MatSidenav} from '@angular/material/sidenav';
import {BreakpointObserver} from '@angular/cdk/layout';
import {SideBarUserComponent} from '../side-bar-user.component/side-bar-user.component';
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
+import {InnerLayoutComponent} from '../../../components/inner-layout.component/inner-layout.component';
+import {MODERATOR_ROUTS} from '../../moderator-module/moderator.routing';
+import {USER_ROUTS} from '../user.routing';
@Component({
selector: 'app-user.component',
imports: [
RouterOutlet,
SideBarUserComponent,
- BreadcrumbsComponent
+ BreadcrumbsComponent,
+ InnerLayoutComponent
],
templateUrl: './user.component.html',
styleUrl: './user.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
-export class UserComponent implements OnInit {
- @ViewChild(MatSidenav)
- sidenav!: MatSidenav;
- isMobile= true;
- isCollapsed = true;
+export class UserComponent {
-
- constructor(private observer: BreakpointObserver) {
-
- }
-
- ngOnInit(): void {
- this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
- this.isMobile = screenSize.matches;
- })
-
-
- }
- toggleMenu() {
- if(this.isMobile){
- this.sidenav.toggle();
- this.isCollapsed = false;
- } else {
- this.sidenav.open();
- this.isCollapsed = !this.isCollapsed;
- }
- }
+ userRouts = USER_ROUTS;
+ basePath = '/main/user';
}
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 2b18ea7..9d1a946 100644
--- a/jambotron-ui/src/app/modules/user-module/user.routing.ts
+++ b/jambotron-ui/src/app/modules/user-module/user.routing.ts
@@ -2,47 +2,47 @@ import {RouterModule, Routes} from '@angular/router';
import {UserComponent} from './user.component/user.component';
-const USER_ROUTES: Routes = [
+export const USER_ROUTS: Routes = [
{
path: '',
component: UserComponent,
- data:{breadcrumb: 'User'},
+ data:{title: 'User'},
children: [
{ path: '', pathMatch: 'full', redirectTo: 'user-welcome' }, // default redirect
{
path: 'user-welcome',
loadComponent: () => import('../user-module/user-welcome.component/user-welcome.component').then((c) => c.UserWelcomeComponent),
- data:{breadcrumb: 'Welcome'}
+ data:{title: 'Welcome', icon:'dashboard', nav:true}
},
{
path: 'tutorials-list',
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent),
- data:{breadcrumb: 'Tutorials List'}
+ data:{title: 'Tutorials List', icon:'list', nav:true}
},
{
path: 'tutorial-add',
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent),
- data:{breadcrumb: 'Add Tutorial'}
+ data:{title: 'Add Tutorial'}
},
{
path: 'tutorial-edit',
loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent),
- data:{breadcrumb: 'Edit Tutorial'}
+ data:{title: 'Edit Tutorial'}
},
{
path: 'ai-models',
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent),
- data:{breadcrumb: 'AI'}
+ data:{title: 'AI'}
},
{
path: 'images',
loadComponent: () => import('../user-module/images.component/images.component').then((c) => c.ImagesComponent),
- data:{breadcrumb: 'Images'}
+ data:{title: 'Images', icon: 'imagesmode', nav:true}
}
]
}
];
-export const userRouting = RouterModule.forChild(USER_ROUTES);
+export const userRouting = RouterModule.forChild(USER_ROUTS);
diff --git a/jambotron-ui/src/app/services/spinner.service.ts b/jambotron-ui/src/app/services/spinner.service.ts
index c4fa063..5c7aef5 100644
--- a/jambotron-ui/src/app/services/spinner.service.ts
+++ b/jambotron-ui/src/app/services/spinner.service.ts
@@ -10,10 +10,10 @@ export class SpinnerService {
}
show() {
- this.visibility.next(true);
+ this.visibility.next(false);
}
hide() {
- this.visibility.next(false);
+ this.visibility.next(true);
}
}
diff --git a/jambotron-ui/src/app/services/user.service.ts b/jambotron-ui/src/app/services/user.service.ts
index f6b1764..9fe6423 100644
--- a/jambotron-ui/src/app/services/user.service.ts
+++ b/jambotron-ui/src/app/services/user.service.ts
@@ -24,8 +24,6 @@ export class UserService {
return this.http.get(API_URL + '/mod', { responseType: 'text' });
}
- getAdminBoard(): Observable {
- return this.http.get(API_URL);
- }
+
}
diff --git a/jambotron-ui/src/index.html b/jambotron-ui/src/index.html
index aa574b3..e054db8 100644
--- a/jambotron-ui/src/index.html
+++ b/jambotron-ui/src/index.html
@@ -7,8 +7,8 @@
-
+
diff --git a/jambotron-ui/src/index_dev.html b/jambotron-ui/src/index_dev.html
index 944af06..3781ee0 100644
--- a/jambotron-ui/src/index_dev.html
+++ b/jambotron-ui/src/index_dev.html
@@ -7,6 +7,8 @@
+
diff --git a/jambotron-ui/src/styles.scss b/jambotron-ui/src/styles.scss
index c22d8a1..32972ed 100644
--- a/jambotron-ui/src/styles.scss
+++ b/jambotron-ui/src/styles.scss
@@ -49,11 +49,11 @@ body {
}
.pc-container{
top: 0;
- padding-left: 5px;
- padding-right: 5px;
+ padding-left: 20px;
+ padding-right: 20px;
position: relative;
- margin-left: 260px;
+ margin-left: 240px;
min-height: calc(100vh - 113px);
background: #fafafbb3;
}
diff --git a/src/main/java/com/jambotronGroup/jambotron/DTOs/RoleDto.java b/src/main/java/com/jambotronGroup/jambotron/DTOs/RoleDto.java
new file mode 100644
index 0000000..fdc0e17
--- /dev/null
+++ b/src/main/java/com/jambotronGroup/jambotron/DTOs/RoleDto.java
@@ -0,0 +1,48 @@
+package com.jambotronGroup.jambotron.DTOs;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jambotronGroup.jambotron.model.ERole;
+import com.jambotronGroup.jambotron.model.Role;
+
+public class RoleDto {
+ @JsonIgnore
+ private Long id;
+ private ERole name;
+
+ public RoleDto() {
+ }
+
+ public RoleDto(Long id, ERole name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public ERole getName() {
+ return name;
+ }
+
+ public void setName(ERole name) {
+ this.name = name;
+ }
+
+ // Mapping helpers
+ public static RoleDto fromEntity(Role role) {
+ if (role == null) return null;
+ return new RoleDto(role.getId(), role.getName());
+ }
+
+ public Role toEntity() {
+ Role role = new Role();
+ role.setId(this.id);
+ role.setName(this.name);
+ return role;
+ }
+}
diff --git a/src/main/java/com/jambotronGroup/jambotron/DTOs/TutorialDto.java b/src/main/java/com/jambotronGroup/jambotron/DTOs/TutorialDto.java
new file mode 100644
index 0000000..166bfb0
--- /dev/null
+++ b/src/main/java/com/jambotronGroup/jambotron/DTOs/TutorialDto.java
@@ -0,0 +1,145 @@
+package com.jambotronGroup.jambotron.DTOs;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jambotronGroup.jambotron.model.Tutorial;
+
+import java.sql.Timestamp;
+
+public class TutorialDto {
+ @JsonIgnore
+ private Long id;
+ private String title;
+ private String description;
+ private Boolean published;
+
+ private boolean tobepublished;
+
+ private Long userID;
+
+ private java.sql.Timestamp created;
+
+ private java.sql.Timestamp modified;
+
+ private String titleimage;
+
+ private String body;
+
+ public TutorialDto() {
+ }
+
+ public TutorialDto(Long id, String title, String description, Boolean published, boolean tobepublished, Long userID, Timestamp created, Timestamp modified, String titleimage, String body) {
+ this.id = id;
+ this.title = title;
+ this.description = description;
+ this.published = published;
+ this.tobepublished = tobepublished;
+ this.userID = userID;
+ this.created = created;
+ this.modified = modified;
+ this.titleimage = titleimage;
+ this.body = body;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Boolean getPublished() {
+ return published;
+ }
+
+ public void setPublished(Boolean published) {
+ this.published = published;
+ }
+
+ public boolean isTobepublished() {
+ return tobepublished;
+ }
+ public void setTobepublished(boolean tobepublished) {
+ this.tobepublished = tobepublished;
+ }
+ public Long getUserID() {
+ return userID;
+ }
+ public void setUserID(Long userID) {
+ this.userID = userID;
+ }
+ public Timestamp getCreated() {
+ return created;
+ }
+ public void setCreated(Timestamp created) {
+ this.created = created;
+ }
+ public Timestamp getModified() {
+ return modified;
+ }
+ public void setModified(Timestamp modified) {
+ this.modified = modified;
+ }
+ public String getTitleimage() {
+ return titleimage;
+ }
+ public void setTitleimage(String titleimage) {
+ this.titleimage = titleimage;
+ }
+ public String getBody() {
+ return body;
+ }
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ // Mapping helpers
+ public static TutorialDto fromEntity(Tutorial tutorial) {
+ if (tutorial == null) return null;
+ return new TutorialDto(
+ tutorial.getId(),
+ tutorial.getTitle(),
+ tutorial.getDescription(),
+ tutorial.isPublished(),
+ tutorial.isTobepublished(),
+ tutorial.getUser().getId(),
+ tutorial.getCreated(),
+ tutorial.getModified(),
+ tutorial.getTitleimage(),
+ tutorial.getBody()
+ );
+ }
+
+ public Tutorial toEntity() {
+ Tutorial tutorial = new Tutorial();
+ tutorial.setId(this.id);
+ tutorial.setTitle(this.title);
+ tutorial.setDescription(this.description);
+ if (this.published != null) {
+ tutorial.setPublished(this.published);
+ }
+ tutorial.setCreated(this.created);
+ tutorial.setModified(this.modified);
+ tutorial.setTobepublished(this.tobepublished);
+ tutorial.setTitleimage(this.titleimage);
+ tutorial.setBody(this.body);
+
+ return tutorial;
+ }
+}
diff --git a/src/main/java/com/jambotronGroup/jambotron/DTOs/UserDto.java b/src/main/java/com/jambotronGroup/jambotron/DTOs/UserDto.java
new file mode 100644
index 0000000..de04d49
--- /dev/null
+++ b/src/main/java/com/jambotronGroup/jambotron/DTOs/UserDto.java
@@ -0,0 +1,102 @@
+package com.jambotronGroup.jambotron.DTOs;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.jambotronGroup.jambotron.model.User;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class UserDto {
+ @JsonIgnore
+ private Long id;
+ private String username;
+ private String email;
+
+ private String password; // typically you don't expose password in DTOs
+
+ private Set roles; // typically you don't expose password in DTOs
+
+
+ public UserDto() {
+ }
+
+ public UserDto(Long id, String username, String email, String password, Set roles) {
+ this.id = id;
+ this.username = username;
+ this.email = email;
+ this.password = password;
+ this.roles = roles;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Set roles) {
+ this.roles = roles;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ // Mapping helpers
+ public static UserDto fromEntity(User user) {
+ if (user == null) return null;
+ return new UserDto(
+ user.getId(),
+ user.getUsername(),
+ user.getEmail(),
+ user.getPassword(),
+ user.getRoles() == null ? null :
+ user.getRoles().stream()
+ .filter(Objects::nonNull)
+ .map(RoleDto::fromEntity)
+ .collect(Collectors.toSet())
+ );
+ }
+
+ public User toEntity() {
+ User user = new User();
+ user.setId(this.id);
+ user.setUsername(this.username);
+ user.setEmail(this.email);
+ user.setPassword(this.password); // Assuming password is already hashed
+ if (this.roles != null) {
+ user.setRoles(this.roles.stream()
+ .filter(Objects::nonNull)
+ .map(RoleDto::toEntity)
+ .collect(Collectors.toSet()));
+ }
+ return user;
+ }
+}
diff --git a/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java b/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java
index 3c64e86..7cfd134 100644
--- a/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java
+++ b/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java
@@ -2,9 +2,11 @@ package com.jambotronGroup.jambotron;
import com.jambotronGroup.jambotron.fileUpload.FilesStorageService;
+
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -14,6 +16,9 @@ public class JambotronApplication implements CommandLineRunner {
@Resource
FilesStorageService storageService;
+// @Autowired
+// UserExportService userExportService;
+
private static final Logger logger = LoggerFactory.getLogger(JambotronApplication.class);
public static void main(String[] args) {
@@ -29,6 +34,14 @@ public class JambotronApplication implements CommandLineRunner {
public void run(String... arg) throws Exception {
// storageService.deleteAll();
storageService.init();
+
+// try {
+// userExportService.exportAllRolesToJson();
+// userExportService.exportAllUsersToJson();
+//
+// } catch (Exception e) {
+// logger.error("Error exporting users or roles to JSON", e);
+// }
}
}
diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java
new file mode 100644
index 0000000..3d75495
--- /dev/null
+++ b/src/main/java/com/jambotronGroup/jambotron/controllers/JsonDumpController.java
@@ -0,0 +1,347 @@
+package com.jambotronGroup.jambotron.controllers;
+
+import com.jambotronGroup.jambotron.jsonDump.JsonDumpService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toList;
+
+@RestController
+@RequestMapping("/api/admin/json-dump")
+@PreAuthorize("hasRole('ADMIN')")
+public class JsonDumpController {
+
+ private static final Logger _logger = LoggerFactory.getLogger(JsonDumpController.class);
+
+ @Autowired
+ private JsonDumpService _jsonDumpService;
+
+ // ==================== EXPORT ENDPOINTS ====================
+
+ @PostMapping("/export/all")
+ public ResponseEntity