Add moderator module with routing, components, and API integration

This commit is contained in:
liosha84
2025-07-17 13:08:23 +03:00
parent c67360273e
commit 70338eae0b
42 changed files with 866 additions and 110 deletions
@@ -39,6 +39,12 @@
User tools User tools
</button> </button>
} }
@if (showModeratorBoard) {
<button mat-menu-item routerLink="moderator/moderator-welcome">
<mat-icon>space_dashboard</mat-icon>
Moderator board
</button>
}
<button mat-menu-item routerLink="profile"> <button mat-menu-item routerLink="profile">
<mat-icon>person</mat-icon> <mat-icon>person</mat-icon>
@@ -232,8 +232,7 @@ export class MainComponent implements OnInit{
); );
// do something here with the data // do something here with the data
dialogloginSubscription.unsubscribe();
dialogSubmitSubscription.unsubscribe();
}); });
} }
} }
@@ -23,6 +23,11 @@ const MAIN_ROUTES: Routes =[
loadChildren: () => loadChildren: () =>
import('../user-module/user.module').then((m) => m.UserModule), import('../user-module/user.module').then((m) => m.UserModule),
}, },
{
path: 'moderator',
loadChildren: () =>
import('../moderator-module/moderator.module').then((m) => m.ModeratorModule),
},
{ {
path: 'home', path: 'home',
component: HomeComponent component: HomeComponent
+1 -35
View File
@@ -6,42 +6,8 @@ export class Tutorial {
published?: boolean; published?: boolean;
created?: Date; created?: Date;
modified?: Date; modified?: Date;
tobepublished?: boolean;
isEdit?: boolean; isEdit?: boolean;
} }
export const TutorialColumns = [
{
key: 'isSelected',
type: 'isSelected',
label: '',
},
{
key: 'title',
type: 'text',
label: 'Title',
required: true,
},
{
key: 'published',
type: 'boolean',
label: 'Is Published',
},
{
key: 'created',
type: 'datetime',
label: 'Created Date',
required: true,
},
{
key: 'modified',
type: 'datetime',
label: 'Modified Date',
required: true
},
{
key: 'isEdit',
type: 'isEdit',
label: '',
}
];
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ModeratorApiService } from './moderator-api.service';
describe('ModeratorApiService', () => {
let service: ModeratorApiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ModeratorApiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Tutorial} from '../models/tutorial.model';
@Injectable({
providedIn: 'root'
})
export class ModeratorApiService {
baseUrl = 'http://localhost:8080/api/moderator';
constructor(private http: HttpClient) {
}
getBePublishedTutorials(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
}
getTutorial(id: string | null | undefined): Observable<Tutorial> {
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
}
publish(id: any,data: any): Observable<any> {
return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data);
}
}
@@ -0,0 +1 @@
<p>moderator-welcome.component works!</p>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ModeratorWelcomeComponent } from './moderator-welcome.component';
describe('ModeratorWelcomeComponent', () => {
let component: ModeratorWelcomeComponent;
let fixture: ComponentFixture<ModeratorWelcomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ModeratorWelcomeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ModeratorWelcomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-moderator-welcome.component',
imports: [],
templateUrl: './moderator-welcome.component.html',
styleUrl: './moderator-welcome.component.scss'
})
export class ModeratorWelcomeComponent {
}
@@ -0,0 +1,4 @@
<app-side-bar-moderator class="pc-sidebar" ></app-side-bar-moderator>
<div class="pc-container">
<router-outlet></router-outlet>
</div>
@@ -0,0 +1,13 @@
//---------------
.pc-sidebar{
top: 65px;
overflow-y: auto;
background-color: rgba(153, 153, 153, 0.16);
backdrop-filter: blur(8px);
}
.pc-container{
top: 0px;
padding-left: 5px;
padding-right: 5px;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ModeratorComponent } from './moderator.component';
describe('ModeratorComponent', () => {
let component: ModeratorComponent;
let fixture: ComponentFixture<ModeratorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ModeratorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ModeratorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {SideBarModeratorComponent} from '../side-bar-moderator.component/side-bar-moderator.component';
@Component({
selector: 'app-moderator.component',
imports: [
RouterOutlet,
SideBarModeratorComponent,
],
templateUrl: './moderator.component.html',
styleUrl: './moderator.component.scss'
})
export class ModeratorComponent {
}
@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {moderatorRouting} from './moderator.routing';
import {ModeratorComponent} from './moderator.component/moderator.component';
@NgModule({
declarations: [],
imports: [
moderatorRouting,
ModeratorComponent,
CommonModule
]
})
export class ModeratorModule { }
@@ -0,0 +1,7 @@
import { ModeratorRouting } from './moderator.routing';
describe('ModeratorRouting', () => {
it('should create an instance', () => {
expect(new ModeratorRouting()).toBeTruthy();
});
});
@@ -0,0 +1,27 @@
import {RouterModule, Routes} from '@angular/router';
import {UserComponent} from '../user-module/user.component/user.component';
import {ModeratorComponent} from './moderator.component/moderator.component';
const MODERATOR_ROUTES: Routes = [
{
path: '',
component: ModeratorComponent,
children: [
{
path: 'moderator-welcome',
loadComponent: () => import('../moderator-module/moderator-welcome.component/moderator-welcome.component').then((c) => c.ModeratorWelcomeComponent),
},
{
path: 'tutorials-list',
loadComponent: () => import('../moderator-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent)
},
{
path: 'tutorial-preview',
loadComponent: () => import('../moderator-module/tutorial-preview.component/tutorial-preview.component').then((c) => c.TutorialPreviewComponent)
}
]
}
];
export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTES);
@@ -0,0 +1,18 @@
<mat-nav-list>
<a mat-list-item routerLink="moderator-welcome">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >Dashboard</span>
}
</span>
</a>
<a mat-list-item routerLink="tutorials-list">
<span class="entry">
<mat-icon>newspaper</mat-icon>
@if (!isCollapsed) {
<span >Tutorials</span>
}
</span>
</a>
</mat-nav-list>
@@ -0,0 +1,14 @@
.entry{
display: flex;
align-items: center;
gap: 1rem;
padding:0.75rem;
color: rgba(24, 255, 255, 0.96);
}
a.mdc-list-item
{
background-color: rgba(24,255,255,0.04);
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SideBarModeratorComponent } from './side-bar-moderator.component';
describe('SideBarModeratorComponent', () => {
let component: SideBarModeratorComponent;
let fixture: ComponentFixture<SideBarModeratorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SideBarModeratorComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SideBarModeratorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,20 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {MatIcon} from '@angular/material/icon';
import {MatListItem, MatNavList} from '@angular/material/list';
import {RouterLink} from '@angular/router';
@Component({
selector: 'app-side-bar-moderator',
imports: [
MatIcon,
MatListItem,
MatNavList,
RouterLink
],
templateUrl: './side-bar-moderator.component.html',
styleUrl: './side-bar-moderator.component.scss',
schemas:[CUSTOM_ELEMENTS_SCHEMA]
})
export class SideBarModeratorComponent {
isCollapsed = false;
}
@@ -0,0 +1,43 @@
<mat-card appearance="outlined">
<mat-card-header>
<mat-card-title>Edit tutorial</mat-card-title>
</mat-card-header>
<mat-card-content >
<mat-form-field class="example-full-width">
<mat-label>Title</mat-label>
<input matInput required
[(ngModel)]="tutorial.title">
</mat-form-field>
<mat-tab-group>
<mat-tab label="Preview">
<markdown class="preview" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Edit">
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Example">
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
<markdown class="variable-binding" [data]="markdown"></markdown>
</mat-tab>
</mat-tab-group>
</mat-card-content>
<mat-card-actions>
<button matButton (click)="publishTutorial()">Publicate</button>
@if (hasError){
<mat-error>
{{errorMessage}}
</mat-error>
}
@if(submitted) {
<h4>Tutorial was submitted successfully!</h4>
<button matButton routerLink="../tutorials-list">Back to list</button>
}
</mat-card-actions>
</mat-card>
@@ -0,0 +1,59 @@
.submit-form {
max-width: 400px;
margin: auto;
}
mat-card{
//margin: 20px;
}
mat-card-title{
color: cyan;
}
.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.example-full-width {
width: 100%;
}
.variable-binding,
.variable-textarea {
width: 49%;
}
.variable-textarea {
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0,0,0,.07);
min-height: 420px;
padding: 8px;
transition: all 300ms ease-out;
}
.variable-textarea:hover {
box-shadow: 0 6px 12px 3px rgba(0,0,0,.09),
0 2px 3px 1px rgba(0,0,0,.06);
}
.variable-binding {
display: block;
float: right;
}
.preview {
/* display: block;
float: right;*/
}
.mat-mdc-card-outlined {
background-color: var(--mat-card-outlined-container-color, var(--mat-sys-surface));
border-radius:0;
border-width: var(--mat-card-outlined-outline-width, 1px);
border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant));
box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0));
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TutorialPreviewComponent } from './tutorial-preview.component';
describe('TutorialPreviewComponent', () => {
let component: TutorialPreviewComponent;
let fixture: ComponentFixture<TutorialPreviewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TutorialPreviewComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TutorialPreviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,94 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {FormsModule} from "@angular/forms";
import {MarkdownComponent} from "ngx-markdown";
import {MatButton} from "@angular/material/button";
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
import {MatError, MatFormField, MatInput, MatLabel} from "@angular/material/input";
import {MatTab, MatTabGroup} from "@angular/material/tabs";
import {ActivatedRoute, RouterLink} from "@angular/router";
import {Tutorial} from '../../models/tutorial.model';
import {UserApiService} from '../../user-module/user-api.service';
import {ModeratorApiService} from '../moderator-api.service';
@Component({
selector: 'app-tutorial-preview.component',
imports: [
FormsModule,
MarkdownComponent,
MatButton,
MatCard,
MatCardActions,
MatCardContent,
MatCardHeader,
MatError,
MatFormField,
MatInput,
MatLabel,
MatTab,
MatTabGroup,
RouterLink,
MatError,
MatFormField
],
templateUrl: './tutorial-preview.component.html',
styleUrl: './tutorial-preview.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class TutorialPreviewComponent {
tutorial: Tutorial = new Tutorial();
submitted = false;
hasError = false;
errorMessage = '';
markdown = `## Markdown __rulez__!
---
### Syntax highlight
\`\`\`typescript
const language = 'typescript';
\`\`\`
### Lists
1. Ordered list
2. Another bullet point
- Unordered list
- Another unordered bullet
### Blockquote
> Blockquote to the max`;
private id: string | null | undefined;
constructor(private moderatorApiService: ModeratorApiService, private route: ActivatedRoute) {
this.route.queryParams
.subscribe(params => {
console.log(params);
this.id = params['id'];
console.log(this.id);
});
this.moderatorApiService.getTutorial(this.id).subscribe(
data=>{
this.tutorial = data;
}
);
}
publishTutorial(): void {
this.tutorial.published = true;
this.moderatorApiService.publish(this.id, this.tutorial)
.subscribe(
response => {
console.log(response);
this.submitted = true;
this.hasError = false;
},
error => {
console.log(error);
this.errorMessage = error.error.message;
this.hasError = true;
});
}
}
@@ -0,0 +1,44 @@
<table mat-table [dataSource]="dataSource">
@for (column of columnsSchema; track column){
<ng-container [matColumnDef]="column.key">
<th mat-header-cell *matHeaderCellDef>
{{column.label}}
</th>
<td mat-cell *matCellDef="let element">
@switch (column.type) {
@case('isEdit') {
<div class="btn-edit" >
<button mat-button routerLink='../tutorial-preview' [queryParams]="{id:element.id}" (click)="element.isEdit = !element.isEdit">
Preview
</button>
</div>
}
@case ('boolean') {
<mat-slide-toggle
class="example-margin"
[checked]="element[column.key]"
(change)="publish(element, $event.checked)"
>
</mat-slide-toggle>
}
@case ('datetime') {
{{ element[column.key] | date: 'medium' }}
}
@default {
{{ element[column.key] }}
}
}
</td>
</ng-container>
}
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TutorialsListComponent } from './tutorials-list.component';
describe('TutorialsListComponent', () => {
let component: TutorialsListComponent;
let fixture: ComponentFixture<TutorialsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TutorialsListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TutorialsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,144 @@
import { Component } from '@angular/core';
import {DatePipe} from "@angular/common";
import {MatButton} from "@angular/material/button";
import {
MatCell,
MatCellDef, MatColumnDef,
MatHeaderCell, MatHeaderCellDef,
MatHeaderRow,
MatHeaderRowDef,
MatRow,
MatRowDef, MatTable, MatTableDataSource
} from "@angular/material/table";
import {MatCheckbox} from "@angular/material/checkbox";
import {MatSlideToggle} from "@angular/material/slide-toggle";
import {Router, RouterLink} from "@angular/router";
import {Tutorial} from '../../models/tutorial.model';
import {EventData} from '../../_shared/event.class';
import {ModeratorApiService} from '../moderator-api.service';
import {TokenStorageService} from '../../services/token-storage.service';
import {EventBusService} from '../../_shared/event-bus.service';
import {AuthService} from '../../services/auth.service';
import {HttpHeaders} from '@angular/common/http';
@Component({
selector: 'app-tutorials-list.component',
imports: [
DatePipe,
MatButton,
MatCell,
MatCellDef,
MatHeaderCell,
MatHeaderRow,
MatHeaderRowDef,
MatRow,
MatRowDef,
MatSlideToggle,
MatTable,
RouterLink,
MatColumnDef,
MatHeaderCellDef
],
templateUrl: './tutorials-list.component.html',
styleUrl: './tutorials-list.component.scss'
})
export class TutorialsListComponent {
displayedColumns: string[] = TutorialColumns.map((col) => col.key)
columnsSchema: any = TutorialColumns
dataSource = new MatTableDataSource<Tutorial>()
constructor(private moderatorApiService: ModeratorApiService,
private storageService: TokenStorageService,
private eventBusService: EventBusService,
private authService: AuthService,
private router: Router
) {
this.getTutorials();
}
getTutorials(): void {
this.moderatorApiService.getBePublishedTutorials()
.subscribe(( data:Tutorial[] ) => {
this.dataSource.data = data;
console.log(data);
},
error => {
console.log(error);
if (
(
error.status === 401
)
&& this.storageService.isLoggedIn()
) {
this.eventBusService.emit(new EventData('logout', null));
}
});
}
publish(element: any, checked: boolean){
element.published = checked;
this.moderatorApiService.publish(element.id, element).subscribe(
response => {
console.log(response);
},
error => {
console.log(error);
});
}
logout(): void {
this.authService.logout().subscribe({
next: res => {
console.log(res);
this.storageService.clean();
//window.location.reload();
this.router.navigate(['main/generate-image']).then(() => {
//window.location.reload();
})
},
error: err => {
console.log(err);
}
});
}
}
export const TutorialColumns = [
{
key: 'title',
type: 'text',
label: 'Title',
required: true,
},
{
key: 'published',
type: 'boolean',
label: 'Is Published',
},
{
key: 'created',
type: 'datetime',
label: 'Created Date',
required: true,
},
{
key: 'modified',
type: 'datetime',
label: 'Modified Date',
required: true
},
{
key: 'isEdit',
type: 'isEdit',
label: '',
}
];
@@ -4,7 +4,7 @@
} }
mat-card{ mat-card{
margin: 20px; //margin: 20px;
} }
mat-card-title{ mat-card-title{
color: cyan; color: cyan;
@@ -50,3 +50,10 @@ mat-card-title{
float: right;*/ float: right;*/
} }
.mat-mdc-card-outlined {
background-color: var(--mat-card-outlined-container-color, var(--mat-sys-surface));
border-radius:0;
border-width: var(--mat-card-outlined-outline-width, 1px);
border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant));
box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0));
}
@@ -4,7 +4,6 @@ import {MatButton} from '@angular/material/button';
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
import {MatTab, MatTabGroup} from '@angular/material/tabs'; import {MatTab, MatTabGroup} from '@angular/material/tabs';
import {NgIf} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Tutorial} from '../../models/tutorial.model'; import {Tutorial} from '../../models/tutorial.model';
import {UserApiService} from '../user-api.service'; import {UserApiService} from '../user-api.service';
@@ -6,9 +6,6 @@
> >
Remove Rows Remove Rows
</button> </button>
<!--<button class="button-add-row" mat-button (click)="addRow()">
Add Row
</button>-->
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button> <button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
</article> </article>
@@ -56,7 +53,9 @@
<mat-slide-toggle <mat-slide-toggle
class="example-margin" class="example-margin"
[checked]="element[column.key]" [checked]="element[column.key]"
[disabled]="true"> [disabled]="column.key !== 'toBePublished'"
(change)="publish(element, $event.checked)"
>
</mat-slide-toggle> </mat-slide-toggle>
} }
@@ -1,6 +1,6 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core'; import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
import {MatList, MatListItem} from '@angular/material/list'; import {MatList, MatListItem} from '@angular/material/list';
import {Tutorial, TutorialColumns} from '../../models/tutorial.model'; import {Tutorial} from '../../models/tutorial.model';
import {TutorialService} from '../../services/tutorial.service'; import {TutorialService} from '../../services/tutorial.service';
import {UserApiService} from '../user-api.service'; import {UserApiService} from '../user-api.service';
import {MatButton} from '@angular/material/button'; import {MatButton} from '@angular/material/button';
@@ -23,22 +23,15 @@ import {
MatTable, MatTable,
MatTableDataSource MatTableDataSource
} from '@angular/material/table'; } from '@angular/material/table';
import {MatFormField, MatInput} from '@angular/material/input'; import {DatePipe} from '@angular/common';
import {DatePipe, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
import {MatCheckbox} from '@angular/material/checkbox'; import {MatCheckbox} from '@angular/material/checkbox';
import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from '@angular/material/datepicker';
import {MatSlideToggle} from '@angular/material/slide-toggle'; import {MatSlideToggle} from '@angular/material/slide-toggle';
@Component({ @Component({
selector: 'app-tutorials-list.component', selector: 'app-tutorials-list.component',
imports: [ imports: [
MatList,
MatListItem,
MatLine,
MatButton, MatButton,
MatIcon,
MatOption,
RouterLink, RouterLink,
ReactiveFormsModule, ReactiveFormsModule,
MatTable, MatTable,
@@ -47,22 +40,12 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
MatRowDef, MatRowDef,
MatRow, MatRow,
FormsModule, FormsModule,
MatInput,
MatFormField,
MatColumnDef, MatColumnDef,
MatHeaderCell, MatHeaderCell,
MatHeaderCellDef, MatHeaderCellDef,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
MatCellDef, MatCellDef,
MatCheckbox, MatCheckbox,
MatDatepickerInput,
MatDatepickerToggle,
MatDatepicker,
DatePipe, DatePipe,
NgIf,
NgForOf,
MatCell, MatCell,
MatSlideToggle MatSlideToggle
], ],
@@ -76,7 +59,7 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
displayedColumns: string[] = TutorialColumns.map((col) => col.key) displayedColumns: string[] = TutorialColumns.map((col) => col.key)
columnsSchema: any = TutorialColumns columnsSchema: any = TutorialColumns
dataSource = new MatTableDataSource<Tutorial>() dataSource = new MatTableDataSource<Tutorial>()
valid: any = {}
constructor(private userApiService: UserApiService, constructor(private userApiService: UserApiService,
private storageService: TokenStorageService, private storageService: TokenStorageService,
@@ -154,22 +137,16 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
}) })
} }
editRow(row: Tutorial) { publish(element: any, checked: boolean){
/*if (row.id === 0) { element.tobepublished = checked;
this.userService.addUser(row).subscribe((newUser: User) => { this.userApiService.update(element.id, element).subscribe(
row.id = newUser.id response => {
row.isEdit = false console.log(response);
}) },
} else { error => {
this.userService.updateUser(row).subscribe(() => (row.isEdit = false)) console.log(error);
}*/ });
}
disableSubmit(id: number) {
if (this.valid[id]) {
return Object.values(this.valid[id]).some((item) => item === false)
}
return false
} }
logout(): void { logout(): void {
@@ -191,3 +168,47 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
} }
} }
export const TutorialColumns = [
{
key: 'isSelected',
type: 'isSelected',
label: '',
},
{
key: 'title',
type: 'text',
label: 'Title',
required: true,
},
{
key: 'published',
type: 'boolean',
label: 'Is Published',
},
{
key: 'created',
type: 'datetime',
label: 'Created Date',
required: true,
},
{
key: 'modified',
type: 'datetime',
label: 'Modified Date',
required: true
},
{
key: 'toBePublished',
type: 'boolean',
label: 'To be published',
required: true
},
{
key: 'isEdit',
type: 'isEdit',
label: '',
}
];
@@ -9,16 +9,13 @@ import {HttpClient, HttpHeaders} from '@angular/common/http';
export class UserApiService { export class UserApiService {
baseUrl = 'http://localhost:8080/api/user'; baseUrl = 'http://localhost:8080/api/user';
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' } )
};
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
} }
getUserAllTutorials(): Observable<Tutorial[]> { getUserAllTutorials(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`, this.httpOptions); return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
} }
getTutorial(id: string | null | undefined): Observable<Tutorial> { getTutorial(id: string | null | undefined): Observable<Tutorial> {
@@ -66,30 +66,6 @@ public class TutorialController {
try { try {
List<Tutorial> tutorials = new ArrayList<Tutorial>(); List<Tutorial> tutorials = new ArrayList<Tutorial>();
/* Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String name = authenticationFacade.getAuthentication().getName();
User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get();
if (user.getRoles().stream().anyMatch(role -> role.getName().name().equals("ROLE_ADMIN"))) {*/
/*
if (title == null)
tutorialRepository.findAll().forEach(tutorials::add);
else
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
*/
/* } else {
// If the user is not an admin, filter tutorials by user
tutorialRepository.findByUserId(user.getId()).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
}*/
//all published tutorials without authentication
tutorialRepository.findByPublished(true).forEach(tutorials::add); tutorialRepository.findByPublished(true).forEach(tutorials::add);
if (tutorials.isEmpty()) { if (tutorials.isEmpty()) {
@@ -102,6 +78,23 @@ public class TutorialController {
} }
} }
@GetMapping("/moderator/tutorials")
public ResponseEntity<List<Tutorial>> getBePublishedTutorials(@RequestParam(required = false) String title) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
tutorialRepository.findBytobepublished(true).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("user/tutorial-add") @PostMapping("user/tutorial-add")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) { public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
@@ -111,8 +104,18 @@ public class TutorialController {
User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get(); User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get();
try { try {
Tutorial newTutorial =new Tutorial(
tutorial.getTitle(),
tutorial.getDescription(),
false,
false,
user,
Timestamp.valueOf(LocalDateTime.now()),
Timestamp.valueOf(LocalDateTime.now())
);
Tutorial _tutorial = tutorialRepository Tutorial _tutorial = tutorialRepository
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user, Timestamp.valueOf(LocalDateTime.now()), Timestamp.valueOf(LocalDateTime.now()))); .save(newTutorial);
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED); return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
} catch (Exception e) { } catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
@@ -131,6 +134,18 @@ public class TutorialController {
} }
} }
@GetMapping("moderator/tutorial-get/{id}")
public ResponseEntity<Tutorial> getBePublishedTutorial(@PathVariable("id") long id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<Tutorial>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PutMapping("user/tutorial-update/{id}") @PutMapping("user/tutorial-update/{id}")
public ResponseEntity<?> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) { public ResponseEntity<?> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id); Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
@@ -140,6 +155,31 @@ public class TutorialController {
servTutorial.setTitle(tutorial.getTitle()); servTutorial.setTitle(tutorial.getTitle());
servTutorial.setDescription(tutorial.getDescription()); servTutorial.setDescription(tutorial.getDescription());
servTutorial.setPublished(tutorial.isPublished()); servTutorial.setPublished(tutorial.isPublished());
servTutorial.setTobepublished(tutorial.isTobepublished());
try {
servTutorial = tutorialRepository.save(servTutorial);
} catch (Exception e) {
map.put("status", 0);
map.put("message", e.getMessage());
return new ResponseEntity<>(map,HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(servTutorial, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PutMapping("moderator/tutorial-update/{id}")
public ResponseEntity<?> moderatorUpdateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
Map<String, Object> map = new LinkedHashMap<String, Object>();
if (tutorialData.isPresent()) {
Tutorial servTutorial = tutorialData.get();
servTutorial.setTitle(tutorial.getTitle());
servTutorial.setDescription(tutorial.getDescription());
servTutorial.setPublished(tutorial.isPublished());
servTutorial.setTobepublished(tutorial.isTobepublished());
try { try {
servTutorial = tutorialRepository.save(servTutorial); servTutorial = tutorialRepository.save(servTutorial);
} catch (Exception e) { } catch (Exception e) {
@@ -2,6 +2,7 @@ package com.jambotronGroup.jambotron.model;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.sql.Timestamp;
@Entity @Entity
@@ -21,6 +22,9 @@ public class Tutorial {
@Column(name = "published") @Column(name = "published")
private boolean published; private boolean published;
@Column(name = "tobepublished")
private boolean tobepublished;
@ManyToOne(fetch = FetchType.LAZY, optional = false) @ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "userID", nullable = false) @JoinColumn(name = "userID", nullable = false)
private User user; private User user;
@@ -37,10 +41,11 @@ public class Tutorial {
} }
public Tutorial(String title, String description, boolean published, User user, java.sql.Timestamp created, java.sql.Timestamp modified) { public Tutorial(String title, String description, boolean published, boolean tobepublished, User user, Timestamp created, Timestamp modified) {
this.title = title; this.title = title;
this.description = description; this.description = description;
this.published = published; this.published = published;
this.tobepublished = tobepublished;
this.user = user; this.user = user;
this.created = created; this.created = created;
this.modified = modified; this.modified = modified;
@@ -74,6 +79,14 @@ public class Tutorial {
this.published = isPublished; this.published = isPublished;
} }
public boolean isTobepublished() {
return tobepublished;
}
public void setTobepublished(boolean tobepublished) {
this.tobepublished = tobepublished;
}
public void setCreated(java.sql.Timestamp created) { public void setCreated(java.sql.Timestamp created) {
this.created = created; this.created = created;
} }
@@ -19,6 +19,7 @@ import java.util.Set;
public class User { public class User {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id; private long id;
@Column(name = "username") @Column(name = "username")
@@ -14,5 +14,7 @@ public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
List<Tutorial> findByUserId(Long userId); List<Tutorial> findByUserId(Long userId);
List<Tutorial> findByUserIdAndTitle(Long userId, String title); List<Tutorial> findByUserIdAndTitle(Long userId, String title);
List<Tutorial> findByPublished(boolean published); List<Tutorial> findByPublished(boolean published);
List<Tutorial> findBytobepublished(boolean tobepublished);
List<Tutorial> findByIdAndTobepublished(Long id,boolean tobepublished);
List<Tutorial> findByTitleContaining(String title); List<Tutorial> findByTitleContaining(String title);
} }
@@ -140,7 +140,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
.requestMatchers("/api/public/tutorials/**").permitAll() .requestMatchers("/api/public/tutorials/**").permitAll()
.requestMatchers("/api/user/**").permitAll() .requestMatchers("/api/user/**").permitAll()
.requestMatchers("/api/moderator/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
); );
@@ -160,6 +160,8 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
config.addAllowedHeader("*"); config.addAllowedHeader("*");
config.addAllowedMethod("*"); config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);
return new CorsFilter(source); return new CorsFilter(source);
} }
} }
@@ -50,6 +50,8 @@ app.origin=http://localhost:4200
spring.mvc.throw-exception-if-no-handler-found=true spring.mvc.throw-exception-if-no-handler-found=true
#spring.mvc.dispatch-options-request=true
# Disable the default mappings # Disable the default mappings
#spring.resources.add-mappings=false #spring.resources.add-mappings=false
+3 -1
View File
@@ -1,7 +1,9 @@
CREATE TABLE IF NOT EXISTS roles CREATE TABLE IF NOT EXISTS roles
( (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name character varying(20) COLLATE pg_catalog."default" name character varying(20)
); );
@@ -4,6 +4,7 @@ CREATE TABLE tutorials
( (
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ), id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
published boolean, published boolean,
tobepublished boolean,
title character varying(255) COLLATE pg_catalog."default", title character varying(255) COLLATE pg_catalog."default",
userid bigint, userid bigint,
created timestamp with time zone, created timestamp with time zone,