diff --git a/MyGallery-front/src/app/Constants/index.ts b/MyGallery-front/src/app/Constants/index.ts index b36ad19..1c5d85b 100644 --- a/MyGallery-front/src/app/Constants/index.ts +++ b/MyGallery-front/src/app/Constants/index.ts @@ -1,17 +1,16 @@ - -export const FILE_TYPES = { - 'txt':{ - color:'#0ec8a2', - icon:'fa-light fa-file-lines' - }, - 'video':{ - color:'#0ec8a2', - icon:'fa-light fa-file-lines' - }, - 'pdf':{ - color:'#0ec8a2', - icon:'fa-light fa-file-pdf' - }, - - - } \ No newline at end of file +export const FILE_TYPES = { + txt: { + color: '#0ec8a2', + icon: 'fa-light fa-file-lines', + }, + video: { + color: '#0ec8a2', + icon: 'fa-light fa-file-lines', + }, + pdf: { + color: '#0ec8a2', + icon: 'fa-light fa-file-pdf', + }, +}; +//192.168.1.22 +export const BASE_URL = 'http://localhost:8080/api/v1'; diff --git a/MyGallery-front/src/app/_helpers/http.interceptor.ts b/MyGallery-front/src/app/_helpers/http.interceptor.ts new file mode 100644 index 0000000..7aceb32 --- /dev/null +++ b/MyGallery-front/src/app/_helpers/http.interceptor.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpInterceptor, + HttpHandler, + HttpRequest, + HTTP_INTERCEPTORS, + HttpErrorResponse, +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { StorageService } from '../services/storage.service'; +import { EventBusService } from '../_shared/event-bus.service'; +import { EventData } from '../_shared/event.class'; + +@Injectable() +export class HttpRequestInterceptor implements HttpInterceptor { + private isRefreshing = false; + + constructor( + private storageService: StorageService, + private eventBusService: EventBusService + ) {} + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + req = req.clone({ + withCredentials: true, + }); + + return next.handle(req).pipe( + catchError((error) => { + if ( + error instanceof HttpErrorResponse && + !req.url.includes('auth/signin') && + error.status === 401 + ) { + return this.handle401Error(req, next); + } + + return throwError(() => error); + }) + ); + } + + private handle401Error(request: HttpRequest, next: HttpHandler) { + if (!this.isRefreshing) { + this.isRefreshing = true; + + if (this.storageService.isLoggedIn()) { + this.eventBusService.emit(new EventData('logout', null)); + } + } + + return next.handle(request); + } +} + +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true }, +]; diff --git a/MyGallery-front/src/app/_shared/event-bus.service.spec.ts b/MyGallery-front/src/app/_shared/event-bus.service.spec.ts new file mode 100644 index 0000000..574fc89 --- /dev/null +++ b/MyGallery-front/src/app/_shared/event-bus.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { EventBusService } from './event-bus.service'; + +describe('EventBusService', () => { + let service: EventBusService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(EventBusService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/_shared/event-bus.service.ts b/MyGallery-front/src/app/_shared/event-bus.service.ts new file mode 100644 index 0000000..8e5ee4b --- /dev/null +++ b/MyGallery-front/src/app/_shared/event-bus.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { EventData } from './event.class'; + +@Injectable({ + providedIn: 'root', +}) +export class EventBusService { + private subject$ = new Subject(); + + emit(event: EventData) { + this.subject$.next(event); + } + + on(eventName: string, action: any): Subscription { + return this.subject$ + .pipe( + filter((e: EventData) => e.name === eventName), + map((e: EventData) => e['value']) + ) + .subscribe(action); + } +} diff --git a/MyGallery-front/src/app/_shared/event.class.ts b/MyGallery-front/src/app/_shared/event.class.ts new file mode 100644 index 0000000..05db07a --- /dev/null +++ b/MyGallery-front/src/app/_shared/event.class.ts @@ -0,0 +1,9 @@ +export class EventData { + name: string; + value: any; + + constructor(name: string, value: any) { + this.name = name; + this.value = value; + } +} diff --git a/MyGallery-front/src/app/app-routing.module.ts b/MyGallery-front/src/app/app-routing.module.ts index 9a4b16d..b000103 100644 --- a/MyGallery-front/src/app/app-routing.module.ts +++ b/MyGallery-front/src/app/app-routing.module.ts @@ -1,23 +1,29 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CreateFolderComponent } from './compenents/create-folder/create-folder.component'; +import { FileDetailsComponent } from './compenents/file-details/file-details.component'; import { FileListsComponent } from './compenents/file-lists/file-lists.component'; import { FolderDetailsComponent } from './compenents/folder-details/folder-details.component'; +import { LoginCompenentComponent } from './compenents/login-compenent/login-compenent.component'; +import { ProfileComponent } from './compenents/profile/profile.component'; +import { RegisterCompenentComponent } from './compenents/register-compenent/register-compenent.component'; import { UploadFileComponent } from './compenents/upload-file/upload-file.component'; const routes: Routes = [ + { path: '', redirectTo: 'login', pathMatch: 'full' }, + { path: 'login', component: LoginCompenentComponent }, + { path: 'register', component: RegisterCompenentComponent }, + { path: 'upload-file', component: UploadFileComponent }, + { path: 'files', component: FileListsComponent }, + { path: 'folders', component: CreateFolderComponent }, + { path: 'profile', component: ProfileComponent }, - - - {path: 'upload-file', component: UploadFileComponent }, - {path: 'files', component: FileListsComponent}, - {path: 'folders',component : CreateFolderComponent}, - // {path: 'folder-details/:id', component: FolderDetailsComponent}, - {path: 'folders/:id', component: CreateFolderComponent}, + { path: 'folder-details/:id', component: FolderDetailsComponent }, + { path: 'file-details/:id', component: FileDetailsComponent }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + exports: [RouterModule], }) -export class AppRoutingModule { } +export class AppRoutingModule {} diff --git a/MyGallery-front/src/app/app.component.css b/MyGallery-front/src/app/app.component.css index 78d26cb..83f1a13 100644 --- a/MyGallery-front/src/app/app.component.css +++ b/MyGallery-front/src/app/app.component.css @@ -38,10 +38,22 @@ mat-sidenav-container { align-items: center; justify-content: flex-start; font-size: 1rem; + + + } + + + + mat-icon { margin-right: 8px; + color: white; +} +span{ + + color: white; } diff --git a/MyGallery-front/src/app/app.component.html b/MyGallery-front/src/app/app.component.html index d98f9e3..41b777e 100644 --- a/MyGallery-front/src/app/app.component.html +++ b/MyGallery-front/src/app/app.component.html @@ -1,48 +1,110 @@ - - MyGalleryFront - - - - - - - - + + My Gallery + + + + + + +
- - My Gallery +
+ My Gallery +
- -

Abderrahim Omar

-

Software Engineer

