Compare commits

...

3 Commits
master ... dev

29 changed files with 197 additions and 153 deletions

View File

@ -13,7 +13,7 @@
- [ ] L'avatar s'affiche pas quand on upload un commentaire (il faut recharger la page) - [ ] L'avatar s'affiche pas quand on upload un commentaire (il faut recharger la page)
- [ ] Faire des meilleurs modal - [ ] Faire des meilleurs modal
- [ ] Terminer l'interface admin - [ ] Terminer l'interface admin
- [ ] Bug (de temps en temps) pour stocker les cookies utilisateur - [x] Bug (de temps en temps) pour stocker les cookies utilisateur
pour run le docker : pour run le docker :
``` ```

View File

@ -28,14 +28,10 @@
} }
], ],
"styles": [ "styles": [
"@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.css" "src/styles.css"
], ],
"scripts": [], "scripts": []
"server": "src/main.server.ts",
"prerender": true,
"ssr": {
"entry": "server.ts"
}
}, },
"configurations": { "configurations": {
"production": { "production": {
@ -97,6 +93,7 @@
} }
], ],
"styles": [ "styles": [
"@angular/material/prebuilt-themes/magenta-violet.css",
"src/styles.css" "src/styles.css"
], ],
"scripts": [] "scripts": []

View File

@ -12,20 +12,23 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^18.2.0", "@angular/animations": "^18.2.0",
"@angular/cdk": "^18.2.14",
"@angular/common": "^18.2.0", "@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0", "@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0", "@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0", "@angular/forms": "^18.2.0",
"@angular/material": "^18.2.14",
"@angular/platform-browser": "^18.2.0", "@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0",
"@angular/platform-server": "^18.2.0", "@angular/platform-server": "^18.2.0",
"@angular/router": "^18.2.0", "@angular/router": "^18.2.0",
"@angular/ssr": "^18.2.12", "@angular/ssr": "^18.2.18",
"@primeng/themes": "^19.1.0",
"express": "^4.18.2", "express": "^4.18.2",
"luxon": "^3.5.0", "luxon": "^3.5.0",
"ngx-cookie-service": "^18.0.0", "ngx-cookie-service": "^18.0.0",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primeng": "^17.18.10", "primeng": "^18.0.2",
"quill": "^2.0.3", "quill": "^2.0.3",
"review-front": "file:", "review-front": "file:",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
@ -33,8 +36,8 @@
"zone.js": "~0.14.10" "zone.js": "~0.14.10"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^18.2.12", "@angular-devkit/build-angular": "^18.2.18",
"@angular/cli": "^18.2.12", "@angular/cli": "^18.2.18",
"@angular/compiler-cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",

View File

@ -1,5 +1,5 @@
<router-outlet></router-outlet> <router-outlet></router-outlet>
@if (isBrowser()) { @if (isBrowser() && (authService.isSessionExpired() && authService.isAuthenticated())) {
<p-dialog header="ATTENTION !" [modal]="true" [closable]="false" [visible]="isSessionExpired"> <p-dialog header="ATTENTION !" [modal]="true" [closable]="false" [visible]="isSessionExpired">
<span>Votre session a <strong>expiré</strong> ! Il va falloir se reconnecter.</span> <span>Votre session a <strong>expiré</strong> ! Il va falloir se reconnecter.</span>
<div class="expired-dialog"> <div class="expired-dialog">

View File

@ -7,12 +7,12 @@ import {DialogModule} from 'primeng/dialog';
import {isPlatformBrowser} from '@angular/common'; import {isPlatformBrowser} from '@angular/common';
import {Button} from 'primeng/button'; import {Button} from 'primeng/button';
import {AuthService} from './auth.service'; import {AuthService} from './auth.service';
import {CookieService} from 'ngx-cookie-service'; import {Router, RouterOutlet} from '@angular/router';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [MenubarModule, FloatLabelModule, ToastModule, DialogModule, Button], imports: [MenubarModule, FloatLabelModule, ToastModule, DialogModule, Button, RouterOutlet],
providers: [ providers: [
MessageService, MessageService,
], ],
@ -23,17 +23,18 @@ export class AppComponent implements OnInit {
isSessionExpired: boolean = false; isSessionExpired: boolean = false;
constructor(@Inject(PLATFORM_ID) private platformId: object, constructor(@Inject(PLATFORM_ID) private platformId: object,
private authService: AuthService, protected authService: AuthService,
private cookieService: CookieService) { private router: Router,) {
} }
isBrowser(): boolean { isBrowser(): boolean {
return isPlatformBrowser(this.platformId); return isPlatformBrowser(this.platformId)
} }
setSessionExpiredFalse(): void { setSessionExpiredFalse(): void {
this.isSessionExpired = false; this.isSessionExpired = false;
this.authService.setSessionExpired(false); this.authService.setSessionExpired(false);
this.router.navigate(['/logout']);
} }
ngOnInit(): void { ngOnInit(): void {

View File

@ -1,10 +1,12 @@
import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core'; import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core';
import {provideRouter} from '@angular/router'; import {provideRouter} from '@angular/router';
import {routes} from './app.routes'; import {routes} from './app.routes';
import {provideClientHydration} from '@angular/platform-browser'; import {provideClientHydration} from '@angular/platform-browser';
import {provideHttpClient, withFetch} from '@angular/common/http'; import {provideHttpClient, withFetch} from '@angular/common/http';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
import {providePrimeNG} from 'primeng/config';
import {myPreset} from './preset'
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@ -12,5 +14,14 @@ export const appConfig: ApplicationConfig = {
provideRouter(routes), provideRouter(routes),
provideClientHydration(), provideClientHydration(),
provideHttpClient(withFetch()), provideHttpClient(withFetch()),
importProvidersFrom([BrowserAnimationsModule])] provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: myPreset,
options: {
darkModeSelector: '.my-app-dark' // class css pour activer le dark mode
}
}
}),
importProvidersFrom([BrowserAnimationsModule]), provideAnimationsAsync()]
}; };

