diff --git a/jambotron-ui/package.json b/jambotron-ui/package.json index f976e95..4e9a8bc 100644 --- a/jambotron-ui/package.json +++ b/jambotron-ui/package.json @@ -24,8 +24,6 @@ "@angular/platform-server": "^20.0.0", "@angular/router": "^20.0.0", "@angular/ssr": "^20.0.2", - "@ant-design/icons-angular": "19.0.0", - "@hreimer/angular-image-viewer": "^0.14.1", "@ng-bootstrap/ng-bootstrap": "19.0.0", "@popperjs/core": "2.11.8", "@primeng/themes": "^19.1.3", @@ -33,11 +31,7 @@ "bootstrap": "5.3.6", "cropperjs": "^2.0.1", "express": "^5.1.0", - "file-saver": "^2.0.5", - "ngx-filesaver": "^20.0.0", "ngx-markdown": "^20.0.0", - "ngx-scrollbar": "18.0.0", - "primeng": "^19.1.3", "prismjs": "^1.30.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", diff --git a/jambotron-ui/src/app/app.module.ts b/jambotron-ui/src/app/app.module.ts index 6ef61e8..fb4aa21 100644 --- a/jambotron-ui/src/app/app.module.ts +++ b/jambotron-ui/src/app/app.module.ts @@ -17,6 +17,7 @@ import {AppRoutingModule} from './app.routes'; import {App} from './app'; import {CustomHttpInterceptor} from './helpers/custom-http-interceptor'; import {AngularMarkdownEditorModule} from 'angular-markdown-editor'; +import {MatFormFieldModule} from '@angular/material/form-field'; @NgModule({ @@ -33,7 +34,8 @@ import {AngularMarkdownEditorModule} from 'angular-markdown-editor'; AdminWelcomeComponent, SettingsComponent, SystemComponent, - AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' }) + AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' }), + MatFormFieldModule ], providers: [ authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{ diff --git a/jambotron-ui/src/app/components/file-upload.component/file-upload.component.html b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.html new file mode 100644 index 0000000..18201d4 --- /dev/null +++ b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.html @@ -0,0 +1,40 @@ +
+ +
+ + + + + +
+
+
+ +@if (progress) { + + + {{ progress }}% + +} + +@if (message) { +
+ {{ message }} +
+} + +@if (fileInfo){ + +} diff --git a/jambotron-ui/src/app/components/file-upload.component/file-upload.component.scss b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.scss new file mode 100644 index 0000000..2052132 --- /dev/null +++ b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.scss @@ -0,0 +1,47 @@ +.progress-bar { + padding: 0; +} + +.progress { + width: 50px; +} + +#fileInput { + position: absolute; + cursor: pointer; + z-index: 10; + opacity: 0; + height: 100%; + left: 0px; + top: 0px; +} + +.mat-toolbar-single-row { + height: auto !important; + background: transparent; + padding: 0; +} + +.mat-toolbar-single-row button { + width: 100px; +} + +.mat-form-field { + width: 100%; +} + +.mat-mdc-form-field { + display: block; +} + +.message { + background-color: #ddd; + padding: 15px; + color: #333; + border: #aaa solid 1px; + border-radius: 4px; + margin-bottom: 10px; +} +img{ + max-width: -webkit-fill-available; +} diff --git a/jambotron-ui/src/app/components/file-upload.component/file-upload.component.spec.ts b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.spec.ts new file mode 100644 index 0000000..dd2958d --- /dev/null +++ b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileUploadComponent } from './file-upload.component'; + +describe('FileUploadComponent', () => { + let component: FileUploadComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileUploadComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FileUploadComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/jambotron-ui/src/app/components/file-upload.component/file-upload.component.ts b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.ts new file mode 100644 index 0000000..5ef375e --- /dev/null +++ b/jambotron-ui/src/app/components/file-upload.component/file-upload.component.ts @@ -0,0 +1,80 @@ +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {MatList, MatListItem} from '@angular/material/list'; +import {AsyncPipe} from '@angular/common'; +import {MatToolbar} from '@angular/material/toolbar'; +import {MatProgressBar} from '@angular/material/progress-bar'; +import {MatFormField, MatInput} from '@angular/material/input'; +import {Observable} from 'rxjs'; +import {HttpEventType, HttpResponse} from '@angular/common/http'; +import {MatButton} from '@angular/material/button'; +import {UserApiService} from '../../modules/user-module/user-api.service'; +import {FileInfo} from '../../models/file-info'; + +@Component({ + selector: 'app-file-upload', + imports: [ + MatToolbar, + MatProgressBar, + MatFormField, + MatButton, + MatInput + ], + templateUrl: './file-upload.component.html', + styleUrl: './file-upload.component.scss' +}) +export class FileUploadComponent { + currentFile?: File; + progress = 0; + message = ''; + + fileName = 'Select File'; + + @Output() fileInfo: FileInfo | undefined; + @Output() onImageUploaded = new EventEmitter(); + constructor(private userApiService: UserApiService) {} + + selectFile(event: any): void { + this.progress = 0; + this.message = ''; + + if (event.target.files && event.target.files[0]) { + const file: File = event.target.files[0]; + this.currentFile = file; + this.fileName = this.currentFile.name; + } else { + this.fileName = 'Select File'; + } + } + + upload(): void { + if (this.currentFile) { + this.userApiService.upload(this.currentFile).subscribe({ + next: (event: any) => { + if (event.type === HttpEventType.UploadProgress) { + this.progress = Math.round((100 * event.loaded) / event.total); + } else if (event instanceof HttpResponse) { + this.message = event.body.message; + this.fileInfo =event.body.fileInfo; + this.onImageUploaded.emit(this.fileInfo); + console.log(event.body); + } + }, + error: (err: any) => { + console.log(err); + this.progress = 0; + + if (err.error && err.error.message) { + this.message = err.error.message; + } else { + this.message = 'Could not upload the file!'; + this.onImageUploaded.emit(undefined); + } + }, + complete: () => { + this.currentFile = undefined; + }, + }); + } + } +} diff --git a/jambotron-ui/src/app/helpers/auth.interceptor.ts b/jambotron-ui/src/app/helpers/auth.interceptor.ts index 7145b6b..7769749 100644 --- a/jambotron-ui/src/app/helpers/auth.interceptor.ts +++ b/jambotron-ui/src/app/helpers/auth.interceptor.ts @@ -19,9 +19,9 @@ export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { - req = req.clone({ - withCredentials: true, - }); + // req = req.clone({ + // withCredentials: true, + // }); return next.handle(req).pipe( catchError((error) => { diff --git a/jambotron-ui/src/app/helpers/custom-http-interceptor.ts b/jambotron-ui/src/app/helpers/custom-http-interceptor.ts index df18c65..04222ec 100644 --- a/jambotron-ui/src/app/helpers/custom-http-interceptor.ts +++ b/jambotron-ui/src/app/helpers/custom-http-interceptor.ts @@ -1,4 +1,4 @@ -import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; +import {HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {Observable, tap} from 'rxjs'; import {SpinnerService} from '../services/spinner.service'; import {Injectable} from '@angular/core'; @@ -19,6 +19,7 @@ export class CustomHttpInterceptor implements HttpInterceptor { } }, (error) => { this.spinnerService.hide(); - })); + })) + ; } } diff --git a/jambotron-ui/src/app/modules/ai-module/generate-image.component/generate-image.component.ts b/jambotron-ui/src/app/modules/ai-module/generate-image.component/generate-image.component.ts index 00b772c..70df3d9 100644 --- a/jambotron-ui/src/app/modules/ai-module/generate-image.component/generate-image.component.ts +++ b/jambotron-ui/src/app/modules/ai-module/generate-image.component/generate-image.component.ts @@ -1,33 +1,23 @@ -import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, inject, Renderer2} from '@angular/core'; -import {AsyncPipe, DOCUMENT, NgOptimizedImage} from "@angular/common"; +import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core'; +import {AsyncPipe} from "@angular/common"; import {FormsModule} from "@angular/forms"; import {MatButton, MatMiniFabButton} from "@angular/material/button"; import { MatCard, MatCardActions, MatCardContent, - MatCardHeader, - MatCardImage, - MatCardXlImage + MatCardHeader } from "@angular/material/card"; import {MatFormField, MatInput, MatLabel} from "@angular/material/input"; import {Image} from '../../../models/image'; import {ZhipuaiImageService} from '../zhipuai-image.service'; import {SpinnerService} from '../../../services/spinner.service'; import {MatProgressSpinner} from '@angular/material/progress-spinner'; - -import {FileUploadService} from '../../../services/file-upload-api.service'; -import {Observable, Subscription} from 'rxjs'; -import {HttpClient, HttpResponse} from '@angular/common/http'; import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar'; import {MatIcon} from '@angular/material/icon'; import {MatTooltip} from '@angular/material/tooltip'; -import {AuthService} from '../../../services/auth.service'; import {TokenStorageService} from '../../../services/token-storage.service'; -import {FileInfo} from '../../../models/file-info'; import {UserApiService} from '../../user-module/user-api.service'; -import {MatProgressBar} from '@angular/material/progress-bar'; - @Component({ selector: 'app-generate-image.component', @@ -43,26 +33,17 @@ import {MatProgressBar} from '@angular/material/progress-bar'; MatInput, MatLabel, MatProgressSpinner, - NgOptimizedImage, - MatCardXlImage, - MatCardImage, MatToolbarRow, MatToolbar, MatIcon, MatMiniFabButton, MatTooltip, - MatProgressBar - - ], templateUrl: './generate-image.component.html', styleUrl: './generate-image.component.scss', schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GenerateImageComponent { - - isSpinnerVisible = false; - query : string = ''; images: Image[] = []; @@ -70,10 +51,6 @@ export class GenerateImageComponent { url: '' }; - currentFile?: File; - message = ''; - - isLoggedIn = false; private storageService: TokenStorageService = inject(TokenStorageService); @@ -81,13 +58,8 @@ export class GenerateImageComponent { constructor( private zhipuaiImageService: ZhipuaiImageService, public spinnerService: SpinnerService, - private uploadService: FileUploadService, - private userApiService:UserApiService, - - + private userApiService:UserApiService ) { - - this.isLoggedIn = this.storageService.isLoggedIn(); } @@ -108,7 +80,6 @@ export class GenerateImageComponent { ) } - save(url: string | undefined) { let requestUrl:string = url?url:""; console.log(requestUrl); @@ -118,9 +89,6 @@ export class GenerateImageComponent { console.log(error); }); } - - - } diff --git a/jambotron-ui/src/app/modules/tutorials-module/tutorials.module.ts b/jambotron-ui/src/app/modules/tutorials-module/tutorials.module.ts index b2d60c5..5d1f0a6 100644 --- a/jambotron-ui/src/app/modules/tutorials-module/tutorials.module.ts +++ b/jambotron-ui/src/app/modules/tutorials-module/tutorials.module.ts @@ -2,13 +2,15 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import {tutorialsRouting} from './tutorials.routing'; import {TutorialsComponent} from './tutorials.component/tutorials.component'; +import {MatFormFieldModule} from '@angular/material/form-field'; @NgModule({ declarations: [], imports: [ tutorialsRouting, TutorialsComponent, - CommonModule + CommonModule, + MatFormFieldModule ] }) export class TutorialsModule { } diff --git a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.html b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.html index ba7a007..4210267 100644 --- a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.html +++ b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.html @@ -1,5 +1,5 @@

Hi

-
+ @for (fileInfo of fileInfos; track fileInfo){ @@ -16,7 +16,7 @@ -
+
diff --git a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.scss b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.scss index ad0d4e1..6a02f8a 100644 --- a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.scss +++ b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.scss @@ -2,20 +2,30 @@ flex: 1 1 auto; } -.mat-dialog-content{ +.mat-mdc-list-item{ + +} +a.mdc-list-item +{ + cursor: grab; + height: 120px; + padding-bottom: 66px; + background-color: rgba(24,255,255,0.04); +} +/*.mat-dialog-content{ min-height: 300px; min-width: 300px; height: 75%; width: 75%; -} -.mdc-dialog--open .mat-mdc-dialog-inner-container +}*/ +/*.mdc-dialog--open .mat-mdc-dialog-inner-container { opacity: 1; width: 600px; -} +}*/ -.mat-mdc-dialog-container { +/*.mat-mdc-dialog-container { width: 600px; height: 500px; display: block; @@ -25,4 +35,9 @@ min-width: inherit; max-width: inherit; outline: 0; +}*/ +/* +.mat-mdc-dialog-content{ + overflow: auto; } +*/ diff --git a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.ts b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.ts index d030b1a..3dce61e 100644 --- a/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.ts +++ b/jambotron-ui/src/app/modules/user-module/dialog-select-image.component/dialog-select-image.component.ts @@ -7,8 +7,7 @@ import { MatDialogTitle } from '@angular/material/dialog'; import {MatButton} from '@angular/material/button'; -import {MatListItem, MatNavList} from '@angular/material/list'; -import {RouterLink} from '@angular/router'; +import {MatListItem} from '@angular/material/list'; import {FileInfo} from '../../../models/file-info'; import {UserApiService} from '../user-api.service'; @@ -20,10 +19,7 @@ import {UserApiService} from '../user-api.service'; MatButton, MatDialogActions, MatDialogClose, - - MatListItem, - MatNavList, - + MatListItem ], templateUrl: './dialog-select-image.component.html', styleUrl: './dialog-select-image.component.scss' @@ -42,10 +38,10 @@ export class DialogSelectImageComponent { console.log(data); }); } + + //open upload dialog upload() { - this.uploadClicked.emit(); - } onNoClick() { @@ -54,5 +50,6 @@ export class DialogSelectImageComponent { select(fileInfo:FileInfo) { this.selectClicked.emit(fileInfo); + this.dialogRef.close(); } } diff --git a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.html b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.html new file mode 100644 index 0000000..07b03c7 --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.html @@ -0,0 +1,14 @@ +

dialog-upload-image.component works!

+ + + + +
+ + + + + + + +
diff --git a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.scss b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.scss new file mode 100644 index 0000000..827c30d --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.scss @@ -0,0 +1,6 @@ +.menu-spacer { + flex: 1 1 auto; +} + + + diff --git a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.spec.ts b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.spec.ts new file mode 100644 index 0000000..7e0f85f --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DialogUploadImageComponent } from './dialog-upload-image.component'; + +describe('DialogUploadImageComponent', () => { + let component: DialogUploadImageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DialogUploadImageComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DialogUploadImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts new file mode 100644 index 0000000..ae2879f --- /dev/null +++ b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts @@ -0,0 +1,54 @@ +import {Component, EventEmitter, inject, Output, ViewChild} from '@angular/core'; +import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component'; +import {MatDialogActions, MatDialogClose, MatDialogContent, MatDialogRef} from '@angular/material/dialog'; +import {MatButton} from '@angular/material/button'; +import {FileInfo} from '../../../models/file-info'; + +@Component({ + selector: 'app-dialog-upload-image.component', + imports: [ + FileUploadComponent, + MatDialogContent, + MatButton, + MatDialogActions, + MatDialogClose + ], + templateUrl: './dialog-upload-image.component.html', + styleUrl: './dialog-upload-image.component.scss' +}) +export class DialogUploadImageComponent { + //select uploaded file + @Output() selectUploadedImageClicked = new EventEmitter(); + + //open select dialog + @Output() openSelectDialogClicked = new EventEmitter(); + + readonly dialogRef = inject(MatDialogRef); + + @ViewChild('imageUpload') imageUpload: FileUploadComponent | undefined; + + imageUploaded:boolean = false; + + onNoClick() { + this.dialogRef.close(); + } + + selectUploadedImage() { + this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo); + this.dialogRef.close(); + } + + openSelectImageDialog() { + this.openSelectDialogClicked.emit(); + this.dialogRef.close(); + } + + imageUpload_ImageUploaded($event: FileInfo | undefined) { + if($event){ + this.imageUploaded = true; + } + else{ + this.imageUploaded = false; + } + } +} diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html index 6dacd62..571308f 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html @@ -4,38 +4,55 @@ - Title - + Title - - Use title image - - - - - -
- Description - - -
-
-
- - -
+ Title image + + + + + + +
+ + + + Title image + + +
+ +
+ +
+ +
+ Description + + + + + + +
+
+
+ + +
+
@@ -47,26 +64,26 @@
-
- Body - - -
-
-
- - -
-
- -
+
+ Body + + + + + + + +
+ + +
diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss index b5cf161..1ee9028 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss @@ -9,9 +9,7 @@ .markdown-editor-container{ display: flex; } -mat-card{ - margin: 20px; -} + mat-card-title{ color: cyan; } @@ -55,3 +53,17 @@ mat-card-title{ /* display: block; float: right;*/ } + +.label-style{ + font-size: -webkit-xxx-large; + font-weight: bold; +} + + +.mat-mdc-card-outlined { + background-color: var(--mat-card-outlined-container-color, var(--mat-sys-surface)); + border-radius:0; + border-width: var(--mat-card-outlined-outline-width, 1px); + border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant)); + box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0)); +} diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts index 34a29e9..adce58d 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts @@ -12,10 +12,11 @@ import {MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {MatTab, MatTabGroup} from '@angular/material/tabs'; import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor'; -import {DialogLoginComponent} from '../../main-module/main.component/dialog-login.component/dialog-login.component'; -import {GlobalConstants} from '../../../global-constants'; import {MatDialog} from '@angular/material/dialog'; import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component'; +import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component'; +import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component'; +import {MatDivider} from '@angular/material/divider'; @Component({ selector: 'app-tutorial-add.component', @@ -33,7 +34,8 @@ import {DialogSelectImageComponent} from '../dialog-select-image.component/dialo MatTabGroup, ReactiveFormsModule, FormsModule, - AngularMarkdownEditorModule + AngularMarkdownEditorModule, + MatDivider ], templateUrl: './tutorial-add.component.html', styleUrl: './tutorial-add.component.scss', @@ -43,14 +45,16 @@ export class TutorialAddComponent implements OnInit{ tutorial: Tutorial = new Tutorial(); submitted = false; - bsEditorInstance!: EditorInstance; markdownText = ''; showEditor = true; + bsEditorInstance!: EditorInstance; tutorialForm!: FormGroup; editorOptions!: EditorOption; readonly dialog = inject(MatDialog); + + markdown = `## Markdown __rulez__! --- @@ -72,10 +76,6 @@ const language = 'typescript'; constructor(private fb: FormBuilder, private markdownService: MarkdownService, private userApiService: UserApiService, - private storageService: TokenStorageService, - private eventBusService: EventBusService, - private router: Router, - private authService: AuthService ) { this.tutorial.description = this.markdown; @@ -86,6 +86,7 @@ const language = 'typescript'; this.editorOptions = { autofocus: false, iconlibrary: 'fa', + height: 300, savable: false, onFullscreenExit: (e) => this.hidePreview(), onShow: (e) => this.bsEditorInstance = e, @@ -101,9 +102,7 @@ const language = 'typescript'; isPreview: [true] }); } - selectImage() { - throw new Error('Method not implemented.'); - } + /** highlight all code found, needs to be wrapped in timer to work properly */ highlight() { setTimeout(() => { @@ -185,7 +184,7 @@ const language = 'typescript'; }); dialogSelectRef.componentInstance.uploadClicked.subscribe(result => { dialogSelectRef.close(); - //this.openSignupDialog(enterAnimationDuration, exitAnimationDuration); + this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration); }) const dialogSelectSubscription = dialogSelectRef.componentInstance.selectClicked @@ -199,4 +198,29 @@ const language = 'typescript'; }); } + + openUploadImageDialog(enterAnimationDuration: string, exitAnimationDuration: string) { + let dialogSelectRef = this.dialog.open(DialogUploadImageComponent, { + height: '500px', + width: '600px', + enterAnimationDuration, + exitAnimationDuration, + // data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password} + }); + dialogSelectRef.componentInstance.openSelectDialogClicked.subscribe(result => { + dialogSelectRef.close(); + this.openSelectImageDialog(enterAnimationDuration, exitAnimationDuration); + }) + + const dialogUploadSubscription = dialogSelectRef.componentInstance.selectUploadedImageClicked + .subscribe(result => { + console.log('Got the data!', result); + + if (result == null) { + return; + } + this.tutorial.titleimage = result.url; + + }); + } } diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html index 9a3eefd..fbd0be4 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html +++ b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html @@ -4,23 +4,97 @@ - Title + Title - - - - - - - - - - - - - + + Title image + + + + + + +
+ + + + Title image + + +
+ + +
+ +
+ +
+ Description + + + + + + + +
+
+
+ + +
+
+
+
+ + + + + + + +
+
+ +
+ Body + + + + + + + +
+ + +
+
+ + + + + + + +
+
+
diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts index 9898445..0872c20 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts +++ b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts @@ -1,13 +1,18 @@ -import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {MarkdownComponent} from 'ngx-markdown'; +import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core'; +import {MarkdownComponent, MarkdownService} from 'ngx-markdown'; import {MatButton} from '@angular/material/button'; import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {MatTab, MatTabGroup} from '@angular/material/tabs'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Tutorial} from '../../../models/tutorial.model'; import {UserApiService} from '../user-api.service'; import {ActivatedRoute, RouterLink} from '@angular/router'; +import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor'; +import {MatDivider} from '@angular/material/divider'; +import {MatDialog} from '@angular/material/dialog'; +import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component'; +import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component'; @Component({ selector: 'app-tutorial-edit.component', @@ -27,17 +32,28 @@ import {ActivatedRoute, RouterLink} from '@angular/router'; FormsModule, MatFormField, RouterLink, - MatError + MatError, + AngularMarkdownEditorModule, + MatDivider ], templateUrl: './tutorial-edit.component.html', styleUrl: './tutorial-edit.component.scss', schemas: [CUSTOM_ELEMENTS_SCHEMA] }) -export class TutorialEditComponent { +export class TutorialEditComponent implements OnInit{ + tutorial: Tutorial = new Tutorial(); submitted = false; hasError = false; errorMessage = ''; + markdownText=""; + bsEditorInstance!: EditorInstance; + tutorialForm!: FormGroup; + editorOptions!: EditorOption; + + readonly dialog = inject(MatDialog); + + markdown = `## Markdown __rulez__! --- @@ -56,7 +72,11 @@ const language = 'typescript'; > Blockquote to the max`; private id: string | null | undefined; - constructor(private userApiService: UserApiService, private route: ActivatedRoute) { + constructor(private userApiService: UserApiService, + private route: ActivatedRoute, + private fb: FormBuilder, + private markdownService: MarkdownService + ) { this.route.queryParams .subscribe(params => { @@ -71,6 +91,62 @@ const language = 'typescript'; } ); } + + ngOnInit(): void { + this.editorOptions = { + autofocus: false, + iconlibrary: 'fa', + height: 300, + savable: false, + onFullscreenExit: (e) => this.hidePreview(), + onShow: (e) => this.bsEditorInstance = e, + parser: (val) => this.parse(val) + }; + this.buildForm(this.tutorial.description); + + } + + buildForm(markdownText: string | undefined) { + this.tutorialForm = this.fb.group({ + body: [markdownText], + isPreview: [true] + }); + } + /** highlight all code found, needs to be wrapped in timer to work properly */ + highlight() { + setTimeout(() => { + this.markdownService.highlight(); + }); + } + + hidePreview() { + if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) { + this.bsEditorInstance.hidePreview(); + } + } + + showFullScreen(isFullScreen: boolean) { + if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) { + this.bsEditorInstance.showPreview(); + this.bsEditorInstance.setFullscreen(isFullScreen); + } + } + + parse(inputValue: string) { + const markedOutput = this.markdownService.parse(inputValue.trim()); + this.highlight(); + + return markedOutput; + } + + onFormChanges(): void { + this.tutorialForm.valueChanges.subscribe(formData => { + if (formData) { + this.markdownText = formData.body; + } + }); + } + updateTutorial(): void { this.userApiService.update(this.id, this.tutorial) .subscribe( @@ -87,4 +163,55 @@ const language = 'typescript'; this.hasError = true; }); } + + openSelectImageDialog(enterAnimationDuration: string, exitAnimationDuration: string) { + + let dialogSelectRef = this.dialog.open(DialogSelectImageComponent, { + height: '500px', + width: '600px', + enterAnimationDuration, + exitAnimationDuration, + // data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password} + }); + dialogSelectRef.componentInstance.uploadClicked.subscribe(result => { + dialogSelectRef.close(); + this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration); + }) + + const dialogSelectSubscription = dialogSelectRef.componentInstance.selectClicked + .subscribe(result => { + console.log('Got the data!', result); + + if (result == null) { + return; + } + this.tutorial.titleimage = result.url; + + }); + } + + openUploadImageDialog(enterAnimationDuration: string, exitAnimationDuration: string) { + let dialogSelectRef = this.dialog.open(DialogUploadImageComponent, { + height: '500px', + width: '600px', + enterAnimationDuration, + exitAnimationDuration, + // data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password} + }); + dialogSelectRef.componentInstance.openSelectDialogClicked.subscribe(result => { + dialogSelectRef.close(); + this.openSelectImageDialog(enterAnimationDuration, exitAnimationDuration); + }) + + const dialogUploadSubscription = dialogSelectRef.componentInstance.selectUploadedImageClicked + .subscribe(result => { + console.log('Got the data!', result); + + if (result == null) { + return; + } + this.tutorial.titleimage = result.url; + + }); + } } diff --git a/jambotron-ui/src/app/modules/user-module/user-api.service.ts b/jambotron-ui/src/app/modules/user-module/user-api.service.ts index 0675f18..8dfda62 100644 --- a/jambotron-ui/src/app/modules/user-module/user-api.service.ts +++ b/jambotron-ui/src/app/modules/user-module/user-api.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import {forkJoin, Observable} from 'rxjs'; import {Tutorial} from '../../models/tutorial.model'; -import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {HttpClient, HttpEvent, HttpHeaders, HttpRequest} from '@angular/common/http'; import {GlobalConstants} from '../../global-constants'; import {FileInfo} from '../../models/file-info'; @@ -15,6 +15,19 @@ export class UserApiService { } + upload(file: File): Observable> { + const formData: FormData = new FormData(); + formData.append('file', file); + + const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, { + reportProgress: true, + responseType: 'json' + }); + + return this.http.request(req); + } + + saveZhipuaiImage(url: string): Observable { return this.http.post(`${this.baseUrl}/saveZhipuAiImage`, {imageUrl:url}); } diff --git a/jambotron-ui/src/app/modules/user-module/user.module.ts b/jambotron-ui/src/app/modules/user-module/user.module.ts index 93853ae..a0df98e 100644 --- a/jambotron-ui/src/app/modules/user-module/user.module.ts +++ b/jambotron-ui/src/app/modules/user-module/user.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import {userRouting} from './user.routing'; import {UserComponent} from './user.component/user.component'; import {AngularMarkdownEditorModule} from 'angular-markdown-editor'; +import {MatFormFieldModule} from '@angular/material/form-field'; @@ -12,7 +13,8 @@ import {AngularMarkdownEditorModule} from 'angular-markdown-editor'; userRouting, UserComponent, CommonModule, - AngularMarkdownEditorModule + AngularMarkdownEditorModule, + MatFormFieldModule ] }) export class UserModule { } diff --git a/jambotron-ui/src/app/services/file-upload-api.service.spec.ts b/jambotron-ui/src/app/services/file-upload-api.service.spec.ts deleted file mode 100644 index bc34a28..0000000 --- a/jambotron-ui/src/app/services/file-upload-api.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { FileUploadApiService } from './file-upload-api.service'; - -describe('FileUploadApiService', () => { - let service: FileUploadApiService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(FileUploadApiService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/jambotron-ui/src/app/services/file-upload-api.service.ts b/jambotron-ui/src/app/services/file-upload-api.service.ts deleted file mode 100644 index 06c48de..0000000 --- a/jambotron-ui/src/app/services/file-upload-api.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import {GlobalConstants} from '../global-constants'; -import {HttpClient, HttpEvent, HttpRequest} from '@angular/common/http'; -import {Observable} from 'rxjs'; -import {FileInfo} from '../models/file-info'; - -@Injectable({ - providedIn: 'root' -}) -export class FileUploadService { - private baseUrl = GlobalConstants.API_URL + '/file'; - - constructor(private http: HttpClient) {} - - upload(file: File): Observable> { - const formData: FormData = new FormData(); - formData.append('file', file); - - const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, { - responseType: 'json', - }); - - return this.http.request(req); - } - - getFiles(): Observable { - return this.http.get(`${this.baseUrl}/files`); - } -} diff --git a/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java b/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java index 413376c..369760e 100644 --- a/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java +++ b/src/main/java/com/jambotronGroup/jambotron/JambotronApplication.java @@ -5,15 +5,9 @@ import com.jambotronGroup.jambotron.fileUpload.FilesStorageService; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.Marker; -import org.slf4j.event.Level; -import org.slf4j.helpers.BasicMarker; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Import; - -import java.util.Iterator; @SpringBootApplication public class JambotronApplication implements CommandLineRunner { @@ -35,4 +29,5 @@ public class JambotronApplication implements CommandLineRunner { // storageService.deleteAll(); storageService.init(); } + } diff --git a/src/main/java/com/jambotronGroup/jambotron/configuretions/MvcConfig.java b/src/main/java/com/jambotronGroup/jambotron/configuretions/MvcConfig.java deleted file mode 100644 index a637531..0000000 --- a/src/main/java/com/jambotronGroup/jambotron/configuretions/MvcConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jambotronGroup.jambotron.configuretions; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -/* -@Configuration - -@EnableWebMvc -public class MvcConfig implements WebMvcConfigurer { - @Value("${spring.resources.static-locations}") - String resourceLocations; - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - //registry.addResourceHandler("/public/**").addResourceLocations(resourceLocations); - registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/"); - - //registry. - registry.addResourceHandler("/media/**").addResourceLocations("resources/main/public/media/"); - - } -}*/ diff --git a/src/main/java/com/jambotronGroup/jambotron/configuretions/NotFoundHandler.java b/src/main/java/com/jambotronGroup/jambotron/configuretions/NotFoundHandler.java deleted file mode 100644 index df66fd9..0000000 --- a/src/main/java/com/jambotronGroup/jambotron/configuretions/NotFoundHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jambotronGroup.jambotron.configuretions; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.util.ResourceUtils; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.servlet.NoHandlerFoundException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.Charset; -/* -@ControllerAdvice -public class NotFoundHandler { - @Value("${spa.default-file}") - String defaultFile; - - @ExceptionHandler(NoHandlerFoundException.class) - public ResponseEntity renderDefaultPage() { - try { - File indexFile = ResourceUtils.getFile(defaultFile); - FileInputStream inputStream = new FileInputStream(indexFile); - String body = StreamUtils.copyToString(inputStream, Charset.defaultCharset()); - return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(body); - } catch (IOException e) { - e.printStackTrace(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("There was an error completing the action."); - } - } -}*/ diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java index b47f4d8..1641451 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java @@ -1,12 +1,14 @@ package com.jambotronGroup.jambotron.controllers; import com.jambotronGroup.jambotron.fileUpload.FilesStorageService; +import com.jambotronGroup.jambotron.fileUpload.ResponseImageUploadResult; import com.jambotronGroup.jambotron.fileUpload.ResponseMessage; import com.jambotronGroup.jambotron.model.FileInfo; import com.jambotronGroup.jambotron.model.User; import com.jambotronGroup.jambotron.payload.request.SaveZhipuAiImageRequest; import com.jambotronGroup.jambotron.security.AuthenticationFacade; import com.jambotronGroup.jambotron.system.SystemServiceImpl; +import com.jambotronGroup.jambotron.utils.FilesRoutingHelper; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,17 +32,16 @@ import java.util.List; import java.util.stream.Collectors; @Controller -//@CrossOrigin("http://localhost:8081") public class FilesController { private static final Logger logger = LoggerFactory.getLogger(FilesController.class); - @Autowired AuthenticationFacade authenticationFacade; @Autowired FilesStorageService storageService; + @PostMapping("/api/user/saveZhipuAiImage") public ResponseEntity saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) { @@ -52,7 +53,6 @@ public class FilesController { String message = ""; - RestTemplate restTemplate = new RestTemplate(); try { @@ -92,14 +92,21 @@ public class FilesController { return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message)); } - @PostMapping("/api/file/upload") + @PostMapping("/api/user/upload") public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) { String message = ""; try { - storageService.save(file); + User user = authenticationFacade.getUser(); + + String localFullFileName = storageService.save(user.getId().toString(), file); + + FileInfo fileInfo = new FileInfo( + file.getOriginalFilename(), + FilesRoutingHelper.getUserImageUrl(localFullFileName)); + message = "Uploaded the file successfully: " + file.getOriginalFilename(); - return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message)); + return ResponseEntity.status(HttpStatus.OK).body(new ResponseImageUploadResult(message,fileInfo)); } catch (Exception e) { message = "Could not upload the file: " + file.getOriginalFilename() + ". Error: " + e.getMessage(); return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message)); @@ -113,8 +120,8 @@ public class FilesController { List fileInfos = storageService.loadUserImages(user.getId().toString()).map(path -> { String filename = path.getFileName().toString(); - String url = MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getUserImage", path.getFileName().toString()).build().toString(); + + String url = FilesRoutingHelper.getUserImageUrl(filename); return new FileInfo(filename, url); }).collect(Collectors.toList()); @@ -127,9 +134,7 @@ public class FilesController { public ResponseEntity> getListFiles() { List fileInfos = storageService.loadAll().map(path -> { String filename = path.getFileName().toString(); - String url = MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString(); - + String url = FilesRoutingHelper.getPublicImageUrl(filename); return new FileInfo(filename, url); }).collect(Collectors.toList()); diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 99d9bf3..70f1434 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -9,6 +9,7 @@ import com.jambotronGroup.jambotron.repository.TutorialRepository; import com.jambotronGroup.jambotron.repository.UserRepository; import com.jambotronGroup.jambotron.security.AuthenticationFacade; import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; +import com.jambotronGroup.jambotron.utils.FilesRoutingHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -118,8 +119,7 @@ public class TutorialController { String.format("Tutorial_%s",newFilename ) ); - String url = MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString(); + String url = FilesRoutingHelper.getPublicImageUrl(path.getFileName().toString()); try { @@ -160,7 +160,12 @@ public class TutorialController { } @PutMapping("user/tutorial-update/{id}") - public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) { + public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial){ + + User user = authenticationFacade.getUser(); + + + Optional tutorialData = tutorialRepository.findById(id); Map map = new LinkedHashMap(); if (tutorialData.isPresent()) { @@ -169,7 +174,22 @@ public class TutorialController { servTutorial.setDescription(tutorial.getDescription()); servTutorial.setPublished(tutorial.isPublished()); servTutorial.setTobepublished(tutorial.isTobepublished()); + try { + + String imageFileName = FilesStorageServiceImpl.getFileNameFromUrl(tutorial.getTitleimage()); + + String servImageFileName = FilesStorageServiceImpl.getFileNameFromUrl(servTutorial.getTitleimage()); + filesStorageService.deletePublicFile(servImageFileName); + + Path path= filesStorageService.moveFile( + user.getId().toString(), + tutorial.getTitleimage(), + String.format("Tutorial_%s",imageFileName ) + ); + + servTutorial.setTitleimage(FilesRoutingHelper.getPublicImageUrl(path.getFileName().toString())); + servTutorial = tutorialRepository.save(servTutorial); } catch (Exception e) { diff --git a/src/main/java/com/jambotronGroup/jambotron/exceptionHandlers/FileUploadExceptionHandler.java b/src/main/java/com/jambotronGroup/jambotron/exceptionHandlers/FileUploadExceptionHandler.java new file mode 100644 index 0000000..9e69a0c --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/exceptionHandlers/FileUploadExceptionHandler.java @@ -0,0 +1,18 @@ +package com.jambotronGroup.jambotron.exceptionHandlers; + +import com.jambotronGroup.jambotron.fileUpload.ResponseMessage; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +//@ControllerAdvice +//public class FileUploadExceptionHandler extends ResponseEntityExceptionHandler { +// +// @ExceptionHandler(MaxUploadSizeExceededException.class) +// public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException exc) { +// return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("File too large!")); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java index 606f005..b56dd4e 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java @@ -10,7 +10,7 @@ import java.util.stream.Stream; public interface FilesStorageService { public void init(); - public void save(String userID,MultipartFile file); + public String save(String userID,MultipartFile file); public void save(MultipartFile file); @@ -25,4 +25,6 @@ public interface FilesStorageService { public Stream loadUserImages(String userID); public Path moveFile(String userID, 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 aae255c..4b4cee0 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java @@ -39,6 +39,16 @@ public class FilesStorageServiceImpl implements FilesStorageService { return path.substring(path.lastIndexOf('/') + 1); // Extract the file name } + /** + * 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. + * @throws Exception If the file cannot be moved. + */ @Override public Path moveFile(String userID, String url, String newFilename) throws Exception { @@ -57,13 +67,34 @@ public class FilesStorageServiceImpl implements FilesStorageService { } @Override - public void save(String userID,MultipartFile file) { + public void deletePublicFile(String filename) { + try { + Path filePath = this.rootPublic.resolve(filename); + Files.deleteIfExists(filePath); + } catch (IOException e) { + throw new RuntimeException("Could not delete the file: " + e.getMessage()); + } + } + + /** + * 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) { + Path targetPath = null; try { Path path = this.root.resolve(userID); path.toFile().mkdirs(); // Ensure user directory exists - Files.copy(file.getInputStream(), path.resolve(file.getOriginalFilename()), + targetPath = path.resolve(file.getOriginalFilename()); + + Files.copy(file.getInputStream(), targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { if (e instanceof FileAlreadyExistsException) { throw new RuntimeException("A file of that name already exists."); @@ -71,6 +102,8 @@ public class FilesStorageServiceImpl implements FilesStorageService { throw new RuntimeException(e.getMessage()); } + + return targetPath.getFileName().toString(); } @Override diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseImageUploadResult.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseImageUploadResult.java new file mode 100644 index 0000000..26d2f09 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseImageUploadResult.java @@ -0,0 +1,21 @@ +package com.jambotronGroup.jambotron.fileUpload; + +import com.jambotronGroup.jambotron.model.FileInfo; + +public class ResponseImageUploadResult extends ResponseMessage { + private FileInfo fileInfo; + + + public ResponseImageUploadResult(String message, FileInfo fileInfo) { + super(message); + this.fileInfo = fileInfo; + } + + public FileInfo getFileInfo() { + return this.fileInfo; + } + + public void setFileInfo(FileInfo fileInfo) { + this.fileInfo = fileInfo; + } +} diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseMessage.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseMessage.java index 978fb01..d10fc55 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseMessage.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/ResponseMessage.java @@ -3,6 +3,8 @@ package com.jambotronGroup.jambotron.fileUpload; public class ResponseMessage { private String message; + + public ResponseMessage(String message) { this.message = message; } diff --git a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java index 19ccd31..08bda7f 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java @@ -104,6 +104,9 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/tutorials-images/**").permitAll() + .requestMatchers("/api/file/files").permitAll() + .requestMatchers("/api/file/upload").permitAll() + .requestMatchers("/files/**").permitAll() .requestMatchers("/api/auth/**").permitAll() diff --git a/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java b/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java new file mode 100644 index 0000000..49e9a66 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java @@ -0,0 +1,25 @@ +package com.jambotronGroup.jambotron.utils; + +import com.jambotronGroup.jambotron.controllers.FilesController; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; + +@Component +public class FilesRoutingHelper { + /** + * Generates a URL for accessing a user's image. + * format: /api/user/user-images/{filename:.+} + * + * @param filename The name of the file. + * @return The URL to access the file. + */ + public static String getUserImageUrl(String filename) { + return MvcUriComponentsBuilder + .fromMethodName(FilesController.class, "getUserImage", filename).build().toString(); + } + + public static String getPublicImageUrl(String filename) { + return MvcUriComponentsBuilder + .fromMethodName(FilesController.class, "getFile", filename).build().toString(); + } +}