- - - - - + + + + + +
-
\ No newline at end of file + +
+ diff --git a/MyGallery-front/src/app/app.component.ts b/MyGallery-front/src/app/app.component.ts index 68b5150..7eb7785 100644 --- a/MyGallery-front/src/app/app.component.ts +++ b/MyGallery-front/src/app/app.component.ts @@ -4,29 +4,71 @@ import { MatSidenav } from '@angular/material/sidenav'; import { delay, filter } from 'rxjs/operators'; import { NavigationEnd, Router } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { FolderDetailsComponent } from './compenents/folder-details/folder-details.component'; - - +import { StorageService } from './services/storage.service'; +import { AuthService } from './services/auth.service'; +import { EventBusService } from './_shared/event-bus.service'; +import { Subscription } from 'rxjs'; @UntilDestroy() @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + styleUrls: ['./app.component.css'], }) export class AppComponent { + private roles: string[] = []; + isLoggedIn = false; + showAdminBoard = false; + showModeratorBoard = false; + showUserBoard = false; + username?: string; + + eventBusSub?: Subscription; title = 'MyGallery'; + @ViewChild(MatSidenav) + sidenav!: MatSidenav; + + constructor( + private observer: BreakpointObserver, + private router: Router, + private storageService: StorageService, + private authService: AuthService, + private eventBusService: EventBusService + ) {} + ngOnInit(): void { + this.isLoggedIn = this.storageService.isLoggedIn(); - + if (this.isLoggedIn) { + const user = this.storageService.getUser(); + this.roles = user.roles; + this.showAdminBoard = this.roles.includes('ROLE_ADMIN'); + this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR'); + this.showUserBoard = this.roles.includes('ROLE_USER'); - - @ViewChild(MatSidenav) - sidenav!: MatSidenav; + this.username = user.username; + } - constructor(private observer: BreakpointObserver, private router: Router) {} + this.eventBusSub = this.eventBusService.on('logout', () => { + this.logout(); + }); + } + + logout(): void { + this.authService.logout().subscribe({ + next: (res) => { + console.log(res); + this.storageService.clean(); + + window.location.reload(); + }, + error: (err) => { + console.log(err); + }, + }); + } ngAfterViewInit() { this.observer @@ -36,7 +78,6 @@ export class AppComponent { if (res.matches) { this.sidenav.mode = 'over'; this.sidenav.close(); - } else { this.sidenav.mode = 'side'; this.sidenav.open(); @@ -55,8 +96,3 @@ export class AppComponent { }); } } - - - - - diff --git a/MyGallery-front/src/app/app.module.ts b/MyGallery-front/src/app/app.module.ts index 26edfc8..1422d59 100644 --- a/MyGallery-front/src/app/app.module.ts +++ b/MyGallery-front/src/app/app.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { RenamePipe } from './compenents/file-lists/rename.pipe'; +import { SafePipe } from './compenents/file-details/Safe.pipe'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -22,17 +22,29 @@ import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatDividerModule } from '@angular/material/divider'; import { CreateFolderComponent } from './compenents/create-folder/create-folder.component'; - +import { LoginCompenentComponent } from './compenents/login-compenent/login-compenent.component'; +import { RegisterCompenentComponent } from './compenents/register-compenent/register-compenent.component'; +import { SearchComponent } from './compenents/search/search.component'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { ProfileComponent } from './compenents/profile/profile.component'; +import { HttpRequestInterceptor } from './_helpers/http.interceptor'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; @NgModule({ declarations: [ AppComponent, UploadFileComponent, FileDetailsComponent, FileListsComponent, - RenamePipe, CreateFolderComponent, UpdateFolderComponent, FolderDetailsComponent, + LoginCompenentComponent, + RegisterCompenentComponent, + SafePipe, + SearchComponent, + ProfileComponent, ], imports: [ BrowserModule, @@ -49,12 +61,11 @@ import { CreateFolderComponent } from './compenents/create-folder/create-folder. MatButtonModule, MatIconModule, MatDividerModule, - - + MatDatepickerModule, + MatNativeDateModule, + MatFormFieldModule, ], providers: [], - bootstrap: [AppComponent] - - + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.html b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.html index 598e1dc..7ead222 100644 --- a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.html +++ b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.html @@ -21,109 +21,20 @@ -
-
+ - - - - -
- - - - - - - - -
-
- -
- -
-
-
- -
- - -
-
-
- - - -
-
-
- \ No newline at end of file diff --git a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.spec.ts b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.spec.ts index 7fb77fc..71519d6 100644 --- a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.spec.ts +++ b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.spec.ts @@ -8,9 +8,8 @@ describe('CreateFolderComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ CreateFolderComponent ] - }) - .compileComponents(); + declarations: [CreateFolderComponent], + }).compileComponents(); fixture = TestBed.createComponent(CreateFolderComponent); component = fixture.componentInstance; diff --git a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.ts b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.ts index ad14256..7086939 100644 --- a/MyGallery-front/src/app/compenents/create-folder/create-folder.component.ts +++ b/MyGallery-front/src/app/compenents/create-folder/create-folder.component.ts @@ -7,24 +7,19 @@ import { FileService } from 'src/app/services/file.service'; import { FileModule } from 'src/app/modules/file/file.module'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MatSidenav } from '@angular/material/sidenav'; -import { untilDestroyed } from '@ngneat/until-destroy'; -import { delay, filter } from 'rxjs'; - @Component({ selector: 'app-create-folder', templateUrl: './create-folder.component.html', - styleUrls: ['./create-folder.component.css'] + styleUrls: ['./create-folder.component.css'], }) export class CreateFolderComponent { - - folderId!:number; + folderId!: number; folders: FolderModule[] = []; folder: FolderModule = new FolderModule(); - allFiles: any = []; - file: FileModule= new FileModule; - id!:number; - + allFiles: any = []; + file: FileModule = new FileModule(); + id!: number; pageSize = 0; perPage = 6; @@ -36,158 +31,94 @@ export class CreateFolderComponent { idFile = ''; @ViewChild(MatSidenav) - details!: MatSidenav; - - - - constructor(private folderService: FolderService, - private router: Router, private fileService :FileService, private route: ActivatedRoute,private observer: BreakpointObserver) { } - - - + details!: MatSidenav; - + constructor( + private folderService: FolderService, + private router: Router, + private route: ActivatedRoute + ) {} ngOnInit(): void { this.getFolders(); + } + saveFolder() { + this.folderService.createFolder(this.folder).subscribe( + (data) => { + console.log(data); + }, + (error) => console.log(error) + ); + } + getFolders() { + this.folderService.getFolderList().subscribe((data) => { + this.folders = data; + this.getFolders; - this.folderId = this.route.snapshot.params['id']; - this.folder = new FolderModule(); - this.folderService.getFolderById(this.folderId).subscribe(data => { - this.folder = data; - - this.getFiles(); + console.log('data ', this.folders); }); } + folderDetails(folderId: number) { + this.router.navigate(['/folder-details', folderId]); + console.log('folderId:', folderId); + } - - - saveFolder(){ -this.folderService.createFolder(this.folder).subscribe(data => { - console.log(data); - ; - -}, - error => console.log(error)); -} - getFolders() { - this.folderService.getFolderList().subscribe(data => { - this.folders = data; - this.getFolders; - - console.log("data ", this.folders) - }); -} -returnFolders(){ - this.router.navigate(['/folders']); - -} - -folderDetails(folderId:number){ - this.router.navigate(['/folders', folderId]); - - - this.router.events.forEach((event) => { - console.log('event',event); - }); - -} - - - onSubmit(){ + onSubmit() { console.log(this.folder); - this.saveFolder() + this.saveFolder(); this.getFolders(); - } - - - types: any = { png: { - icon: 'fa fa-light fa-image text-info', - class: 'info' + class: 'info', }, pdf: { - icon: 'fa fa-file-pdf-o text-danger', - class: 'danger' + class: 'danger', }, csv: { - icon: 'fa fa-file-excel-o text-success', - class: 'success' + class: 'success', }, txt: { - icon: 'fa fa-file-text-o text-secondary', - class: 'gold' + class: 'gold', }, pptx: { - icon: 'fa fa-file-powerpoint-o text-warning', - class: 'warning' - } - , + class: 'warning', + }, mp4: { - icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } + class: 'dark', + }, - , rar: { - icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } - - - } - - - - - - - private getFiles(){ - this.folderService.getAllFiles(this.folderId).subscribe(data =>{ - let files= []; - let datalist:any = data; - for (let a of datalist) { - if (a.id) { - files.push(a) - - } - } - this.allFiles = files; - }); - } - - - + class: 'dark', + }, + }; - selectedFiles?: FileList; - currentFile?: File; - progress = 0; - message = ''; - + selectedFiles?: FileList; + currentFile?: File; + progress = 0; + message = ''; selectFile(event: any): void { this.selectedFiles = event.target.files; } - upload(folderId:number , folderName: string): void { + upload(folderId: number, folderName: string): void { this.progress = 0; - console.log("folderId", folderId) - console.log("folderName", folderName) + console.log('folderId', folderId); + console.log('folderName', folderName); if (this.selectedFiles) { const file: File | null = this.selectedFiles.item(0); @@ -197,10 +128,9 @@ folderDetails(folderId:number){ this.folderService.upload(this.currentFile).subscribe({ next: (event: any) => { if (event.type === HttpEventType.UploadProgress) { - this.progress = Math.round(100 * event.loaded / event.total); + this.progress = Math.round((100 * event.loaded) / event.total); } else if (event instanceof HttpResponse) { this.message = event.body.message; - } }, error: (err: any) => { @@ -214,49 +144,11 @@ folderDetails(folderId:number){ } this.currentFile = undefined; - } + }, }); } this.selectedFiles = undefined; } } - - - - - - - - - - - ngAfterViewInit() { - this.observer - .observe(['(max-width: 500px; min-height: 500px)']) - .pipe(delay(1), untilDestroyed(this)) - .subscribe((res) => { - if (res.matches) { - this.details.mode = 'over'; - this.details.close(); - - } else { - this.details.mode = 'side'; - this.details.open(); - } - }); - - this.router.events - .pipe( - untilDestroyed(this), - filter((e) => e instanceof NavigationEnd) - ) - .subscribe(() => { - if (this.details.mode === 'over') { - this.details.close(); - } - }); - } - - } diff --git a/MyGallery-front/src/app/compenents/file-details/Safe.pipe.ts b/MyGallery-front/src/app/compenents/file-details/Safe.pipe.ts new file mode 100644 index 0000000..d007495 --- /dev/null +++ b/MyGallery-front/src/app/compenents/file-details/Safe.pipe.ts @@ -0,0 +1,17 @@ +// Angular +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +/** + * Sanitize HTML + */ +@Pipe({ + name: 'safe', +}) +export class SafePipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) {} + + transform(url: string) { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/MyGallery-front/src/app/compenents/file-details/file-details.component.css b/MyGallery-front/src/app/compenents/file-details/file-details.component.css index e69de29..6a996e6 100644 --- a/MyGallery-front/src/app/compenents/file-details/file-details.component.css +++ b/MyGallery-front/src/app/compenents/file-details/file-details.component.css @@ -0,0 +1,97 @@ +.document{ + + height: 100%; + width: 100%; + border: 2px; + + padding: 0.5em; + +display: flex; +} + +.document .details{ + + width: 40%; + background-color: transparent; + border: 0px; + margin-right: 0.15rem; + +} + + +.document .frame-body{ + height: 90%; + width: 100%; + +} + + +.document .icon{ + + +color: steelblue; + +} + + +.wraped{ + + display: inline; + margin: 10px; + +} + +.display{ + display: flex; + + +} + + +.bottun { + flex: 1 1 auto; + + padding: 5px; + text-align: center; + text-transform: uppercase; + transition: 0.5s; + background-size: 100% auto; + color: white; + text-shadow: 0px 0px 10px rgba(0,0,0,0.2); + box-shadow: 0 0 20px #eee; + border-radius: 10px; + border-color: transparent; + + background-image: linear-gradient(to right, #004a9f, #22577E);} + + + .bottun:hover { + background-position: right center; + } + + + .viewer{ + + height: 100%; + width: 100%; + border: 2px; + border-color: gray; + box-shadow: 0px 0px 6px 6px rgba(64, 59, 59, 0.318); + padding: 0.5em; + border-radius: 13px; + } + + + .icon-container { + /* display: none !important; */ + visibility: visible; + + } + + + .icon-container:hover { + /* display: block !important; */ + visibility: visible; + color: white; + } + \ No newline at end of file diff --git a/MyGallery-front/src/app/compenents/file-details/file-details.component.html b/MyGallery-front/src/app/compenents/file-details/file-details.component.html index ce67a9a..619fff0 100644 --- a/MyGallery-front/src/app/compenents/file-details/file-details.component.html +++ b/MyGallery-front/src/app/compenents/file-details/file-details.component.html @@ -1 +1,49 @@ -

file-details works!

+
+
+

Details:

+

desktop_windows {{ file.name }}

+

desktop_windows {{ file.type }}

+

+ desktop_windows {{ file.extension }} +

+

desktop_windows {{ file.size }}

+ +
+

Tags:

+
+ + +
+
+
+ +
+

File viewer

+ + +
+
diff --git a/MyGallery-front/src/app/compenents/file-details/file-details.component.ts b/MyGallery-front/src/app/compenents/file-details/file-details.component.ts index ef4ace9..2acbe47 100644 --- a/MyGallery-front/src/app/compenents/file-details/file-details.component.ts +++ b/MyGallery-front/src/app/compenents/file-details/file-details.component.ts @@ -1,10 +1,52 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { FileModule } from 'src/app/modules/file/file.module'; +import { FileService } from 'src/app/services/file.service'; @Component({ selector: 'app-file-details', templateUrl: './file-details.component.html', - styleUrls: ['./file-details.component.css'] + styleUrls: ['./file-details.component.css'], }) -export class FileDetailsComponent { +export class FileDetailsComponent implements OnInit { + constructor( + private fileService: FileService, + private route: ActivatedRoute + ) {} + files: FileModule[] = []; + + id!: string; + file: FileModule = new FileModule(); + + ngOnInit(): void { + this.id = this.route.snapshot.params['id']; + this.file = new FileModule(); + this.fileService.getFileById(this.id).subscribe((data) => { + this.file = data; + console.log(data); + }); + this.getTags(); + } + + tags: any = []; + private getTags() { + this.fileService.getTags(this.id).subscribe((data) => { + let allTags = []; + let datalist: any = data; + for (let a of datalist) { + if (a.id) { + allTags.push(a); + } + } + this.tags = allTags; + }); + } + + deleteTag(fileId: string, tagId: number) { + this.fileService.deleteTag(fileId, tagId).subscribe((data) => { + console.log(data); + this.getTags(); + }); + } } diff --git a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.css b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.css index 13001d1..ccc0d38 100644 --- a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.css +++ b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.css @@ -558,3 +558,10 @@ body { .dropzone:hover .dropzone-image { opacity: 1; } + + +.wrap-div{ + + + display: flex; +} \ No newline at end of file diff --git a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.html b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.html index a9bd50b..f4bee85 100644 --- a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.html +++ b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.html @@ -12,50 +12,52 @@
-
- -
+
+
+ +
-
-
- {{ progress }}% +
+
+ {{ progress }}% +
-
- + -
- +
+ +
-
- -
+ + +
@@ -105,7 +116,7 @@
diff --git a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.spec.ts b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.spec.ts index fec6613..cbe18e2 100644 --- a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.spec.ts +++ b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.spec.ts @@ -8,9 +8,8 @@ describe('FileListsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ FileListsComponent ] - }) - .compileComponents(); + declarations: [FileListsComponent], + }).compileComponents(); fixture = TestBed.createComponent(FileListsComponent); component = fixture.componentInstance; diff --git a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.ts b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.ts index 0979a2d..69f66ab 100644 --- a/MyGallery-front/src/app/compenents/file-lists/file-lists.component.ts +++ b/MyGallery-front/src/app/compenents/file-lists/file-lists.component.ts @@ -1,127 +1,125 @@ import { HttpEventType, HttpResponse } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { Observable } from 'rxjs'; -import { FILE_TYPES } from 'src/app/Constants'; import { FileModule } from 'src/app/modules/file/file.module'; +import { PaginatedData } from 'src/app/modules/FilePage/PaginatedData'; import { FileService } from 'src/app/services/file.service'; - +import { SearchComponent } from '../search/search.component'; @Component({ selector: 'app-file-lists', + templateUrl: './file-lists.component.html', - styleUrls: ['./file-lists.component.css'] + styleUrls: ['./file-lists.component.css'], }) - - export class FileListsComponent implements OnInit { files: FileModule[] = []; // name=this.file.extension; - pageSize = 0; - perPage = 6; - p: number = 1; name = ''; size = ''; extension = ''; folderName = ''; id = ''; + tag = ''; + pageSize!: number; + pageNo!: number; + + totalElements!: number; + totalPages!: number; + last!: boolean; types: any = { png: { + icon: 'fa fa-light fa-image text-info', + class: 'info', + }, + jpg: { icon: 'fa fa-light fa-image text-info', - class: 'info' + class: 'info', }, pdf: { - icon: 'fa fa-file-pdf-o text-danger', - class: 'danger' + class: 'danger', }, csv: { - icon: 'fa fa-file-excel-o text-success', - class: 'success' + class: 'success', }, - txt: { + docx: { + icon: 'fa fa-file-text-o text-secondary', + class: 'gold', + }, + txt: { icon: 'fa fa-file-text-o text-secondary', - class: 'gold' + class: 'gold', }, pptx: { - icon: 'fa fa-file-powerpoint-o text-warning', - class: 'warning' - } - , + class: 'warning', + }, mp4: { - icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } + class: 'dark', + }, - , - rar: { + mp3: { + icon: 'fa fa-file-audio-o text-dark', + class: 'dark', + }, + rar: { icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } - - - } - + class: 'dark', + }, + }; + data!: PaginatedData; - file: FileModule = new FileModule(); + file?: FileModule; selectedFiles?: FileList; currentFile?: File; progress = 0; message = ''; - - - - constructor(private fileSrvice: FileService, - private router: Router) { } - + constructor(private fileSrvice: FileService, private router: Router) {} ngOnInit(): void { this.getFiles(); - } - - private getFiles() { - this.fileSrvice.getFiles().subscribe(data => { - - this.files = data; - - console.log("data ", this.files) - + this.fileSrvice.getFiles().subscribe((data) => { + this.files = data.content; + this.pageNo = data.pageNo; + this.pageSize = data.pageSize; + this.totalElements = data.totalElements; + this.totalPages = data.totalPages; + this.last = data.last; + + console.log('data ', this.files); }); } deleteFile(id: string) { - this.fileSrvice.deleteFile(id).subscribe(data => { - + this.fileSrvice.deleteFile(id).subscribe((data) => { console.log(data); this.getFiles(); - - }) + }); } - selectFile(event: any): void { this.selectedFiles = event.target.files; } upload(): void { this.progress = 0; - + if (this.selectedFiles) { const file: File | null = this.selectedFiles.item(0); @@ -130,14 +128,12 @@ export class FileListsComponent implements OnInit { this.fileSrvice.upload(this.currentFile).subscribe({ next: (event: any) => { if (event.type === HttpEventType.UploadProgress) { - this.progress = Math.round(100 * event.loaded / event.total); + this.progress = Math.round((100 * event.loaded) / event.total); this.getFiles(); - } else if (event instanceof HttpResponse) { this.message = event.body.message; - - } else { - this.progress =0 + } else { + this.progress = 0; } }, error: (err: any) => { @@ -149,24 +145,34 @@ export class FileListsComponent implements OnInit { } else { this.message = 'Could not upload the file!'; } - this.progress=0; + this.progress = 0; this.currentFile = undefined; - } + }, }); - } - - + this.selectedFiles = undefined; } - - - - } - - + fileDetails(id: string) { + this.router.navigate(['file-details', id]); + console.log(); + } + fetchNextPage(e: any) { + this.fileSrvice.getFilesByPageNumber(e - 1).subscribe((data) => { + //@ts-ignore + + this.files = data.content; + this.pageNo = data.pageNo; + this.pageSize = data.pageSize; + this.totalElements = data.totalElements; + this.totalPages = data.totalPages; + this.last = data.last; + //@ts-ignore + + console.log('data ', this.files); + }); + console.log('e', e); + } } - - diff --git a/MyGallery-front/src/app/compenents/file-lists/rename.pipe.ts b/MyGallery-front/src/app/compenents/file-lists/rename.pipe.ts deleted file mode 100644 index 658958f..0000000 --- a/MyGallery-front/src/app/compenents/file-lists/rename.pipe.ts +++ /dev/null @@ -1,69 +0,0 @@ - - - - - -import { PipeTransform, Pipe } from '@angular/core'; - - -@Pipe({ name: 'rename' }) -export class RenamePipe implements PipeTransform { - - - color = { - 'green':{ - color:'#2da9e9', - icon: 'fa fa-file-excel-o text-success' - }, - dark: { - color:'#0ec8a2', - - }, - blue:{ - color:'#0ec8a2', - icon:'fa-file-excel-o' - }, - - - } - transform(typeString: string): string { - - - if (typeString === 'csv') { - return 'fa fa-file-excel-o text-success'; - - - } - - - else if (typeString === 'png' || typeString === 'jpeg' ) { - return 'fa fa-light fa-image text-primary'; - } - - - - else if (typeString === 'mp4') { - return 'fa fa-file-video-o text-dark'; - } - - - - else if (typeString === 'pdf') { - return 'fa fa-file-pdf-o text-danger'; - } - - else if (typeString === 'pptx') { - return 'fa fa-file-powerpoint-o text-warning'; - - - - } else if (typeString === 'txt') { - return 'fa fa-file-text-o text-dark'; - - - } - else { - return typeString - } - } -} \ No newline at end of file diff --git a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.css b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.css index ba51370..17b9fde 100644 --- a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.css +++ b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.css @@ -154,18 +154,14 @@ section { transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; padding-right: 5rem; - margin-left: 64%; + margin-left: 2%; margin-bottom: 20px; } -.container2{ - height: 400px; - width: 100%; - margin-bottom: 110px; -} + diff --git a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.html b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.html index 2b06e32..a373bbb 100644 --- a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.html +++ b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.html @@ -1,141 +1,158 @@ - - - - - - - - - - - + + - -
-
- -
- -

Folder Details

-
-
-
-
-
- -
-
-
{{folder.folderName}}
- -
- - -
-
-
-
-
- -
-
- -
+ +
+
+ +
+ +
+ + +
+ + +
+
+
+ +
+
+
+ +
+ +
-
- -
- - -
- - - - - -
-
- {{ progress }}% +
+ +
+ +
+ +
+
+ {{ progress }}%
-
- - - - - - -
- - - -
-

Files

- - -
- -
- -
- -
- -
- -
- - -
- -
-
+
+
+
+ + + + +
- - - -
-
- - - - +
-
- -
- - - - - - - +
+ +
+
+ diff --git a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.ts b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.ts index dcd836e..c60aa3a 100644 --- a/MyGallery-front/src/app/compenents/folder-details/folder-details.component.ts +++ b/MyGallery-front/src/app/compenents/folder-details/folder-details.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { HttpEventType, HttpResponse } from '@angular/common/http'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { FileModule } from 'src/app/modules/file/file.module'; import { FolderModule } from 'src/app/modules/folder/folder.module'; import { FileService } from 'src/app/services/file.service'; @@ -9,17 +9,9 @@ import { FolderService } from 'src/app/services/folder.service'; @Component({ selector: 'app-folder-details', templateUrl: './folder-details.component.html', - styleUrls: ['./folder-details.component.css'] + styleUrls: ['./folder-details.component.css'], }) export class FolderDetailsComponent { - - - - - - - - pageSize = 0; perPage = 6; p: number = 1; @@ -31,110 +23,115 @@ export class FolderDetailsComponent { types: any = { png: { - icon: 'fa fa-light fa-image text-info', - class: 'info' + class: 'info', }, pdf: { - icon: 'fa fa-file-pdf-o text-danger', - class: 'danger' + class: 'danger', }, csv: { - icon: 'fa fa-file-excel-o text-success', - class: 'success' + class: 'success', }, txt: { - icon: 'fa fa-file-text-o text-secondary', - class: 'gold' + class: 'gold', }, pptx: { - icon: 'fa fa-file-powerpoint-o text-warning', - class: 'warning' - } - , + class: 'warning', + }, mp4: { - icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } + class: 'dark', + }, - , rar: { - icon: 'fa fa-file-video-o text-dark', - class: 'dark' - } + class: 'dark', + }, + }; + allFiles: any = []; - } + folderId!: number; + folder: FolderModule = new FolderModule(); + file: FileModule = new FileModule(); + id!: number; + constructor( + private folderService: FolderService, + private fileService: FileService, + private route: ActivatedRoute, + private router: Router + ) {} - allFiles: any = []; + ngOnInit(): void { + this.folderId = this.route.snapshot.params['id']; + this.folder = new FolderModule(); + this.folderService.getFolderById(this.folderId).subscribe((data) => { + this.folder = data; - folderId!: number; - folder: FolderModule = new FolderModule; - - file: FileModule= new FileModule; - - id!:number; - - - - constructor(private folderService: FolderService , private fileService :FileService, private route: ActivatedRoute) { } - - - - ngOnInit(): void { - this.folderId = this.route.snapshot.params['id']; - this.folder = new FolderModule(); - this.folderService.getFolderById(this.folderId).subscribe(data => { - this.folder = data; - - this.getFiles(); - }); - - - - } - - - private getFiles(){ - this.folderService.getAllFiles(this.folderId).subscribe(data =>{ - let files= []; - let datalist:any = data; - for (let a of datalist) { - if (a.id) { - files.push(a) - - } - } - this.allFiles = files; - }); - } - + this.getFiles(); + }); + } + onSubmit() { + console.log(this.folder); + this.fileFolder(); + this.getFiles(); + } + + fileFolder() { + this.folderService.fileFolder(this.folder, this.file).subscribe( + (data) => { + console.log(data); + }, + (error) => console.log(error) + ); + } + deleteFile(fileId: string, folderId: number) { + this.folderService.deleteFile(fileId, folderId).subscribe((data) => { + console.log(data); + this.getFiles(); + }); + } - selectedFiles?: FileList; - currentFile?: File; - progress = 0; - message = ''; - + fileDetails(id: string) { + this.router.navigate(['file-details', id]); + console.log(); + } + + private getFiles() { + this.folderService.getAllFiles(this.folderId).subscribe((data) => { + let files = []; + let datalist: any = data; + for (let a of datalist) { + if (a.id) { + files.push(a); + } + } + this.allFiles = files; + }); + } + + selectedFiles?: FileList; + currentFile?: File; + progress = 0; + message = ''; selectFile(event: any): void { this.selectedFiles = event.target.files; } - upload(folderId:number , folderName: string): void { + upload(folderId: number, folderName: string): void { this.progress = 0; - console.log("folderId", folderId) - console.log("folderName", folderName) + console.log('folderId', folderId); + console.log('folderName', folderName); if (this.selectedFiles) { const file: File | null = this.selectedFiles.item(0); @@ -144,10 +141,9 @@ export class FolderDetailsComponent { this.folderService.upload(this.currentFile).subscribe({ next: (event: any) => { if (event.type === HttpEventType.UploadProgress) { - this.progress = Math.round(100 * event.loaded / event.total); + this.progress = Math.round((100 * event.loaded) / event.total); } else if (event instanceof HttpResponse) { this.message = event.body.message; - } }, error: (err: any) => { @@ -161,19 +157,11 @@ export class FolderDetailsComponent { } this.currentFile = undefined; - } + }, }); } this.selectedFiles = undefined; } } - - } - - - - - - \ No newline at end of file diff --git a/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.css b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.css new file mode 100644 index 0000000..9577391 --- /dev/null +++ b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.css @@ -0,0 +1,44 @@ + + + + +.container{ + + height: 80%; + width: 60%; + margin: auto; + +} + + +.login{ + + position: relative; + margin-bottom: 10px; + + border-color: gray; + box-shadow: 0px 0px 6px 6px rgba(64, 59, 59, 0.318); + + border-radius: 13px; + +display: flexbox; +justify-content: end; + +height: 100%; +width: 80%; + + +} + +.svg{ + + height: 100%; +width: 80%; +margin-bottom: -48px; +} + + + + + + diff --git a/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.html b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.html new file mode 100644 index 0000000..95f64b0 --- /dev/null +++ b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.html @@ -0,0 +1,114 @@ +
+ +
diff --git a/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.spec.ts b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.spec.ts new file mode 100644 index 0000000..f230726 --- /dev/null +++ b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginCompenentComponent } from './login-compenent.component'; + +describe('LoginCompenentComponent', () => { + let component: LoginCompenentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginCompenentComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginCompenentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.ts b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.ts new file mode 100644 index 0000000..25f3c4f --- /dev/null +++ b/MyGallery-front/src/app/compenents/login-compenent/login-compenent.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; +import { StorageService } from 'src/app/services/storage.service'; + +@Component({ + selector: 'app-login-compenent', + templateUrl: './login-compenent.component.html', + styleUrls: ['./login-compenent.component.css'], +}) +export class LoginCompenentComponent implements OnInit { + form: any = { + username: null, + password: null, + }; + isLoggedIn = false; + isLoginFailed = false; + errorMessage = ''; + roles: string[] = []; + + constructor( + private authService: AuthService, + private storageService: StorageService, + private router: Router + ) {} + + ngOnInit(): void { + if (this.storageService.isLoggedIn()) { + this.isLoggedIn = true; + this.roles = this.storageService.getUser().roles; + } + } + + onSubmit(): void { + const { username, password } = this.form; + + this.authService.login(username, password).subscribe({ + next: (data) => { + this.storageService.saveUser(data); + + this.isLoginFailed = false; + this.isLoggedIn = true; + this.roles = this.storageService.getUser().roles; + this.reloadPage(); + }, + error: (err) => { + this.errorMessage = err.error.message; + this.isLoginFailed = true; + }, + }); + } + + reloadPage(): void { + window.location.reload(); + } +} diff --git a/MyGallery-front/src/app/compenents/profile/profile.component.css b/MyGallery-front/src/app/compenents/profile/profile.component.css new file mode 100644 index 0000000..e69de29 diff --git a/MyGallery-front/src/app/compenents/profile/profile.component.html b/MyGallery-front/src/app/compenents/profile/profile.component.html new file mode 100644 index 0000000..eb35de8 --- /dev/null +++ b/MyGallery-front/src/app/compenents/profile/profile.component.html @@ -0,0 +1,19 @@ +
+
+

+ {{ currentUser.username }} Profile +

+
+

+ Email: + {{ currentUser.email }} +

+ Roles: +
    +
  • + {{ role }} +
  • +
+
+ + Please login. diff --git a/MyGallery-front/src/app/compenents/profile/profile.component.spec.ts b/MyGallery-front/src/app/compenents/profile/profile.component.spec.ts new file mode 100644 index 0000000..246039d --- /dev/null +++ b/MyGallery-front/src/app/compenents/profile/profile.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProfileComponent } from './profile.component'; + +describe('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProfileComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/compenents/profile/profile.component.ts b/MyGallery-front/src/app/compenents/profile/profile.component.ts new file mode 100644 index 0000000..e0c06ac --- /dev/null +++ b/MyGallery-front/src/app/compenents/profile/profile.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { StorageService } from 'src/app/services/storage.service'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.css'], +}) +export class ProfileComponent implements OnInit { + currentUser: any; + + constructor(private storageService: StorageService) {} + + ngOnInit(): void { + this.currentUser = this.storageService.getUser(); + } +} diff --git a/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.css b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.css new file mode 100644 index 0000000..f5f8bb3 --- /dev/null +++ b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.css @@ -0,0 +1,33 @@ +label { + display: block; + margin-top: 10px; +} + +.card-container.card { + max-width: 400px !important; + padding: 40px 40px; +} + +.card { + background-color: #f7f7f7; + padding: 20px 25px 30px; + margin: 0 auto 25px; + margin-top: 50px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); +} + +.profile-img-card { + width: 96px; + height: 96px; + margin: 0 auto 10px; + display: block; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + border-radius: 50%; +} + diff --git a/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.html b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.html new file mode 100644 index 0000000..0d09dfc --- /dev/null +++ b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.html @@ -0,0 +1,122 @@ +
+
+
+ +
+
+
diff --git a/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.spec.ts b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.spec.ts new file mode 100644 index 0000000..ec36a3a --- /dev/null +++ b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegisterCompenentComponent } from './register-compenent.component'; + +describe('RegisterCompenentComponent', () => { + let component: RegisterCompenentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RegisterCompenentComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(RegisterCompenentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.ts b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.ts new file mode 100644 index 0000000..b1ba4b8 --- /dev/null +++ b/MyGallery-front/src/app/compenents/register-compenent/register-compenent.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; + +@Component({ + selector: 'app-register-compenent', + templateUrl: './register-compenent.component.html', + styleUrls: ['./register-compenent.component.css'], +}) +export class RegisterCompenentComponent implements OnInit { + form: any = { + username: null, + email: null, + password: null, + }; + isSuccessful = false; + isSignUpFailed = false; + errorMessage = ''; + + constructor(private authService: AuthService) {} + + ngOnInit(): void {} + + onSubmit(): void { + const { username, email, password } = this.form; + + this.authService.register(username, email, password).subscribe({ + next: (data) => { + console.log(data); + this.isSuccessful = true; + this.isSignUpFailed = false; + }, + error: (err) => { + this.errorMessage = err.error.message; + this.isSignUpFailed = true; + }, + }); + } +} diff --git a/MyGallery-front/src/app/compenents/search/search.component.css b/MyGallery-front/src/app/compenents/search/search.component.css new file mode 100644 index 0000000..e69de29 diff --git a/MyGallery-front/src/app/compenents/search/search.component.html b/MyGallery-front/src/app/compenents/search/search.component.html new file mode 100644 index 0000000..a266323 --- /dev/null +++ b/MyGallery-front/src/app/compenents/search/search.component.html @@ -0,0 +1,12 @@ + diff --git a/MyGallery-front/src/app/compenents/search/search.component.spec.ts b/MyGallery-front/src/app/compenents/search/search.component.spec.ts new file mode 100644 index 0000000..eb69af9 --- /dev/null +++ b/MyGallery-front/src/app/compenents/search/search.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchComponent } from './search.component'; + +describe('SearchComponent', () => { + let component: SearchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SearchComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/compenents/search/search.component.ts b/MyGallery-front/src/app/compenents/search/search.component.ts new file mode 100644 index 0000000..cd72941 --- /dev/null +++ b/MyGallery-front/src/app/compenents/search/search.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-search', + templateUrl: './search.component.html', + styleUrls: ['./search.component.css'], +}) +export class SearchComponent { + name = ''; + size = ''; + extension = ''; + folderName = ''; + id = ''; + tag = ''; +} diff --git a/MyGallery-front/src/app/compenents/upload-file/upload-file.component.html b/MyGallery-front/src/app/compenents/upload-file/upload-file.component.html index 159ca1e..ea35e48 100644 --- a/MyGallery-front/src/app/compenents/upload-file/upload-file.component.html +++ b/MyGallery-front/src/app/compenents/upload-file/upload-file.component.html @@ -1,4 +1,4 @@ -
+
Upload File
@@ -9,32 +9,37 @@
Upload File
-
- - - +
- -
-
+
{{ progress }}%
- - -
\ No newline at end of file + +
diff --git a/MyGallery-front/src/app/compenents/upload-file/upload-file.component.ts b/MyGallery-front/src/app/compenents/upload-file/upload-file.component.ts index 0e8ec0b..ae8663a 100644 --- a/MyGallery-front/src/app/compenents/upload-file/upload-file.component.ts +++ b/MyGallery-front/src/app/compenents/upload-file/upload-file.component.ts @@ -8,65 +8,55 @@ import { FileService } from 'src/app/services/file.service'; @Component({ selector: 'app-upload-file', templateUrl: './upload-file.component.html', - styleUrls: ['./upload-file.component.css'] + styleUrls: ['./upload-file.component.css'], }) -export class UploadFileComponent implements OnInit{ - +export class UploadFileComponent implements OnInit { selectedFiles?: FileList; currentFile?: File; progress = 0; message = ''; - + file: FileModule = new FileModule(); + constructor(private fileSrvice: FileService, private router: Router) {} + ngOnInit(): void {} + selectFile(event: any): void { + this.selectedFiles = event.target.files; + } - file: FileModule = new FileModule(); - constructor(private fileSrvice: FileService, - private router: Router) { } - ngOnInit(): void { - - } + upload(): void { + this.progress = 0; - selectFile(event: any): void { - this.selectedFiles = event.target.files; - } - - upload(): void { - this.progress = 0; - - if (this.selectedFiles) { - const file: File | null = this.selectedFiles.item(0); - - if (file) { - this.currentFile = file; - - this.fileSrvice.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; - - } - }, - 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.currentFile = undefined; + if (this.selectedFiles) { + const file: File | null = this.selectedFiles.item(0); + + if (file) { + this.currentFile = file; + + this.fileSrvice.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; + } + }, + 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.selectedFiles = undefined; + + this.currentFile = undefined; + }, + }); } - } - + this.selectedFiles = undefined; + } + } } diff --git a/MyGallery-front/src/app/modules/FilePage/PaginatedData.ts b/MyGallery-front/src/app/modules/FilePage/PaginatedData.ts new file mode 100644 index 0000000..bcff5ad --- /dev/null +++ b/MyGallery-front/src/app/modules/FilePage/PaginatedData.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [], + imports: [CommonModule], +}) +export class PaginatedData { + content!: T[]; + pageNo!: number; + pageSize!: number; + totalElements!: number; + totalPages!: number; + last!: boolean; +} diff --git a/MyGallery-front/src/app/modules/Tag/tag.ts b/MyGallery-front/src/app/modules/Tag/tag.ts new file mode 100644 index 0000000..53a7173 --- /dev/null +++ b/MyGallery-front/src/app/modules/Tag/tag.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FileModule } from '../file/file.module'; + +@NgModule({ + declarations: [], + imports: [CommonModule], +}) +export class Tag { + id!: number; + tagName!: string; +} diff --git a/MyGallery-front/src/app/modules/User/user.ts b/MyGallery-front/src/app/modules/User/user.ts new file mode 100644 index 0000000..0a46405 --- /dev/null +++ b/MyGallery-front/src/app/modules/User/user.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [], + imports: [CommonModule], +}) +export class User { + id!: number; + firstName!: string; + lastName!: string; + username!: string; + password!: string; + photo!: string; + email!: string; + phone!: string; + birthday!: Date; + country!: string; + city!: string; + address!: string; + active!: boolean; + + roles!: String[]; +} diff --git a/MyGallery-front/src/app/modules/file/file.module.ts b/MyGallery-front/src/app/modules/file/file.module.ts index 44efaad..5672f75 100644 --- a/MyGallery-front/src/app/modules/file/file.module.ts +++ b/MyGallery-front/src/app/modules/file/file.module.ts @@ -1,16 +1,12 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - - +import { Tag } from '../Tag/tag'; @NgModule({ declarations: [], - imports: [ - CommonModule - ] + imports: [CommonModule], }) export class FileModule { - id!: string; name!: string; type!: string; @@ -18,9 +14,6 @@ export class FileModule { size!: number; extension!: string; description!: string; - folder!: number[]; - - - - + folder!: number[]; + tags!: string; } diff --git a/MyGallery-front/src/app/modules/fileFolder/FileFolder.ts b/MyGallery-front/src/app/modules/fileFolder/FileFolder.ts new file mode 100644 index 0000000..d3e09c1 --- /dev/null +++ b/MyGallery-front/src/app/modules/fileFolder/FileFolder.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + + + +@NgModule({ + declarations: [], + imports: [ + CommonModule + ] +}) +export class FileFolder { + + folderId!: number; + fileId!: string; + + + + + +} diff --git a/MyGallery-front/src/app/services/auth.service.spec.ts b/MyGallery-front/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/MyGallery-front/src/app/services/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/services/auth.service.ts b/MyGallery-front/src/app/services/auth.service.ts new file mode 100644 index 0000000..e7d36f5 --- /dev/null +++ b/MyGallery-front/src/app/services/auth.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { BASE_URL } from '../Constants'; + +const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }), +}; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private baseURL = `${BASE_URL}/auth/`; + + constructor(private http: HttpClient) {} + + login(username: string, password: string): Observable { + return this.http.post( + this.baseURL + 'signin', + { + username, + password, + }, + httpOptions + ); + } + + register(username: string, email: string, password: string): Observable { + return this.http.post( + this.baseURL + 'signup', + { + username, + email, + password, + }, + httpOptions + ); + } + + logout(): Observable { + return this.http.post(this.baseURL + 'signout', {}, httpOptions); + } +} diff --git a/MyGallery-front/src/app/services/file.service.ts b/MyGallery-front/src/app/services/file.service.ts index c999e7f..4e2533d 100644 --- a/MyGallery-front/src/app/services/file.service.ts +++ b/MyGallery-front/src/app/services/file.service.ts @@ -1,18 +1,18 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { FileModule } from '../modules/file/file.module'; +import { map } from 'rxjs/operators'; + import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http'; +import { PaginatedData } from '../modules/FilePage/PaginatedData'; +import { FileModule } from '../modules/file/file.module'; +import { BASE_URL } from '../Constants'; + @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class FileService { - - - - private baseURL = "http://localhost:8080/v1/file"; - constructor(private httpClient: HttpClient) { } - - + private baseURL = `${BASE_URL}/file`; + constructor(private httpClient: HttpClient) {} upload(file: File): Observable> { const formData: FormData = new FormData(); @@ -21,46 +21,64 @@ export class FileService { const req = new HttpRequest('POST', `${this.baseURL}/upload`, formData, { reportProgress: true, - responseType: 'json' + responseType: 'json', }); return this.httpClient.request(req); } - - getFiles(): Observable { - return this.httpClient.get(`${this.baseURL}/files`); + getFiles(): Observable> { + return this.httpClient.get>( + `${this.baseURL}/files`, + { + responseType: 'text' as 'json', + headers: { + Authorization: `Bearer ${ + JSON.parse(window.localStorage.getItem('auth-user') ?? '{}')?.token + }`, + }, + } + ); } + getAllFiles(pageNo: number): Observable> { + return this.httpClient.get>( + `${this.baseURL}/files?pageNo` + pageNo + ); + } + getFilesByPageNumber(pageNo: number): Observable> { + return this.httpClient.get>( + `${this.baseURL}/files`, + { params: { pageNo } } + ); + } -filec!:FileModule; - + file!: FileModule; -getFileById(id: string): Observable{ - return this.httpClient.get(`${this.baseURL}/${id}`); -} + getFileById(id: string): Observable { + return this.httpClient.get(`${this.baseURL}/files/${id}`); + } -updateFile(id:string, file: FileModule): Observable{ - return this.httpClient.put(`${this.baseURL}/${id}`,file); -} + updateFile(id: string, file: FileModule): Observable { + return this.httpClient.put(`${this.baseURL}/${id}`, file); + } -deleteFile(id: string):Observable{ - - + deleteFile(id: string): Observable { return this.httpClient.delete(`${this.baseURL}/${id}`); + } + deleteTag(fileId: String, tagId: number): Observable { + return this.httpClient.delete( + `${this.baseURL}/deleteTag/${fileId}/${tagId}` + ); + } -} - - - - - - - - -/*getAllFilesOfFolder(id: number): Observable{ + /*getAllFilesOfFolder(id: number): Observable{ return this.httpClient.get(`${this.baseURL}/${id}`+'/files'); }*/ + + getTags(id: string): Observable { + return this.httpClient.get(`${this.baseURL}/${id}` + '/tags'); + } } diff --git a/MyGallery-front/src/app/services/folder.service.ts b/MyGallery-front/src/app/services/folder.service.ts index 7c8930b..9f0013d 100644 --- a/MyGallery-front/src/app/services/folder.service.ts +++ b/MyGallery-front/src/app/services/folder.service.ts @@ -3,43 +3,58 @@ import { Observable } from 'rxjs'; import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http'; import { FolderModule } from '../modules/folder/folder.module'; +import { FileFolder } from '../modules/fileFolder/FileFolder'; +import { BASE_URL } from '../Constants'; +import { FileModule } from '../modules/file/file.module'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class FolderService { + private baseURL = `${BASE_URL}/folder`; + constructor(private httpClient: HttpClient) {} + getFolderList(): Observable { + return this.httpClient.get(`${this.baseURL}`, { + responseType: 'text' as 'json', + headers: { + Authorization: `Bearer ${ + JSON.parse(window.localStorage.getItem('auth-user') ?? '{}')?.token + }`, + }, + }); + } - private baseURL = "http://localhost:8080/v1/folder"; - constructor(private httpClient: HttpClient) { } - - - - getFolderList():Observable{ - - return this.httpClient.get(`${this.baseURL}`); + createFolder(folder: FolderModule): Observable { + return this.httpClient.post(`${this.baseURL}`, folder); } - - createFolder(projct:FolderModule): Observable{ - return this.httpClient.post(`${this.baseURL}`, projct); + + fileFolder(folder: FolderModule, file: FileModule): Observable { + return this.httpClient.post(`${this.baseURL}/fileToFolder`, { + folder, + file, + }); } - - getFolderById(id: number): Observable{ + + getFolderById(id: number): Observable { return this.httpClient.get(`${this.baseURL}/${id}`); } - - updateFolder(id:number, project: FolderModule): Observable{ - return this.httpClient.put(`${this.baseURL}/${id}`,project); + + updateFolder(id: number, folder: FolderModule): Observable { + return this.httpClient.put(`${this.baseURL}/${id}`, folder); } - - deleteFolder(id: number):Observable{ + + deleteFolder(id: number): Observable { return this.httpClient.delete(`${this.baseURL}/${id}`); - } - - - + + deleteFile(fileId: String, folderId: number): Observable { + return this.httpClient.delete( + `${this.baseURL}/deleteFile/${fileId}/${folderId}` + ); + } + upload(file: File): Observable> { const formData: FormData = new FormData(); @@ -47,17 +62,15 @@ export class FolderService { const req = new HttpRequest('POST', `${this.baseURL}/upload`, formData, { reportProgress: true, - responseType: 'json' + responseType: 'json', }); return this.httpClient.request(req); } - - - getAllFiles(id: number): Observable{ - return this.httpClient.get(`${this.baseURL}/${id}`+'/files'); - } - - + + getAllFiles(id: number): Observable { + return this.httpClient.get( + `${this.baseURL}/${id}` + '/files' + ); } - \ No newline at end of file +} diff --git a/MyGallery-front/src/app/services/storage.service.spec.ts b/MyGallery-front/src/app/services/storage.service.spec.ts new file mode 100644 index 0000000..e7fe5b5 --- /dev/null +++ b/MyGallery-front/src/app/services/storage.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { StorageService } from './storage.service'; + +describe('StorageService', () => { + let service: StorageService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(StorageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/services/storage.service.ts b/MyGallery-front/src/app/services/storage.service.ts new file mode 100644 index 0000000..51998f0 --- /dev/null +++ b/MyGallery-front/src/app/services/storage.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; + +const USER_KEY = 'auth-user'; + +@Injectable({ + providedIn: 'root', +}) +export class StorageService { + constructor() {} + + clean(): void { + window.localStorage.clear(); + } + + public saveUser(user: any): void { + window.localStorage.removeItem(USER_KEY); + window.localStorage.setItem(USER_KEY, JSON.stringify(user)); + } + + public getUser(): any { + const user = window.localStorage.getItem(USER_KEY); + if (user) { + return JSON.parse(user); + } + + return {}; + } + + public isLoggedIn(): boolean { + const user = window.localStorage.getItem(USER_KEY); + if (user) { + return true; + } + + return false; + } +} diff --git a/MyGallery-front/src/app/services/tag.service.spec.ts b/MyGallery-front/src/app/services/tag.service.spec.ts new file mode 100644 index 0000000..78f5572 --- /dev/null +++ b/MyGallery-front/src/app/services/tag.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TagService } from './tag.service'; + +describe('FolderService', () => { + let service: TagService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TagService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MyGallery-front/src/app/services/tag.service.ts b/MyGallery-front/src/app/services/tag.service.ts new file mode 100644 index 0000000..1745559 --- /dev/null +++ b/MyGallery-front/src/app/services/tag.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http'; +import { FolderModule } from '../modules/folder/folder.module'; +import { FileFolder } from '../modules/fileFolder/FileFolder'; +import { BASE_URL } from '../Constants'; +import { FileModule } from '../modules/file/file.module'; +import { Tag } from '../modules/Tag/tag'; + +@Injectable({ + providedIn: 'root', +}) +export class TagService { + private baseURL = `${BASE_URL}/tag`; + + constructor(private httpClient: HttpClient) {} + + getTagList(): Observable { + return this.httpClient.get(`${this.baseURL}`); + } + + createTag(tag: Tag): Observable { + return this.httpClient.post(`${this.baseURL}`, tag); + } + + getTagById(id: number): Observable { + return this.httpClient.get(`${this.baseURL}/${id}`); + } + + updateTag(id: number, tag: Tag): Observable { + return this.httpClient.put(`${this.baseURL}/${id}`, tag); + } + + deleteTag(id: number): Observable { + return this.httpClient.delete(`${this.baseURL}/${id}`); + } + + getAllFiles(id: number): Observable { + return this.httpClient.get(`${this.baseURL}/${id}/files`); + } +} diff --git a/MyGallery-front/src/assets/Images/app-bg.component.svg b/MyGallery-front/src/assets/Images/app-bg.component.svg new file mode 100644 index 0000000..1bd2369 --- /dev/null +++ b/MyGallery-front/src/assets/Images/app-bg.component.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MyGallery-front/src/assets/Images/bg.jpg b/MyGallery-front/src/assets/Images/bg.jpg new file mode 100644 index 0000000..8a9b5d9 Binary files /dev/null and b/MyGallery-front/src/assets/Images/bg.jpg differ diff --git a/MyGallery-front/src/assets/Images/bg.svg b/MyGallery-front/src/assets/Images/bg.svg new file mode 100644 index 0000000..d5eae9f --- /dev/null +++ b/MyGallery-front/src/assets/Images/bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MyGallery-front/src/assets/Images/profile.png b/MyGallery-front/src/assets/Images/profile.png new file mode 100644 index 0000000..bcbec6a Binary files /dev/null and b/MyGallery-front/src/assets/Images/profile.png differ diff --git a/MyGallery-front/src/index.html b/MyGallery-front/src/index.html index 5f00356..ba04469 100644 --- a/MyGallery-front/src/index.html +++ b/MyGallery-front/src/index.html @@ -1 +1,17 @@ - \ No newline at end of file + + + + + MyGalleryFront + + + + + + + + + diff --git a/galloryBack/pom.xml b/galloryBack/pom.xml index 75fe18a..9194437 100644 --- a/galloryBack/pom.xml +++ b/galloryBack/pom.xml @@ -5,57 +5,70 @@ org.springframework.boot spring-boot-starter-parent - 3.0.0 + 2.7.3 com.mygallery mygallery 0.0.1-SNAPSHOT mygallery - photo gallery project + gallery project 17 - - org.springframework.boot spring-boot-starter-data-jpa - - + - commons-io - commons-io - 2.11.0 + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + org.springframework.boot spring-boot-starter-web - org.springframework.boot - spring-boot-devtools + mysql + mysql-connector-java runtime - true + - com.mysql - mysql-connector-j - runtime + io.jsonwebtoken + jjwt + 0.9.1 + + + commons-io + commons-io + 2.11.0 org.projectlombok lombok true + org.springframework.boot spring-boot-starter-test test + + org.springframework.security + spring-security-test + test + @@ -63,16 +76,8 @@ org.springframework.boot spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - \ No newline at end of file + diff --git a/galloryBack/src/main/java/com/mygallery/MygalleryApplication.java b/galloryBack/src/main/java/com/mygallery/MygalleryApplication.java index b668fb8..5c03320 100644 --- a/galloryBack/src/main/java/com/mygallery/MygalleryApplication.java +++ b/galloryBack/src/main/java/com/mygallery/MygalleryApplication.java @@ -3,13 +3,18 @@ import com.mygallery.response.ResponseMessage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @SpringBootApplication + public class MygalleryApplication extends ResponseEntityExceptionHandler { public static void main(String[] args) { @@ -19,4 +24,8 @@ public static void main(String[] args) { public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException exc) { return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("File too large!")); } + + + + } diff --git a/galloryBack/src/main/java/com/mygallery/controllers/AuthController.java b/galloryBack/src/main/java/com/mygallery/controllers/AuthController.java new file mode 100644 index 0000000..f404913 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/controllers/AuthController.java @@ -0,0 +1,122 @@ +package com.mygallery.controllers; + +import com.mygallery.dtos.ERole; +import com.mygallery.dtos.SignupDto; +import com.mygallery.enities.LoginDto; +import com.mygallery.enities.Role; +import com.mygallery.enities.User; +import com.mygallery.repositories.RoleRepository; +import com.mygallery.repositories.UserRepository; +import com.mygallery.response.JwtResponse; +import com.mygallery.response.ResponseMessage; +import com.mygallery.security.jwt.JwtUtils; +import com.mygallery.security.services.UserDetailsImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@CrossOrigin(origins = "http://localhost:8081", maxAge = 3600) +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + @Autowired + AuthenticationManager authenticationManager; + + @Autowired + UserRepository userRepository; + + @Autowired + RoleRepository roleRepository; + + @Autowired + PasswordEncoder encoder; + + @Autowired + JwtUtils jwtUtils; + + @PostMapping("/signin") + public ResponseEntity authenticateUser(@Valid @RequestBody LoginDto loginRequest) { + + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = jwtUtils.generateJwtToken(authentication); + + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + List roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority()).collect(Collectors.toList()); + + return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); + } + + @PostMapping("/signup") + public ResponseEntity registerUser(@Valid @RequestBody SignupDto signUpRequest) { + if (userRepository.existsByUsername(signUpRequest.getUsername())) { + return ResponseEntity.badRequest().body(new ResponseMessage("Error: Username is already taken!")); + } + + if (userRepository.existsByEmail(signUpRequest.getEmail())) { + return ResponseEntity.badRequest().body(new ResponseMessage("Error: Email is already in use!")); + } + + // Create new user's account + User user = new User(signUpRequest.getUsername(), + signUpRequest.getEmail(), + encoder.encode(signUpRequest.getPassword())); + + Set strRoles = signUpRequest.getRole(); + Set roles = new HashSet<>(); + + if (strRoles == null) { + Role userRole = roleRepository.findByName(ERole.ROLE_USER) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(userRole); + } else { + strRoles.forEach(role -> { + switch (role) { + case "admin": + Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(adminRole); + + break; + case "mod": + Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(modRole); + + break; + + default: + Role userRole = roleRepository.findByName(ERole.ROLE_USER) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(userRole); + } + }); + } + + user.setRoles(roles); + userRepository.save(user); + + return ResponseEntity.ok(new ResponseMessage("User registered successfully!")); + } + + @PostMapping("/signout") + public ResponseEntity logoutUser() { + ResponseCookie cookie = jwtUtils.getCleanJwtCookie(); + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) + .body(new ResponseMessage("You've been signed out!")); + } +} diff --git a/galloryBack/src/main/java/com/mygallery/controllers/FileController.java b/galloryBack/src/main/java/com/mygallery/controllers/FileController.java index f7dba1f..95ff3d5 100644 --- a/galloryBack/src/main/java/com/mygallery/controllers/FileController.java +++ b/galloryBack/src/main/java/com/mygallery/controllers/FileController.java @@ -1,104 +1,103 @@ package com.mygallery.controllers; -import com.mygallery.dtos.FileDto; + import com.mygallery.enities.File; +import com.mygallery.enities.FileResponse; +import com.mygallery.enities.PaginationConsts; +import com.mygallery.enities.Tag; import com.mygallery.repositories.FileRepository; import com.mygallery.response.ResponseMessage; import com.mygallery.services.FileService; +import com.mygallery.services.TagService; import org.springframework.beans.factory.annotation.Autowired; 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 org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; + +@CrossOrigin(origins = "http://localhost:8081", maxAge = 3600) @RestController -@CrossOrigin(origins = "http://localhost:8081") -@RequestMapping("v1/file") + +@RequestMapping("/api/v1/file/") + public class FileController { private final Path rootPath = Paths.get("uploads"); - @Autowired - private FileService fileService; - + private final FileService fileService; @Autowired - private FileRepository fileRepository; + private final FileRepository fileRepository; + @Autowired + TagService tagService; - public FileController(FileService fileService,FileRepository fileRepository) { - this.fileRepository= fileRepository; + public FileController(FileService fileService, FileRepository fileRepository) { + this.fileRepository = fileRepository; this.fileService = fileService; } + + //Upload file @PostMapping("/upload") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public File uploadFile(@RequestParam("file") MultipartFile file) throws IOException { - return fileService.Upload(file); - - } - @GetMapping("/files") - public ResponseEntity> getListFiles() { - List fileInfos = fileService.getAllFiles().map(path -> { - String id = path.getId(); - String name = path.getName(); - String type = path.getType(); - long size = path.getSize(); - String extension=path.getExtension(); - - - String exetention = Optional.ofNullable(name) - .filter(f -> f.contains(".")) - .map(f -> f.substring(name.lastIndexOf(".") + 1)) - .get() - .toLowerCase(); - System.out.println(exetention); - - - String url = MvcUriComponentsBuilder.fromMethodName(FileController.class, "getFile", path.getId() + "." + exetention).build().toString(); - return new FileDto(id,name, type, url, size,extension); - - - }).collect(Collectors.toList()); - - return ResponseEntity.status(HttpStatus.OK).body(fileInfos); - } - - - @GetMapping("/files/{filename:.+}") + //Display file content + @GetMapping("/display/{filename:.+}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity getFile(@PathVariable String filename) { + Resource file = fileService.getFile(filename); + String nameoffile = file.getFilename(); + String[] id = nameoffile.split("\\."); + String[] types = fileRepository.getType(id[0]).split("/"); + MediaType contentType = new MediaType(types[0], types[1]); + return ResponseEntity.ok().contentType(contentType).header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + file.getFilename()).body(file); + } + //Download file + @GetMapping("/{filename:.+}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity downloadFile(@PathVariable String filename) { Resource file = fileService.getFile(filename); + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename()).body(file); + } - return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file); + //find file by id + @RequestMapping(value = "/files/{id}", method = RequestMethod.GET) + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Optional findById(@PathVariable("id") String id) { + return fileService.getfilebyId(id); } + //Delete file by id @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity deleteFile(@PathVariable String id) { String message = ""; try { fileService.delete(id); - - message = "Delete the file successfully: " + id; return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message)); - } catch (Exception e) { message = "Could not delete the file: " + id + ". Error: " + e.getMessage(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ResponseMessage(message)); @@ -106,4 +105,34 @@ public ResponseEntity deleteFile(@PathVariable String id) { } + //Get all files + @GetMapping("/files") + @PreAuthorize("hasRole('MODERATOR') or hasRole('ADMIN') or hasRole('USER')") + public FileResponse getAllFiles(@RequestParam(value = "pageNo", defaultValue = PaginationConsts.DEFAULT_PAGE_NUMBER, required = false) int pageNo, @RequestParam(value = "pageSize", defaultValue = PaginationConsts.DEFAULT_PAGE_SIZE, required = false) int pageSize, @RequestParam(value = "sortBy", defaultValue = PaginationConsts.DEFAULT_SORT_BY, required = false) String sortBy, @RequestParam(value = "sortDir", defaultValue = PaginationConsts.DEFAULT_SORT_DIRECTION, required = false) String sortDir, @RequestParam(value = "keyword", defaultValue = "", required = false) String keyword) { + + return fileService.getAllFiles(pageNo, pageSize, sortBy, sortDir, keyword); + } + + + // get tags of file + @GetMapping("/{id}/tags") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public List getTags(@PathVariable("id") String id) { + + return this.tagService.getFilesOfTag(id); + + } + + + //Remove tag from file by id + @DeleteMapping("/deleteTag/{fileId}/{tagId}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity> deleteTag(@PathVariable("fileId") String fileId, @PathVariable("tagId") Long tagId) { + fileService.deleteTag(fileId, tagId); + Map response = new HashMap<>(); + response.put("deleted", Boolean.TRUE); + return ResponseEntity.ok(response); + } + + } diff --git a/galloryBack/src/main/java/com/mygallery/controllers/FolderController.java b/galloryBack/src/main/java/com/mygallery/controllers/FolderController.java index 72989aa..11908ac 100644 --- a/galloryBack/src/main/java/com/mygallery/controllers/FolderController.java +++ b/galloryBack/src/main/java/com/mygallery/controllers/FolderController.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -19,73 +20,74 @@ import java.util.List; import java.util.Map; -@CrossOrigin(origins = "http://localhost:8081") +@CrossOrigin(origins = "http://localhost:8081" , maxAge = 3600) @RestController -@RequestMapping("v1/folder") +@RequestMapping("/api/v1/folder") public class FolderController { - @Autowired - private FileService fileService; @Autowired private final FolderService service; - - - @Autowired - private FileRepository fileRepository; - + private final FileService fileService; + @Autowired + private final FileRepository fileRepository; - public FolderController(FolderService service,FileService fileService,FileRepository fileRepository ) { + public FolderController(FolderService service, FileService fileService, FileRepository fileRepository) { this.service = service; - this.fileService= fileService; + this.fileService = fileService; this.fileRepository = fileRepository; } + //Create new folder @PostMapping - public Folder save(@RequestBody Folder Folder) { - service.save(Folder); - return Folder; + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Folder save(@RequestBody Folder folder) { + service.save(folder); + return folder; } + // Update Folder @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity update(@PathVariable Long id, @RequestBody Folder folder) { - Folder newFolder = service.findById(id); - newFolder.setFolderName(folder.getFolderName()); - - service.save(newFolder); return new ResponseEntity<>(newFolder, HttpStatus.OK); } + // Get All Folders -// @PostAuthorize("hasAuthority('ADMIN')") @GetMapping + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity> getAll() { List folders = service.getAll(); return new ResponseEntity<>(folders, HttpStatus.OK); - } - @GetMapping("/{id}/files") - public List getAllEmployeeOfProject(@PathVariable("id") Long folderId) { - return this.fileService.getAllFilesOfFolder(folderId); + //Get all files of folder + @GetMapping("/{id}/files") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public List getAllFilesOfFolder(@PathVariable("id") Long folderId) { + return this.fileService.getAllFilesOfFolder(folderId); } - // Get Project by ID + + // Get Folder by ID @GetMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity findById(@PathVariable("id") Long id) { Folder folder = service.findById(id); return new ResponseEntity<>(folder, HttpStatus.OK); - } - //Delet folder + + //Delete folder @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public ResponseEntity> delete(@PathVariable Long id) { service.delete(id); Map response = new HashMap<>(); @@ -94,26 +96,59 @@ public ResponseEntity> delete(@PathVariable Long id) { } - @PostMapping("/upload") - public File uploadFile(@RequestParam("file") MultipartFile file) throws IOException { + //remove file from folder + @DeleteMapping("/deleteFile/{fileId}/{folderId}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity> deleteFile(@PathVariable("fileId") String fileId, @PathVariable("folderId") Long folderId) { + service.deleteFile(fileId, folderId); + Map response = new HashMap<>(); + response.put("deleted", Boolean.TRUE); + return ResponseEntity.ok(response); + } - return fileService.Upload(file); + +/* + @PostMapping("/addFile") + public ResponseEntity> addFile(@RequestBody String fileId, Long folderId) { + service.addFile(fileId,folderId); + Map response = new HashMap<>(); + response.put("added", Boolean.TRUE); + return ResponseEntity.ok(response); + }*/ + //need to fixing + @PostMapping("/upload") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public File uploadFile(@RequestParam("file") MultipartFile file) throws IOException { + return fileService.Upload(file); } - // Assing Folder To file - @PostMapping("/fileFolder") - public Collection AddFileToFolder(@RequestBody FileFolder fileFolder) { - File file = (File) fileService.loadFileByName(fileFolder.getFileName()); + // Link File To Folder By Name + @PostMapping("/fileFoldername") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Collection AsignFileToFolder(@RequestBody FileFolder fileFolder) { + File file = fileService.loadFileByName(fileFolder.getFileName()); Folder folder = service.findByName(fileFolder.getFolderName()); - Collection folders = file.getFolder(); folders.add(folder); file.setFolder(folders); - fileRepository.save(file); + fileRepository.save(file); + return file.getFolder(); + } + + //Link file to folder By Id + @PostMapping("/fileToFolder") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Collection AddFileFolder(@RequestBody FileFolder fileFolder) { + File file = fileService.FindFileById(fileFolder.getFileId()); + Folder folder = service.findFolderById(fileFolder.getFolderId()); + Collection folders = file.getFolder(); + folders.add(folder); + file.setFolder(folders); + fileRepository.save(file); return file.getFolder(); } diff --git a/galloryBack/src/main/java/com/mygallery/controllers/TagController.java b/galloryBack/src/main/java/com/mygallery/controllers/TagController.java new file mode 100644 index 0000000..34a391f --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/controllers/TagController.java @@ -0,0 +1,113 @@ +package com.mygallery.controllers; + + +import com.mygallery.dtos.TagFile; +import com.mygallery.enities.File; +import com.mygallery.enities.Tag; +import com.mygallery.repositories.FileRepository; +import com.mygallery.services.FileService; +import com.mygallery.services.TagService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@CrossOrigin(origins = "http://localhost:8081" , maxAge = 3600) +@RestController +@RequestMapping("/api/v1/tag") +public class TagController { + @Autowired + private final TagService service; + @Autowired + private final FileService fileService; + @Autowired + private final FileRepository fileRepository; + + + public TagController(TagService service, FileService fileService, FileRepository fileRepository) { + this.service = service; + this.fileService = fileService; + this.fileRepository = fileRepository; + + } + + //Create new tag + @PostMapping + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Tag save(@RequestBody Tag tag) { + service.save(tag); + return tag; + } + + + // Update Tag + @PutMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity update(@PathVariable Long id, @RequestBody Tag tag) { + Tag newTag = service.findById(id); + newTag.setTagName(tag.getTagName()); + service.save(newTag); + return new ResponseEntity<>(newTag, HttpStatus.OK); + } + + + // Get All tags + + @GetMapping + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity> getAll() { + List tags = service.getAll(); + return new ResponseEntity<>(tags, HttpStatus.OK); + } + + + //get all files of tag + @GetMapping("/{id}/files") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public List getAllTagsOfFile(@PathVariable("id") Long id) { + return this.fileService.getAllFilesOfTag(id); + + } + + + // Get Tag by ID + @GetMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity findById(@PathVariable("id") Long id) { + Tag tag = service.findById(id); + return new ResponseEntity<>(tag, HttpStatus.OK); + + } + + + //Delete tag byId + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public ResponseEntity> delete(@PathVariable Long id) { + service.delete(id); + Map response = new HashMap<>(); + response.put("deleted", Boolean.TRUE); + return ResponseEntity.ok(response); + } + + + //Link tag with file By Id + @PostMapping("/tagToFile") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public Collection AddTagToFile(@RequestBody TagFile tagFile) { + File file = fileService.FindFileById(tagFile.getFileId()); + Tag tag = service.findById(tagFile.getTagId()); + Collection tags = file.getTags(); + tags.add(tag); + file.setTags(tags); + fileRepository.save(file); + return file.getTags(); + } + +} diff --git a/galloryBack/src/main/java/com/mygallery/controllers/TestController.java b/galloryBack/src/main/java/com/mygallery/controllers/TestController.java new file mode 100644 index 0000000..a39dc33 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/controllers/TestController.java @@ -0,0 +1,35 @@ +package com.mygallery.controllers; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/api/test") +public class TestController { + @GetMapping("/all") + public String allAccess() { + return "Public Content."; + } + + @GetMapping("/user") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public String userAccess() { + return "User Content."; + } + + @GetMapping("/mod") + @PreAuthorize("hasRole('ROLE_MODERATOR')") + public String moderatorAccess() { + return "Moderator Board."; + } + + @GetMapping("/admin") + @PreAuthorize("hasRole('ADMIN')") + public String adminAccess() { + return "Admin Board."; + } +} diff --git a/galloryBack/src/main/java/com/mygallery/dtos/ERole.java b/galloryBack/src/main/java/com/mygallery/dtos/ERole.java new file mode 100644 index 0000000..68995ae --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/dtos/ERole.java @@ -0,0 +1,9 @@ +package com.mygallery.dtos; + +public enum ERole { + + ROLE_ADMIN, + ROLE_USER, + ROLE_MODERATOR + +} diff --git a/galloryBack/src/main/java/com/mygallery/dtos/FileDto.java b/galloryBack/src/main/java/com/mygallery/dtos/FileDto.java index 482e0eb..547b3ab 100644 --- a/galloryBack/src/main/java/com/mygallery/dtos/FileDto.java +++ b/galloryBack/src/main/java/com/mygallery/dtos/FileDto.java @@ -10,7 +10,7 @@ @ToString public class FileDto { - private String id; + private String id; private String name; private String type; @@ -20,13 +20,28 @@ public class FileDto { private String description; - public FileDto(String id, String name, String type, String url, long size,String extension) { - this.id=id; - this.name=name; - this.type=type; + private FolderDto Folder; + + private TagDto Tag; + + public FileDto(String id, String name, String type, String url, long size) { + this.id = id; + this.name = name; + this.type = type; + this.url = url; + this.size = size; + + + } + + public FileDto(String id, String name, String type, String url, long size, String extension,TagDto Tag ) { + this.id = id; + this.name = name; + this.type = type; this.url = url; - this.size=size; - this.extension=extension; + this.size = size; + this.extension = extension; + this.Tag = Tag; } } diff --git a/galloryBack/src/main/java/com/mygallery/dtos/FileFolder.java b/galloryBack/src/main/java/com/mygallery/dtos/FileFolder.java index 3b2058f..d8e6941 100644 --- a/galloryBack/src/main/java/com/mygallery/dtos/FileFolder.java +++ b/galloryBack/src/main/java/com/mygallery/dtos/FileFolder.java @@ -5,7 +5,11 @@ @Data public class FileFolder { - private String folderName; - private String fileName; + private String folderName; + private String fileName; + + private long folderId; + private String fileId; + } diff --git a/galloryBack/src/main/java/com/mygallery/dtos/FolderDto.java b/galloryBack/src/main/java/com/mygallery/dtos/FolderDto.java index cac8f12..def256d 100644 --- a/galloryBack/src/main/java/com/mygallery/dtos/FolderDto.java +++ b/galloryBack/src/main/java/com/mygallery/dtos/FolderDto.java @@ -8,4 +8,6 @@ public class FolderDto { private Long id; private String folderName; + private FileDto File; + } diff --git a/galloryBack/src/main/java/com/mygallery/dtos/SignupDto.java b/galloryBack/src/main/java/com/mygallery/dtos/SignupDto.java new file mode 100644 index 0000000..2b45a85 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/dtos/SignupDto.java @@ -0,0 +1,37 @@ +package com.mygallery.dtos; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Column; +import java.util.Set; + + +@Getter +@Setter +public class SignupDto { + + + private String fullName; + + + @Column(unique = true, nullable = false) + private String username; + + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String password; + + + @Column(unique = true, nullable = false) + private String email; + + + private Set role; + + + + + +} diff --git a/galloryBack/src/main/java/com/mygallery/dtos/TagDto.java b/galloryBack/src/main/java/com/mygallery/dtos/TagDto.java new file mode 100644 index 0000000..57af516 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/dtos/TagDto.java @@ -0,0 +1,13 @@ +package com.mygallery.dtos; + +import lombok.Data; + +@Data +public class TagDto { + + private Long id; + private String tagName; + + private FileDto File; + +} diff --git a/galloryBack/src/main/java/com/mygallery/dtos/TagFile.java b/galloryBack/src/main/java/com/mygallery/dtos/TagFile.java new file mode 100644 index 0000000..84c5683 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/dtos/TagFile.java @@ -0,0 +1,15 @@ +package com.mygallery.dtos; + +import lombok.Data; + +@Data +public class TagFile { + + private String tageName; + private String fileName; + + private long tagId; + private String fileId; + + +} diff --git a/galloryBack/src/main/java/com/mygallery/enities/File.java b/galloryBack/src/main/java/com/mygallery/enities/File.java index 8d19e7d..f33c0fb 100644 --- a/galloryBack/src/main/java/com/mygallery/enities/File.java +++ b/galloryBack/src/main/java/com/mygallery/enities/File.java @@ -1,15 +1,15 @@ package com.mygallery.enities; -import java.util.Collection; - import com.fasterxml.jackson.annotation.JsonIgnore; -import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.GenericGenerator; +import javax.persistence.*; +import java.util.Collection; + @Entity @AllArgsConstructor @NoArgsConstructor @@ -20,49 +20,63 @@ public class File { - - - - - @Id @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") - + @Column(name = "id") private String id; + @Column(name = "name") private String name; + + @Column(name = "type") private String type; + + @Column(name = "size") private long size; - private String description; + @Column(name = "extension") private String extension; + @Column(name = "url") + private String url; + @ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, targetEntity = Folder.class, fetch = FetchType.LAZY) @JoinTable(name = "files_folder", - joinColumns = @JoinColumn(name ="fileId"), - inverseJoinColumns = @JoinColumn(name ="folderId") + joinColumns = @JoinColumn(name = "fileId"), + inverseJoinColumns = @JoinColumn(name = "folderId") ) + @JsonIgnore + private Collection folder; - //@JsonIgnore - private Collection folder; + @ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, + targetEntity = Tag.class, fetch = FetchType.LAZY) + @JoinTable( + name = "file_tag", + joinColumns = @JoinColumn(name = "file_id"), + inverseJoinColumns = @JoinColumn(name = "tag_id")) + + @JsonIgnore + private Collection tags; public File(String name, String type, long size) { this.name = name; this.type = type; - this.size=size; + this.size = size; } public File(String id) { - this.id=id; + this.id = id; } + + } diff --git a/galloryBack/src/main/java/com/mygallery/enities/FileResponse.java b/galloryBack/src/main/java/com/mygallery/enities/FileResponse.java new file mode 100644 index 0000000..34b7538 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/FileResponse.java @@ -0,0 +1,20 @@ +package com.mygallery.enities; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FileResponse { + private List content; + private int pageNo; + private int pageSize; + private long totalElements; + private int totalPages; + private boolean last; + +} diff --git a/galloryBack/src/main/java/com/mygallery/enities/Folder.java b/galloryBack/src/main/java/com/mygallery/enities/Folder.java index 0cd78c8..7f89dcb 100644 --- a/galloryBack/src/main/java/com/mygallery/enities/Folder.java +++ b/galloryBack/src/main/java/com/mygallery/enities/Folder.java @@ -1,14 +1,13 @@ package com.mygallery.enities; import com.fasterxml.jackson.annotation.JsonIdentityInfo; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import javax.persistence.*; import java.util.Collection; @Entity @@ -16,7 +15,7 @@ @NoArgsConstructor @Getter @Setter -@Table(name = "folder") +@Table(name = "folders") @JsonIdentityInfo(scope = Folder.class, generator = ObjectIdGenerators.PropertyGenerator.class, @@ -28,6 +27,7 @@ public class Folder { private Long folderId; + @Column(unique = true, nullable = false) private String folderName; @ManyToMany(mappedBy = "folder", targetEntity = File.class, fetch = FetchType.LAZY) diff --git a/galloryBack/src/main/java/com/mygallery/enities/LoginDto.java b/galloryBack/src/main/java/com/mygallery/enities/LoginDto.java new file mode 100644 index 0000000..3ac38dc --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/LoginDto.java @@ -0,0 +1,20 @@ +package com.mygallery.enities; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + + +@Data +@Getter +@Setter +public class LoginDto { + + @NotBlank + private String username; + + @NotBlank + private String password; +} diff --git a/galloryBack/src/main/java/com/mygallery/enities/PaginationConsts.java b/galloryBack/src/main/java/com/mygallery/enities/PaginationConsts.java new file mode 100644 index 0000000..8313816 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/PaginationConsts.java @@ -0,0 +1,10 @@ +package com.mygallery.enities; + +public class PaginationConsts { + + public static final String DEFAULT_PAGE_NUMBER = "0"; + public static final String DEFAULT_PAGE_SIZE = "10"; + public static final String DEFAULT_SORT_BY = "id"; + public static final String DEFAULT_SORT_DIRECTION = "asc"; + +} diff --git a/galloryBack/src/main/java/com/mygallery/enities/Role.java b/galloryBack/src/main/java/com/mygallery/enities/Role.java new file mode 100644 index 0000000..d2c6781 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/Role.java @@ -0,0 +1,31 @@ +package com.mygallery.enities; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.mygallery.dtos.ERole; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.persistence.*; + +@Entity +@Table(name = "roles") + +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString + +@JsonIdentityInfo(scope = Role.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") + +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Enumerated(EnumType.STRING) + private ERole name; +} \ No newline at end of file diff --git a/galloryBack/src/main/java/com/mygallery/enities/Tag.java b/galloryBack/src/main/java/com/mygallery/enities/Tag.java new file mode 100644 index 0000000..8238c2d --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/Tag.java @@ -0,0 +1,42 @@ +package com.mygallery.enities; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.*; +import java.util.Collection; + + +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Table(name = "tags") + +@JsonIdentityInfo(scope = Tag.class, generator = ObjectIdGenerators.PropertyGenerator.class, + + property = "id") +public class Tag { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + + private Long id; + + @Column(unique = true, nullable = false) + private String tagName; + + + @ManyToMany(mappedBy = "tags", targetEntity = File.class, fetch = FetchType.LAZY) + + // @JsonIgnore + + private Collection files; + + +} diff --git a/galloryBack/src/main/java/com/mygallery/enities/User.java b/galloryBack/src/main/java/com/mygallery/enities/User.java new file mode 100644 index 0000000..3ecd168 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/enities/User.java @@ -0,0 +1,70 @@ +package com.mygallery.enities; + + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.HashSet; +import java.util.Set; + + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@ToString +@Data +@JsonIdentityInfo(scope = User.class, generator = ObjectIdGenerators.PropertyGenerator.class, + + property = "id") + +@Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "username"), @UniqueConstraint(columnNames = "email")}) +public class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + + + private Long id; + + + private String fullName; + + + @NotBlank + @Size(max = 20) + private String username; + + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String password; + + + @NotBlank + @Size(max = 50) + @Email + private String email; + + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "users_id"), inverseJoinColumns = @JoinColumn(name = "roles_id")) + private Set roles = new HashSet<>(); + + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } + + /*public void save(Role role) { + this.roles.add(role); + }*/ +} diff --git a/galloryBack/src/main/java/com/mygallery/enums/Tag.java b/galloryBack/src/main/java/com/mygallery/enums/Tag.java deleted file mode 100644 index d07ea36..0000000 --- a/galloryBack/src/main/java/com/mygallery/enums/Tag.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.mygallery.enums; - -public enum Tag { -} diff --git a/galloryBack/src/main/java/com/mygallery/repositories/FileRepository.java b/galloryBack/src/main/java/com/mygallery/repositories/FileRepository.java index fe1c00b..fe0a80c 100644 --- a/galloryBack/src/main/java/com/mygallery/repositories/FileRepository.java +++ b/galloryBack/src/main/java/com/mygallery/repositories/FileRepository.java @@ -1,23 +1,43 @@ package com.mygallery.repositories; import com.mygallery.enities.File; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; -@Repository +import java.util.List; +@Repository public interface FileRepository extends JpaRepository { - - /* @Override - void deleteById(String aLong);*/ -// - /*@Query(value="SELECT name FROM File f WHERE f.id=?",nativeQuery = true) - String selectFileName(String id);*/ - @Query(value="SELECT name FROM Files f WHERE f.id=?",nativeQuery = true) + @Query(value = "SELECT name FROM Files f WHERE f.id=?", nativeQuery = true) String getName(String id); + @Query(value = "SELECT type FROM Files f WHERE f.id LIKE %?%", nativeQuery = true) + String getType(String id); + File findByName(String fileName); + + File findFileById(String Id); + + + //Remove tag from file + @Modifying + @Transactional + @Query(value = " DELETE FROM file_tag\n" + " WHERE file_id LIKE %?% AND tag_id=?;\n", nativeQuery = true) + void deleteTag(String fileId, Long tagId); + + + @Query(value = "SELECT * FROM files WHERE name LIKE %?1%" + " OR extension LIKE %?1%" + " OR type LIKE %?1%" + " OR CONCAT(size, '') LIKE %?1%", nativeQuery = true) + List search(String keyword); + + + @Query(value = "SELECT * FROM files WHERE name LIKE %?1%" + " OR extension LIKE %?1%" + " OR type LIKE %?1%" + " OR CONCAT(size, '') LIKE %?1% ", nativeQuery = true) + Page findAll(Pageable pageable, String keyword); + + } diff --git a/galloryBack/src/main/java/com/mygallery/repositories/FolderRepository.java b/galloryBack/src/main/java/com/mygallery/repositories/FolderRepository.java index 32a4f08..cdf7a84 100644 --- a/galloryBack/src/main/java/com/mygallery/repositories/FolderRepository.java +++ b/galloryBack/src/main/java/com/mygallery/repositories/FolderRepository.java @@ -2,13 +2,32 @@ import com.mygallery.enities.Folder; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; @Repository public interface FolderRepository extends JpaRepository { - Folder findByFolderId(Long folderId); + Folder findByFolderId(Long folderId); Folder findByFolderName(String folderName); + + + //Remove file from folder + @Modifying + @Transactional + @Query(value = " DELETE FROM files_folder\n" + "WHERE file_id LIKE %?% AND folder_id=?;\n", nativeQuery = true) + void deleteFile(String fileId, Long folderId); + +/* + + @Query(value=" SELECT id, folder_id FROM files, folders WHERE files.id LIKE %?% and folders.folder_id LIKE %?% ",nativeQuery = true) + void addFile(String fileId, Long folderId); + +*/ + + } diff --git a/galloryBack/src/main/java/com/mygallery/repositories/RoleRepository.java b/galloryBack/src/main/java/com/mygallery/repositories/RoleRepository.java new file mode 100644 index 0000000..dfa688a --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/repositories/RoleRepository.java @@ -0,0 +1,16 @@ +package com.mygallery.repositories; + + +import com.mygallery.dtos.ERole; +import com.mygallery.enities.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RoleRepository extends JpaRepository { + + Optional findByName(ERole name); + +} diff --git a/galloryBack/src/main/java/com/mygallery/repositories/TagRepository.java b/galloryBack/src/main/java/com/mygallery/repositories/TagRepository.java new file mode 100644 index 0000000..53a6acf --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/repositories/TagRepository.java @@ -0,0 +1,22 @@ +package com.mygallery.repositories; + +import com.mygallery.enities.Tag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository + +public interface TagRepository extends JpaRepository { + + Tag findByTagName(String tagName); + + + Optional findById(Long id); + + + @Query(value = "SELECT id, tag_name FROM tags WHERE id LIKE %?%", nativeQuery = true) + Tag findByTagId(Long id); +} diff --git a/galloryBack/src/main/java/com/mygallery/repositories/UserRepository.java b/galloryBack/src/main/java/com/mygallery/repositories/UserRepository.java new file mode 100644 index 0000000..34d70fc --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/repositories/UserRepository.java @@ -0,0 +1,19 @@ +package com.mygallery.repositories; + + +import com.mygallery.enities.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + + +@Repository +public interface UserRepository extends JpaRepository { + + + User findByUsername(String username); + + + Boolean existsByUsername(String username); + + Boolean existsByEmail(String email); +} diff --git a/galloryBack/src/main/java/com/mygallery/response/JwtResponse.java b/galloryBack/src/main/java/com/mygallery/response/JwtResponse.java new file mode 100644 index 0000000..5993b0c --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/response/JwtResponse.java @@ -0,0 +1,32 @@ +package com.mygallery.response; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class JwtResponse { + + private String token; + private String type = "Bearer"; + private Long id; + private String username; + private String email; + private List roles; + + public JwtResponse(String accessToken, Long id, String username, String email, List roles) { + this.token = accessToken; + this.id = id; + this.username = username; + this.email = email; + this.roles = roles; + } + + + + + + +} \ No newline at end of file diff --git a/galloryBack/src/main/java/com/mygallery/security/WebSecurityConfig.java b/galloryBack/src/main/java/com/mygallery/security/WebSecurityConfig.java new file mode 100644 index 0000000..1f83e53 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/WebSecurityConfig.java @@ -0,0 +1,117 @@ +package com.mygallery.security; + +import com.mygallery.security.jwt.AuthEntryPointJwt; +import com.mygallery.security.jwt.AuthTokenFilter; +import com.mygallery.security.services.UserDetailsServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; + +@Configuration +//@EnableWebSecurity +@EnableGlobalMethodSecurity( + // securedEnabled = true, + // jsr250Enabled = true, + prePostEnabled = true) +public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { + @Autowired + UserDetailsServiceImpl userDetailsService; + + @Autowired + private AuthEntryPointJwt unauthorizedHandler; + + @Bean + public AuthTokenFilter authenticationJwtTokenFilter() { + return new AuthTokenFilter(); + } + +// @Override +// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { +// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); +// } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; + } + +// @Bean +// @Override +// public AuthenticationManager authenticationManagerBean() throws Exception { +// return super.authenticationManagerBean(); +// } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { + return authConfig.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + +// @Override +// protected void configure(HttpSecurity http) throws Exception { +// http.cors().and().csrf().disable() +// .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() +// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() +// .authorizeRequests().antMatchers("/api/auth/**").permitAll() +// .antMatchers("/api/test/**").permitAll() +// .anyRequest().authenticated(); +// +// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); +// } + + + /* @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + // This Origin header you can see that in Network tab + configuration.setAllowedOrigins(Arrays.asList("http://localhost:8081", "")); + configuration.setAllowedMethods(Arrays.asList("GET","POST", "PUT")); + configuration.setAllowedHeaders(Arrays.asList("content-type")); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + }*/ + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.cors().and().csrf().disable() + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .authorizeRequests().antMatchers("/api/v1/auth/**").permitAll() + .antMatchers(HttpMethod.OPTIONS).permitAll() + .anyRequest().authenticated(); + + http.authenticationProvider(authenticationProvider()); + + http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/galloryBack/src/main/java/com/mygallery/security/jwt/AuthEntryPointJwt.java b/galloryBack/src/main/java/com/mygallery/security/jwt/AuthEntryPointJwt.java new file mode 100644 index 0000000..7280e7f --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/jwt/AuthEntryPointJwt.java @@ -0,0 +1,41 @@ +package com.mygallery.security.jwt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Component +public class AuthEntryPointJwt implements AuthenticationEntryPoint { + + private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws IOException, ServletException { + logger.error("Unauthorized error: {}", authException.getMessage()); + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + final Map body = new HashMap<>(); + body.put("status", HttpServletResponse.SC_UNAUTHORIZED); + body.put("error", "Unauthorized"); + body.put("message", authException.getMessage()); + body.put("path", request.getServletPath()); + + final ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), body); + } + +} diff --git a/galloryBack/src/main/java/com/mygallery/security/jwt/AuthTokenFilter.java b/galloryBack/src/main/java/com/mygallery/security/jwt/AuthTokenFilter.java new file mode 100644 index 0000000..d0e30d6 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/jwt/AuthTokenFilter.java @@ -0,0 +1,60 @@ +package com.mygallery.security.jwt; + +import com.mygallery.security.services.UserDetailsServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class AuthTokenFilter extends OncePerRequestFilter { + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + + private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + String jwt = parseJwt(request); + if (jwt != null && jwtUtils.validateJwtToken(jwt)) { + String username = jwtUtils.getUserNameFromJwtToken(jwt); + + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + logger.error("Cannot set user authentication: {}", e); + } + + filterChain.doFilter(request, response); + } + + private String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); + + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7, headerAuth.length()); + } + + return null; + } +} diff --git a/galloryBack/src/main/java/com/mygallery/security/jwt/JwtUtils.java b/galloryBack/src/main/java/com/mygallery/security/jwt/JwtUtils.java new file mode 100644 index 0000000..26aa7d7 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/jwt/JwtUtils.java @@ -0,0 +1,65 @@ +package com.mygallery.security.jwt; + +import com.mygallery.security.services.UserDetailsImpl; +import io.jsonwebtoken.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.web.util.WebUtils; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +@Component +public class JwtUtils { + private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); + + + @Value("${mygallery.app.jwtSecret}") + private String jwtSecret; + + @Value("${mygallery.app.jwtExpirationMs}") + private int jwtExpirationMs; + + @Value("${mygallery.app.jwtCookieName}") + private String jwtCookie; + + public String generateJwtToken(Authentication authentication) { + + UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); + + return Jwts.builder().setSubject((userPrincipal.getUsername())).setIssuedAt(new Date()).setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret).compact(); + } + + public String getUserNameFromJwtToken(String token) { + return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); + } + + public boolean validateJwtToken(String authToken) { + try { + Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); + return true; + } catch (SignatureException e) { + logger.error("Invalid JWT signature: {}", e.getMessage()); + } catch (MalformedJwtException e) { + logger.error("Invalid JWT token: {}", e.getMessage()); + } catch (ExpiredJwtException e) { + logger.error("JWT token is expired: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + logger.error("JWT token is unsupported: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + logger.error("JWT claims string is empty: {}", e.getMessage()); + } + + return false; + } + + public ResponseCookie getCleanJwtCookie() { + ResponseCookie cookie = ResponseCookie.from(jwtCookie, null).path("/api").build(); + return cookie; + } +} \ No newline at end of file diff --git a/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsImpl.java b/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsImpl.java new file mode 100644 index 0000000..8b31577 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsImpl.java @@ -0,0 +1,103 @@ +package com.mygallery.security.services; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mygallery.enities.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class UserDetailsImpl implements UserDetails { + private static final long serialVersionUID = 1L; + + private final Long id; + + private final String username; + + private final String email; + + @JsonIgnore + private final String password; + + private final Collection authorities; + + public UserDetailsImpl(Long id, String username, String email, String password, + Collection authorities) { + this.id = id; + this.username = username; + this.email = email; + this.password = password; + this.authorities = authorities; + } + + public static UserDetailsImpl build(User user) { + List authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName().name())) + .collect(Collectors.toList()); + + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPassword(), + authorities); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + UserDetailsImpl user = (UserDetailsImpl) o; + return Objects.equals(id, user.id); + } +} diff --git a/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsServiceImpl.java b/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsServiceImpl.java new file mode 100644 index 0000000..499e9b7 --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/security/services/UserDetailsServiceImpl.java @@ -0,0 +1,27 @@ +package com.mygallery.security.services; + +import com.mygallery.enities.User; +import com.mygallery.repositories.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + @Autowired + UserRepository userRepository; + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username); + + return UserDetailsImpl.build(user); + } + +} diff --git a/galloryBack/src/main/java/com/mygallery/services/FileService.java b/galloryBack/src/main/java/com/mygallery/services/FileService.java index 5454e10..7c5c5b2 100644 --- a/galloryBack/src/main/java/com/mygallery/services/FileService.java +++ b/galloryBack/src/main/java/com/mygallery/services/FileService.java @@ -1,17 +1,27 @@ package com.mygallery.services; +import com.mygallery.controllers.FileController; +import com.mygallery.dtos.FileDto; import com.mygallery.enities.File; +import com.mygallery.enities.FileResponse; import com.mygallery.enities.Folder; +import com.mygallery.enities.Tag; import com.mygallery.repositories.FileRepository; import com.mygallery.repositories.FolderRepository; +import com.mygallery.repositories.TagRepository; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import java.io.IOException; import java.net.MalformedURLException; @@ -22,11 +32,13 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; @Service public class FileService { + //Create path to upload file in local storage private final Path rootPath = Paths.get("uploads"); @Autowired @@ -34,7 +46,8 @@ public class FileService { @Autowired private FolderRepository folderRepository; - + @Autowired + private TagRepository tagRepository; public FileService(FileRepository fileRepository) { this.fileRepository = fileRepository; @@ -45,20 +58,15 @@ public FileService(FileRepository fileRepository) { public File Upload(MultipartFile file) throws IOException { + String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename())); File formFile = new File(fileName, file.getContentType(), file.getSize()); - LinkOption[] linkOptions = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - String extension = Optional.ofNullable(fileName) - .filter(f -> f.contains(".")) - .map(f -> f.substring(fileName.lastIndexOf(".") + 1)) - .get() - .toLowerCase(); + String extension = Optional.ofNullable(fileName).filter(f -> f.contains(".")).map(f -> f.substring(fileName.lastIndexOf(".") + 1)).get().toLowerCase(); -// System.out.println(exetention); try { if (Files.notExists(rootPath, linkOptions)) { @@ -104,7 +112,10 @@ public Resource getFile(String id) { //Load all files public Stream getAllFiles() { - return fileRepository.findAll().stream(); + Sort nameSort = Sort.by("name"); + Sort sizeSort = Sort.by("size"); + Sort groupBySort = sizeSort.and(nameSort); + return fileRepository.findAll(groupBySort).stream(); } public String Extension(String filename) { @@ -113,30 +124,16 @@ public String Extension(String filename) { } - //Delete file by id public boolean delete(String id) { try { - /* File file= new File(id); - String exetention= Optional.ofNullable(file.getName()) - .filter(f -> f.contains(".")) - .map(f -> f.substring(file.getName().lastIndexOf(".") + 1)) - .get() - .toLowerCase(); - System.out.println(exetention);*/ -// System.out.println(fileRepository.selectFileName(id)); -// fileRepository.deleteById(id); -// Path file = rootPath.resolve(fileRepository.selectFileName(id)); + String extension = FilenameUtils.getExtension(fileRepository.getName(id)); + Path filepath = rootPath.resolve(id + "." + extension); - String extension= FilenameUtils.getExtension(fileRepository.getName(id)); - Path filepath = rootPath.resolve(id + "." +extension); - - // Path filepath = rootPath.resolve(id + "." +fileRepository.getType(id).split("/", 2)[1]); - System.out.println(filepath); Files.deleteIfExists(filepath); fileRepository.deleteById(id); return Files.deleteIfExists(filepath); @@ -147,19 +144,133 @@ public boolean delete(String id) { } + } + + public File loadFileByName(String fileName) { + return fileRepository.findByName(fileName); } - public List getAllFilesOfFolder(Long folderId){ - Folder folder = this.folderRepository.findByFolderId(folderId); + public Optional getfilebyId(String id) { + return fileRepository.findById(id); + } + + public String getFileType(String type) { + + return fileRepository.getType(type); + } + + public File FindFileById(String fileId) { + return fileRepository.findFileById(fileId); + } + + + public File findById(String id) { + return fileRepository.findById(id).get(); + } + + + public File findFileById(String Id) { + return fileRepository.findFileById(Id); + } + + + public List findPaginated(int pageNo, int pageSize) { + Pageable paging = PageRequest.of(pageNo, pageSize); + Page pagedResult = fileRepository.findAll(paging); + + return pagedResult.toList(); + } + + // search by a keyword + public List listAll(String keyword) { + if (keyword != null) { + return fileRepository.search(keyword); + } + return fileRepository.findAll(); + } + + // convert Entity into DTO + private FileDto mapToDTO(File file) { + FileDto fileDto = new FileDto(); + fileDto.setId(file.getId()); + fileDto.setName(file.getName()); + fileDto.setExtension(file.getExtension()); + fileDto.setSize(file.getSize()); + fileDto.setType(file.getType()); - List files = (List )folder.getFiles(); + return fileDto; + } + + // convert DTO to entity + private File mapToEntity(FileDto fileDto) { + File file = new File(); + file.setName(fileDto.getName()); + + file.setExtension(fileDto.getExtension()); + file.setSize(fileDto.getSize()); + file.setType(fileDto.getType()); + return file; + } + + + //Display File content + + public FileResponse getAllFiles(int pageNo, int pageSize, String sortBy, String sortDir, String keyword) { + + Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); + + // create Pageable instance + Pageable pageable = PageRequest.of(pageNo, pageSize, sort); + + Page files = fileRepository.findAll(pageable, keyword); + + + // get content for page object + List listOfFiles = files.getContent(); + + + List content = listOfFiles.stream().map(file -> mapToDTO(file)).collect(Collectors.toList()); + content.forEach(fileDto -> { + String url = MvcUriComponentsBuilder.fromMethodName(FileController.class, "getFile", fileDto.getId() + "." + fileDto.getExtension()).build().toString(); + + fileDto.setUrl(url); + + + }); + FileResponse fileResponse = new FileResponse(); + fileResponse.setContent(content); + fileResponse.setPageNo(files.getNumber()); + fileResponse.setPageSize(files.getSize()); + fileResponse.setTotalElements(files.getTotalElements()); + fileResponse.setTotalPages(files.getTotalPages()); + fileResponse.setLast(files.isLast()); + + return fileResponse; + } + + + //Delete tage from file by id + public void deleteTag(String fileId, Long tagId) { + fileRepository.deleteTag(fileId, tagId); + } + + + //get all files of tag + public List getAllFilesOfTag(Long tagId) { + Tag tag = this.tagRepository.findByTagId(tagId); + List files = (List) tag.getFiles(); return files; } - public File loadFileByName(String fileName) { - return fileRepository.findByName(fileName); + + //get all files of folder + public List getAllFilesOfFolder(Long folderId) { + Folder folder = this.folderRepository.findByFolderId(folderId); + List files = (List) folder.getFiles(); + return files; + } diff --git a/galloryBack/src/main/java/com/mygallery/services/FolderService.java b/galloryBack/src/main/java/com/mygallery/services/FolderService.java index edbdafe..0f72150 100644 --- a/galloryBack/src/main/java/com/mygallery/services/FolderService.java +++ b/galloryBack/src/main/java/com/mygallery/services/FolderService.java @@ -20,7 +20,7 @@ public FolderService(FolderRepository folderRepository) { public void save(Folder folder) { - folderRepository.save(folder); + folderRepository.save(folder); } @@ -34,6 +34,17 @@ public void delete(Long id) { } + public void deleteFile(String fileId, Long folderId) { + + folderRepository.deleteFile(fileId, folderId); + } + + + /*public void addFile (String fileId, Long folderId){ + + folderRepository.addFile(fileId,folderId); + }*/ + public Folder findById(Long id) { return folderRepository.findById(id).get(); } @@ -46,4 +57,11 @@ public List getAll() { public Folder findByName(String folderName) { return folderRepository.findByFolderName(folderName); } + + + public Folder findFolderById(Long folderId) { + return folderRepository.findByFolderId(folderId); + } + + } diff --git a/galloryBack/src/main/java/com/mygallery/services/TagService.java b/galloryBack/src/main/java/com/mygallery/services/TagService.java new file mode 100644 index 0000000..fd46d1e --- /dev/null +++ b/galloryBack/src/main/java/com/mygallery/services/TagService.java @@ -0,0 +1,64 @@ +package com.mygallery.services; + + +import com.mygallery.enities.File; +import com.mygallery.enities.Tag; +import com.mygallery.repositories.FileRepository; +import com.mygallery.repositories.TagRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class TagService { + @Autowired + private final TagRepository tagRepository; + + @Autowired + private final FileRepository fileRepository; + + public TagService(TagRepository tagRepository, FileRepository fileRepository) { + this.tagRepository = tagRepository; + this.fileRepository = fileRepository; + } + + + public void save(Tag tag) { + + tagRepository.save(tag); + } + + + public void update(Tag tag) { + tagRepository.save(tag); + } + + + public void delete(Long id) { + tagRepository.deleteById(id); + } + + + public Tag findById(Long id) { + return tagRepository.findById(id).get(); + } + + + public List getAll() { + return tagRepository.findAll(); + } + + public Tag findByName(String tagName) { + return tagRepository.findByTagName(tagName); + } + + + public List getFilesOfTag(String id) { + + File file = this.fileRepository.findFileById(id); + + List tags = (List) file.getTags(); + return tags; + } +} diff --git a/galloryBack/src/main/resources/application.properties b/galloryBack/src/main/resources/application.properties index 4cdcaf3..495b4ff 100644 --- a/galloryBack/src/main/resources/application.properties +++ b/galloryBack/src/main/resources/application.properties @@ -1,39 +1,23 @@ - spring.jpa.show-sql=true - - spring.jpa.hibernate.ddl-auto=update -spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect - - +spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect # Enable spring data repos spring.data.jpa.repositories.enabled=true - # Replace with your connection string spring.datasource.url=jdbc:mysql://localhost:3306/DB_MYGALLERY - # Replace with your credentials +#spring.datasource.username=COURSSPRING +#spring.datasource.password=COURSSPRING +spring.datasource.username=omaritroad +spring.datasource.password=omaritroad -spring.datasource.username=COURSSPRING -spring.datasource.password=COURSSPRING -#spring.datasource.username=omaritroad -#spring.datasource.password=omaritroad - - -spring.datasource.driverClassName=com.mysql.jdbc.Driver - -springdoc.api-docs.path=/api-docs - -#to solve the problem of working springfox -spring.mvc.pathmatch.matching-strategy=ant-path-matcher - - -#customize the path of our API documentation, default was:http://localhost:8080/swagger-ui.html -springdoc.swagger-ui.path=/swagger-ui-custom.html -#to sort the API paths in order of their HTTP methods -springdoc.swagger-ui.operationsSorter=method +spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.servlet.multipart.max-file-size=20MB spring.servlet.multipart.max-request-size=20MB + +mygallery.app.jwtSecret=myGallerySecretKey +mygallery.app.jwtExpirationMs=86400000 +mygallery.app.jwtCookieName=cookies