View File

@ -1,5 +1,4 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import {Author} from './models/author'; import {Author} from './models/author';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
import {DateTime} from 'luxon'; import {DateTime} from 'luxon';
@ -11,33 +10,38 @@ export class AuthService {
private sessionExpiredSubject = new BehaviorSubject<boolean>(false); private sessionExpiredSubject = new BehaviorSubject<boolean>(false);
sessionExpired$ = this.sessionExpiredSubject.asObservable(); sessionExpired$ = this.sessionExpiredSubject.asObservable();
constructor(private cookieService: CookieService) { constructor() {
this.checkSessionExpiration(); this.checkSessionExpiration();
} }
isAuthenticated(): boolean { isAuthenticated(): boolean {
return this.cookieService.check("author") && return sessionStorage.getItem("author") !== null &&
this.cookieService.check("token") && sessionStorage.getItem("token") !== null &&
this.cookieService.check("token-expiration-date") && sessionStorage.getItem("token-expiration-date") !== null;
this.cookieService.get("author") !== '' &&
this.cookieService.get("token-expiration-date") !== '' &&
this.cookieService.get("token") !== '';
} }
getTokenExpirationDate(): DateTime { getTokenExpirationDate(): string | null {
return DateTime.fromISO(this.cookieService.get("token-expiration-date")); return sessionStorage.getItem("token-expiration-date");
} }
isSessionExpired(): boolean { isSessionExpired(): boolean {
return this.getTokenExpirationDate() < DateTime.now() && this.isAuthenticated(); const tokenExpirationDate = this.getTokenExpirationDate();
if (tokenExpirationDate) {
return DateTime.fromISO(tokenExpirationDate) < DateTime.now() && this.isAuthenticated();
}
return true
} }
getAuthenticatedAuthor(): Author { getAuthenticatedAuthor(): Author | null {
return JSON.parse(this.cookieService.get('author')); const authorStr = sessionStorage.getItem('author')
if (authorStr) {
return JSON.parse(authorStr);
}
return null;
} }
getAuthenticatedAuthorToken(): string { getAuthenticatedAuthorToken(): string | null{
return this.cookieService.get('token'); return sessionStorage.getItem('token');
} }
setSessionExpired(expired: boolean) { setSessionExpired(expired: boolean) {

View File

@ -1,9 +1,7 @@
import {Component, EventEmitter, Input, Output} from '@angular/core'; import {Component, EventEmitter, Input, Output} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {InputTextareaModule} from 'primeng/inputtextarea';
import {Button} from 'primeng/button'; import {Button} from 'primeng/button';
import {CommentService} from '../../services/comment.service'; import {CommentService} from '../../services/comment.service';
import {Author} from '../../models/author';
import {Subscription} from 'rxjs'; import {Subscription} from 'rxjs';
import {Comment} from '../../models/comment'; import {Comment} from '../../models/comment';
import {MessageService} from 'primeng/api'; import {MessageService} from 'primeng/api';
@ -15,7 +13,6 @@ import {AuthService} from '../../auth.service';
standalone: true, standalone: true,
imports: [ imports: [
ReactiveFormsModule, ReactiveFormsModule,
InputTextareaModule,
Button, Button,
NgStyle NgStyle
], ],
@ -36,8 +33,8 @@ export class CommentFormComponent {
} }
onSubmit() { onSubmit() {
let token: string = this.authService.getAuthenticatedAuthorToken(); let token = this.authService.getAuthenticatedAuthorToken();
let author: Author = this.authService.getAuthenticatedAuthor(); let author = this.authService.getAuthenticatedAuthor();
if (this.commentForm.valid && author && token && this.commentForm.value.content) { if (this.commentForm.valid && author && token && this.commentForm.value.content) {
// get l'image de profile après avoir créé le commentaire // get l'image de profile après avoir créé le commentaire
this.subs.push(this.commentService.create(this.commentForm.value.content, this.postId, author.id, token).subscribe({ this.subs.push(this.commentService.create(this.commentForm.value.content, this.postId, author.id, token).subscribe({

View File

@ -25,8 +25,9 @@ export class HeaderComponent {
} }
private initializeMenu(): void { private initializeMenu(): void {
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
this.actualAuthor = this.authService.getAuthenticatedAuthor(); if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
} }
if (this.actualAuthor) { if (this.actualAuthor) {

View File

@ -1,7 +1,6 @@
import {Component, Input, OnDestroy} from '@angular/core'; import {Component, Input, OnDestroy} from '@angular/core';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {InputTextModule} from 'primeng/inputtext'; import {InputTextModule} from 'primeng/inputtext';
import {InputTextareaModule} from 'primeng/inputtextarea';
import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload';
import {mergeMap, Subscription} from 'rxjs'; import {mergeMap, Subscription} from 'rxjs';
import {PostService} from '../../services/post.service'; import {PostService} from '../../services/post.service';
@ -11,6 +10,7 @@ import {Router} from '@angular/router';
import {Author} from '../../models/author'; import {Author} from '../../models/author';
import {AuthorService} from '../../services/author.service'; import {AuthorService} from '../../services/author.service';
import {AuthService} from '../../auth.service'; import {AuthService} from '../../auth.service';
import {Button} from 'primeng/button';
@Component({ @Component({
selector: 'app-post-form', selector: 'app-post-form',
@ -18,9 +18,9 @@ import {AuthService} from '../../auth.service';
imports: [ imports: [
ReactiveFormsModule, ReactiveFormsModule,
InputTextModule, InputTextModule,
InputTextareaModule,
FileUploadModule, FileUploadModule,
EditorModule EditorModule,
Button
], ],
templateUrl: './post-form.component.html', templateUrl: './post-form.component.html',
styleUrls: ['./post-form.component.css'] styleUrls: ['./post-form.component.css']
@ -105,9 +105,9 @@ export class PostFormComponent implements OnDestroy {
if (this.isUpdateMode && this.postId) { if (this.isUpdateMode && this.postId) {
this.subs.push( this.subs.push(
this.postService.updatePost(this.postId, postData, this.authService.getAuthenticatedAuthorToken()).pipe( this.postService.updatePost(this.postId, postData, this.authService.getAuthenticatedAuthorToken()!).pipe(
mergeMap((_) => { mergeMap((_) => {
return this.postService.changeIllustration(this.postId, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()); return this.postService.changeIllustration(this.postId, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()!);
}) })
).subscribe({ ).subscribe({
next: (_) => { next: (_) => {
@ -118,11 +118,11 @@ export class PostFormComponent implements OnDestroy {
); );
} else { } else {
this.subs.push( this.subs.push(
this.postService.createPost(postData, this.authService.getAuthenticatedAuthorToken()).pipe( this.postService.createPost(postData, this.authService.getAuthenticatedAuthorToken()!).pipe(
mergeMap(post => mergeMap(post =>
this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()).pipe( this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()!).pipe(
mergeMap((_) => mergeMap((_) =>
this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()), this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()!),
) )
) )
) )

View File

@ -5,7 +5,7 @@
<span>{{ category }}</span> <span>{{ category }}</span>
<em>Publié le {{ date | date : "dd/MM/yyyy à HH:mm" }}</em> <em>Publié le {{ date | date : "dd/MM/yyyy à HH:mm" }}</em>
<span class="desc">{{ description }}</span> <span class="desc">{{ description }}</span>
<p-button routerLink="post/{{ postId }}" label="Lire la suite"/> <p-button routerLink="post/{{ postId }}" >Lire la suite</p-button>
<a routerLink="/profile/{{ authorId }}" class="user-profile"> <a routerLink="/profile/{{ authorId }}" class="user-profile">
@if (authorProfilePicture) { @if (authorProfilePicture) {
<p-avatar image="data:image/jpeg;base64,{{ authorProfilePicture }}" shape="circle" styleClass="mr-2" <p-avatar image="data:image/jpeg;base64,{{ authorProfilePicture }}" shape="circle" styleClass="mr-2"

View File

@ -4,16 +4,17 @@ import {CardModule} from 'primeng/card';
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {RouterLink} from '@angular/router'; import {RouterLink} from '@angular/router';
import {AvatarModule} from 'primeng/avatar'; import {AvatarModule} from 'primeng/avatar';
import {MatButton, MatFabButton} from '@angular/material/button';
@Component({ @Component({
selector: 'app-post-home', selector: 'app-post-home',
standalone: true, standalone: true,
imports: [ imports: [
Button,
CardModule, CardModule,
DatePipe, DatePipe,
RouterLink, RouterLink,
AvatarModule AvatarModule,
Button,
], ],
templateUrl: './post-home.component.html', templateUrl: './post-home.component.html',
styleUrl: './post-home.component.css' styleUrl: './post-home.component.css'

View File

@ -37,7 +37,7 @@ export class RegisterFormComponent implements OnDestroy {
]; ];
subs: Subscription[] = []; subs: Subscription[] = [];
form: FormGroup; form: FormGroup;
actualAuthor: Author | undefined; actualAuthor: string | undefined;
constructor(private formBuilder: FormBuilder, constructor(private formBuilder: FormBuilder,
private authorService: AuthorService, private authorService: AuthorService,
@ -45,8 +45,9 @@ export class RegisterFormComponent implements OnDestroy {
private messageService: MessageService, private messageService: MessageService,
private authService: AuthService, private authService: AuthService,
) { ) {
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { const authenticatedAuthor = this.authService.getAuthenticatedAuthorToken();
this.actualAuthor = this.authService.getAuthenticatedAuthor(); if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
} }
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
username: ['', [Validators.required, Validators.maxLength(255)]], username: ['', [Validators.required, Validators.maxLength(255)]],
@ -83,7 +84,7 @@ export class RegisterFormComponent implements OnDestroy {
this.username, this.username,
this.password, this.password,
this.role, this.role,
this.authService.getAuthenticatedAuthorToken()).subscribe({ this.authService.getAuthenticatedAuthorToken()!).subscribe({
next: (author: Author) => { next: (author: Author) => {
this.successMessage('Succès', `Auteur ${author.name} créé avec succès`); this.successMessage('Succès', `Auteur ${author.name} créé avec succès`);
this.createdAuthor.emit(author); this.createdAuthor.emit(author);

View File

@ -7,7 +7,6 @@ import {Subscription, switchMap} from 'rxjs';
import {AuthorService} from '../../services/author.service'; import {AuthorService} from '../../services/author.service';
import {MessageService} from 'primeng/api'; import {MessageService} from 'primeng/api';
import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload';
import {CookieService} from 'ngx-cookie-service';
import {Author} from '../../models/author'; import {Author} from '../../models/author';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {AuthService} from '../../auth.service'; import {AuthService} from '../../auth.service';
@ -38,7 +37,6 @@ export class UpdateProfileFormComponent implements OnDestroy {
constructor(private formBuilder: FormBuilder, constructor(private formBuilder: FormBuilder,
private authorService: AuthorService, private authorService: AuthorService,
private messageService: MessageService, private messageService: MessageService,
private cookieService: CookieService,
private authService: AuthService, private authService: AuthService,
private router: Router, private router: Router,
) { ) {
@ -80,7 +78,7 @@ export class UpdateProfileFormComponent implements OnDestroy {
} }
onSubmit() { onSubmit() {
const token: string = this.authService.getAuthenticatedAuthorToken(); const token = this.authService.getAuthenticatedAuthorToken();
if (this.form.valid && token && this.password === this.passwordConfirm) { if (this.form.valid && token && this.password === this.passwordConfirm) {
const newUsername = this.form.value.username; const newUsername = this.form.value.username;
if (this.uploadedFile) { if (this.uploadedFile) {
@ -92,7 +90,7 @@ export class UpdateProfileFormComponent implements OnDestroy {
next: (author: Author) => { next: (author: Author) => {
this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès");
this.updatedAuthorEvent.emit(author); this.updatedAuthorEvent.emit(author);
this.cookieService.set('author', JSON.stringify(author)); sessionStorage.setItem('author', JSON.stringify(author));
this.router.navigate(['/']); this.router.navigate(['/']);
}, },
error: (err) => { error: (err) => {
@ -104,7 +102,7 @@ export class UpdateProfileFormComponent implements OnDestroy {
next: (author: Author) => { next: (author: Author) => {
this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès"); this.successMessage("Mise à jour réussie", "Profil mit à jour avec succès");
this.updatedAuthorEvent.emit(author); this.updatedAuthorEvent.emit(author);
this.cookieService.set('author', JSON.stringify(author)); sessionStorage.setItem('author', JSON.stringify(author));
this.router.navigate(['/']); this.router.navigate(['/']);
}, },
error: (err) => { error: (err) => {

View File

@ -1,17 +1,13 @@
import {CanActivateFn, Router} from '@angular/router'; import {CanActivateFn, Router} from '@angular/router';
import {inject} from '@angular/core'; import {inject} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {AuthService} from '../auth.service'; import {AuthService} from '../auth.service';
import {Role} from '../models/role'; import {Role} from '../models/role';
export const adminGuard: CanActivateFn = (route, state) => { export const adminGuard: CanActivateFn = (route, state) => {
const router = inject(Router); const router = inject(Router);
const cookieService = inject(CookieService);
const authService: AuthService = inject(AuthService); const authService: AuthService = inject(AuthService);
if ((authService.isAuthenticated() && JSON.parse(sessionStorage.getItem("author")!).role !== Role.ADMIN) || !authService.isAuthenticated()) {
if ((authService.isAuthenticated() && JSON.parse(cookieService.get("author")).role !== Role.ADMIN) || !authService.isAuthenticated()) {
router.navigate(['/']); router.navigate(['/']);
} }
return true; return true;
}; };

View File

@ -1,11 +1,9 @@
import {CanActivateFn, Router} from '@angular/router'; import {CanActivateFn, Router} from '@angular/router';
import {inject} from '@angular/core'; import {inject} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
export const authGuard: CanActivateFn = (route, state) => { export const authGuard: CanActivateFn = (route, state) => {
const router = inject(Router); const router = inject(Router);
const cookieService = inject(CookieService); if (sessionStorage.getItem("author") !== null || sessionStorage.getItem("token") !== null) {
if (cookieService.check("author") || cookieService.check("token")) {
router.navigate(['/']); router.navigate(['/']);
} }

View File

@ -1,17 +1,18 @@
import {CanActivateFn, Router} from '@angular/router'; import {CanActivateFn, Router} from '@angular/router';
import {inject} from '@angular/core'; import {inject} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {AuthService} from '../auth.service'; import {AuthService} from '../auth.service';
import {Role} from '../models/role'; import {Role} from '../models/role';
export const writerGuard: CanActivateFn = (route, state) => { export const writerGuard: CanActivateFn = (route, state) => {
const router = inject(Router); const router = inject(Router);
const cookieService = inject(CookieService);
const authService = inject(AuthService); const authService = inject(AuthService);
const authorStr = sessionStorage.getItem("author");
if ((authService.isAuthenticated() && JSON.parse(cookieService.get("author")).role !== Role.WRITER) || !authService.isAuthenticated()) { if (authorStr) {
if ((authService.isAuthenticated() && JSON.parse(authorStr).role !== Role.WRITER) || !authService.isAuthenticated()) {
router.navigate(['/']); router.navigate(['/']);
} }
return true; return true;
}
return false;
}; };

View File

@ -33,7 +33,7 @@
</td> </td>
<td>{{ author.role }}</td> <td>{{ author.role }}</td>
<td> <td>
<p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warning" <p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warn"
label="Modifier"/> label="Modifier"/>
<p-dialog header='Modifier "{{ author.name }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]"> <p-dialog header='Modifier "{{ author.name }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]">
<app-register-form [adminForm]="true" [username]="author.name"> <app-register-form [adminForm]="true" [username]="author.name">

View File

@ -29,9 +29,9 @@ export class HomeComponent implements OnDestroy {
constructor( constructor(
private postService: PostService, private postService: PostService,
private authService: AuthService) { private authService: AuthService) {
const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authenticatedAuthor) {
this.actualAuthor = this.authService.getAuthenticatedAuthor(); this.actualAuthor = authenticatedAuthor;
} else { } else {
this.authService.checkSessionExpiration(); this.authService.checkSessionExpiration();
} }

View File

@ -7,7 +7,6 @@ import {ToastModule} from 'primeng/toast';
import {MessageService} from 'primeng/api'; import {MessageService} from 'primeng/api';
import {Author} from '../../models/author'; import {Author} from '../../models/author';
import {Subscription, switchMap} from 'rxjs'; import {Subscription, switchMap} from 'rxjs';
import {CookieService} from 'ngx-cookie-service';
import {HeaderComponent} from '../../components/header/header.component'; import {HeaderComponent} from '../../components/header/header.component';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {ConfigurationService} from '../../configuration.service'; import {ConfigurationService} from '../../configuration.service';
@ -35,9 +34,9 @@ export class LoginComponent implements OnDestroy {
constructor(private authorService: AuthorService, constructor(private authorService: AuthorService,
private messageService: MessageService, private messageService: MessageService,
private cookieService: CookieService,
private router: Router, private router: Router,
private configurationService: ConfigurationService,) {} private configurationService: ConfigurationService,) {
}
sendLogins(): void { sendLogins(): void {
if (this.password === this.confirmPassword) { if (this.password === this.confirmPassword) {
@ -45,26 +44,15 @@ export class LoginComponent implements OnDestroy {
( (
this.authorService.login(this.name, this.password).pipe( this.authorService.login(this.name, this.password).pipe(
switchMap((tokenObj: any) => { switchMap((tokenObj: any) => {
this.cookieService.delete('token', '/', this.configurationService.getDomain()) // sessionStorage.removeItem('token');
this.cookieService.set("token", tokenObj.token, { sessionStorage.setItem('token', tokenObj.token);
domain: this.configurationService.getDomain(),
secure: true,
path: '/'
});
return this.authorService.me(tokenObj.token) return this.authorService.me(tokenObj.token)
})) }))
.subscribe({ .subscribe({
next: (author: Author) => { next: (author: Author) => {
this.cookieService.delete('author', '/', this.configurationService.getDomain()) // sessionStorage.removeItem('author');
this.cookieService.set("author", JSON.stringify(author), { sessionStorage.setItem('author', JSON.stringify(author));
domain: this.configurationService.getDomain(), sessionStorage.setItem('token-expiration-date', DateTime.now().plus({millisecond: this.configurationService.getTokenTTL()}).toISO())
secure : true,
path: '/' });
this.cookieService.set('token-expiration-date', DateTime.now().plus({millisecond: this.configurationService.getTokenTTL()}).toISO(), {
domain: this.configurationService.getDomain(),
secure: true,
path: '/',
})
this.getAuthorCookie(); this.getAuthorCookie();
this.router.navigate(['/']).then(() => { this.router.navigate(['/']).then(() => {
this.successMessage('Connecté avec succès', 'Heureux de vous revoir ' + this.actualAuthor?.name) this.successMessage('Connecté avec succès', 'Heureux de vous revoir ' + this.actualAuthor?.name)
@ -79,7 +67,10 @@ export class LoginComponent implements OnDestroy {
} }
getAuthorCookie(): void { getAuthorCookie(): void {
this.actualAuthor = JSON.parse(this.cookieService.get("author")); const authorStr = sessionStorage.getItem('author');
if (authorStr) {
this.actualAuthor = JSON.parse(authorStr);
}
} }
successMessage(summary: string, detail: string): void { successMessage(summary: string, detail: string): void {

View File

@ -1,9 +1,7 @@
import {Component, OnInit} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {HeaderComponent} from '../../components/header/header.component'; import {HeaderComponent} from '../../components/header/header.component';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import {MessageService} from 'primeng/api'; import {MessageService} from 'primeng/api';
import {ConfigurationService} from '../../configuration.service';
@Component({ @Component({
selector: 'app-logout', selector: 'app-logout',
@ -16,17 +14,14 @@ import {ConfigurationService} from '../../configuration.service';
}) })
export class LogoutComponent implements OnInit { export class LogoutComponent implements OnInit {
constructor(private cookieService: CookieService, constructor(private messageService: MessageService,
private messageService: MessageService, private router: Router) {
private router: Router, }
private configurationService: ConfigurationService,) { }
ngOnInit(): void { ngOnInit(): void {
const routes: string[] = ['/', '/login', '/register', '/logout', '/profile', '/post', '/new-post', '/admin'] sessionStorage.removeItem("author");
Object.keys(this.cookieService.getAll()).forEach(key => { sessionStorage.removeItem("token");
routes.forEach(route => { sessionStorage.removeItem("token-expiration-date");
this.cookieService.delete(key, route, this.configurationService.getDomain());
})
});
this.router.navigate(['/']).then(() => this.successMessage('Déconnexion', 'Vous avez été deconnecté avec succès')); this.router.navigate(['/']).then(() => this.successMessage('Déconnexion', 'Vous avez été deconnecté avec succès'));
} }

View File

@ -35,7 +35,7 @@
</p-dialog> </p-dialog>
</td> </td>
<td> <td>
<p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warning" <p-button icon="pi pi-pencil" (click)="openDialog(updateDialogVisibility, rowIndex)" severity="warn"
label="Modifier"/> label="Modifier"/>
<p-dialog header='Modifier "{{ post.title }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]"> <p-dialog header='Modifier "{{ post.title }}"' [modal]="true" [(visible)]="updateDialogVisibility[rowIndex]">
<app-post-form [actualAuthor]="actualAuthor" <app-post-form [actualAuthor]="actualAuthor"

View File

@ -37,14 +37,17 @@ export class MyPostsComponent implements OnDestroy {
updateDialogVisibility: boolean[] = []; updateDialogVisibility: boolean[] = [];
deleteDialogVisibility: boolean[] = []; deleteDialogVisibility: boolean[] = [];
posts: Post[] = []; posts: Post[] = [];
actualAuthor: Author; actualAuthor: Author | undefined;
constructor(private authService: AuthService, constructor(private authService: AuthService,
private postService: PostService, private postService: PostService,
private authorService: AuthorService, private authorService: AuthorService,
private messageService: MessageService) { private messageService: MessageService) {
this.actualAuthor = this.authService.getAuthenticatedAuthor(); const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
if (authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
}
this.updatePosts(); this.updatePosts();
} }
@ -59,8 +62,9 @@ export class MyPostsComponent implements OnDestroy {
} }
updatePosts(): void { updatePosts(): void {
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { const authorToken = this.authService.getAuthenticatedAuthorToken()
this.authorService.getAuthorsPosts(this.actualAuthor?.id, this.authService.getAuthenticatedAuthorToken()).subscribe({ if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated() && authorToken) {
this.authorService.getAuthorsPosts(this.actualAuthor?.id, authorToken).subscribe({
next: posts => this.posts = posts, next: posts => this.posts = posts,
error: error => this.failureMessage("Erreur", error.error.message), error: error => this.failureMessage("Erreur", error.error.message),
} }
@ -71,13 +75,16 @@ export class MyPostsComponent implements OnDestroy {
} }
deletePost(id: bigint, rowIndex: number) { deletePost(id: bigint, rowIndex: number) {
this.postService.deletePost(id, this.authService.getAuthenticatedAuthorToken()).subscribe({ const authorToken = this.authService.getAuthenticatedAuthorToken()
if (authorToken) {
this.postService.deletePost(id, authorToken).subscribe({
next: (_) => { next: (_) => {
this.updatePosts() this.updatePosts()
this.successMessage("Post supprimé", "Ce post a été supprimé avec succès") this.successMessage("Post supprimé", "Ce post a été supprimé avec succès")
}, },
error: error => this.failureMessage("Erreur", error.error.message), error: error => this.failureMessage("Erreur", error.error.message),
}); });
}
this.closeDialog(this.deleteDialogVisibility, rowIndex) this.closeDialog(this.deleteDialogVisibility, rowIndex)
} }

View File

@ -2,7 +2,6 @@ import {Component, EventEmitter, OnDestroy} from '@angular/core';
import {HeaderComponent} from '../../components/header/header.component'; import {HeaderComponent} from '../../components/header/header.component';
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {InputTextModule} from 'primeng/inputtext'; import {InputTextModule} from 'primeng/inputtext';
import {InputTextareaModule} from 'primeng/inputtextarea';
import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload'; import {FileSelectEvent, FileUploadModule} from 'primeng/fileupload';
import {mergeMap, Subscription} from 'rxjs'; import {mergeMap, Subscription} from 'rxjs';
import {PostService} from '../../services/post.service'; import {PostService} from '../../services/post.service';
@ -21,7 +20,6 @@ import {AuthService} from '../../auth.service';
HeaderComponent, HeaderComponent,
ReactiveFormsModule, ReactiveFormsModule,
InputTextModule, InputTextModule,
InputTextareaModule,
FileUploadModule, FileUploadModule,
EditorModule, EditorModule,
PostFormComponent, PostFormComponent,
@ -49,7 +47,10 @@ export class NewPostComponent implements OnDestroy {
category: ['', [Validators.required, Validators.maxLength(50)]], category: ['', [Validators.required, Validators.maxLength(50)]],
}); });
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) {
this.actualAuthor = this.authService.getAuthenticatedAuthor(); const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
if (authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
}
} else { } else {
this.authService.checkSessionExpiration(); this.authService.checkSessionExpiration();
} }
@ -72,12 +73,14 @@ export class NewPostComponent implements OnDestroy {
category: formData.category as string category: formData.category as string
}; };
const authenticatedAuthor = this.authService.getAuthenticatedAuthorToken();
if (authenticatedAuthor) {
this.subs.push( this.subs.push(
this.postService.createPost(postToPost, this.authService.getAuthenticatedAuthorToken()).pipe( this.postService.createPost(postToPost, authenticatedAuthor).pipe(
mergeMap(post => mergeMap(post =>
this.authorService.attributePost(this.actualAuthor?.id, post.id, this.authService.getAuthenticatedAuthorToken()).pipe( this.authorService.attributePost(this.actualAuthor?.id, post.id, authenticatedAuthor).pipe(
mergeMap((_) => mergeMap((_) =>
this.postService.changeIllustration(post.id, this.uploadedFile, this.authService.getAuthenticatedAuthorToken()), this.postService.changeIllustration(post.id, this.uploadedFile, authenticatedAuthor),
) )
) )
) )
@ -92,6 +95,9 @@ export class NewPostComponent implements OnDestroy {
} }
}) })
); );
} else {
console.error("Profil mal chargé")
}
} }
} }

View File

@ -49,7 +49,12 @@ export class PostComponent {
private authService: AuthService,) { private authService: AuthService,) {
this.route.paramMap.subscribe(params => { this.route.paramMap.subscribe(params => {
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) {
this.actualAuthor = this.authService.getAuthenticatedAuthor(); const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
if (authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
} else {
console.error('Profil mal chargé');
}
} else { } else {
this.authService.checkSessionExpiration(); this.authService.checkSessionExpiration();
} }

View File

@ -43,7 +43,12 @@ export class ProfileComponent implements OnDestroy {
})); }));
}) })
if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) { if (!(this.authService.isSessionExpired()) && this.authService.isAuthenticated()) {
this.actualAuthor = this.authService.getAuthenticatedAuthor(); const authenticatedAuthor = this.authService.getAuthenticatedAuthor();
if (authenticatedAuthor) {
this.actualAuthor = authenticatedAuthor;
} else {
console.error("Profil mal chargé");
}
} else { } else {
this.authService.checkSessionExpiration(); this.authService.checkSessionExpiration();
} }

21
src/app/preset.ts Normal file
View File

@ -0,0 +1,21 @@
import {definePreset} from '@primeng/themes';
import Aura from '@primeng/themes/aura';
export const myPreset = definePreset(Aura, {
semantic: {
primary: {
50: '{indigo.50}',
100: '{indigo.100}',
200: '{indigo.200}',
300: '{indigo.300}',
400: '{indigo.400}',
500: '{indigo.500}',
600: '{indigo.600}',
700: '{indigo.700}',
800: '{indigo.800}',
900: '{indigo.900}',
950: '{indigo.950}'
}
}
});

View File

@ -7,9 +7,11 @@
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="icon.jpg"> <link rel="icon" type="image/x-icon" href="icon.jpg">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head> </head>
<body> <body class="mat-typography">
<app-root></app-root> <app-root></app-root>
<footer class="footer"> <footer class="footer">
<p class="footer-creator">Site web réalisé par <strong>Guams</strong>.</p> <p class="footer-creator">Site web réalisé par <strong>Guams</strong>.</p>

View File

@ -1,4 +1,4 @@
@import '../node_modules/primeng/resources/themes/lara-light-indigo/theme.css'; /*@import '../node_modules/primeng/resources/themes/lara-light-indigo/theme.css';*/
@import '../node_modules/primeicons/primeicons.css'; @import '../node_modules/primeicons/primeicons.css';
@import '../node_modules/quill/dist/quill.bubble.css'; @import '../node_modules/quill/dist/quill.bubble.css';
@import '../node_modules/quill/dist/quill.snow.css'; @import '../node_modules/quill/dist/quill.snow.css';
@ -53,3 +53,6 @@ app-root {
.footer-link:hover { .footer-link:hover {
text-decoration: underline; text-decoration: underline;
} }
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }