Skip to content

Commit 39c96f0

Browse files
authored
FE - Enhance authenticated routings (#20)
1 parent fcc86a4 commit 39c96f0

16 files changed

+152
-46
lines changed

frontend/src/app/app-routing.module.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import { ArticleComponent } from "./pages/article/article.component";
99
import { ProfileComponent } from "./pages/profile/profile.component";
1010
import { FeedMenuEnum } from "./common/models/view/feed.view-model";
1111
import { ProfileRoutingData } from "./common/models/view/profile-routing-data.model";
12+
import { authenticationGuard } from "./common/guards/authentication.guard";
1213

1314
const routes: Routes = [
1415
{ path: '', component: HomeComponent },
1516
{ path: 'login', component: LoginComponent },
1617
{ path: 'register', component: RegisterComponent },
17-
{ path: 'settings', component: UserSettingsComponent },
18-
{ path: 'editor', children: [
18+
{ path: 'settings', component: UserSettingsComponent, canActivate: [authenticationGuard] },
19+
{
20+
path: 'editor',
21+
canActivateChild: [authenticationGuard],
22+
children: [
1923
{ path: '', component: EditorComponent },
2024
{ path: ':slug', component: EditorComponent }
2125
]
@@ -37,6 +41,7 @@ const routes: Routes = [
3741
},
3842
{
3943
path: 'my-profile',
44+
canActivate: [authenticationGuard],
4045
children: [
4146
{
4247
path: '', component: ProfileComponent,
@@ -49,7 +54,7 @@ const routes: Routes = [
4954
],
5055

5156
},
52-
{ path: '**', redirectTo: ''}
57+
{ path: '**', redirectTo: '/' }
5358
];
5459

5560
@NgModule({
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { CanActivateFn } from '@angular/router';
3+
4+
import { authenticationGuard } from './authentication.guard';
5+
6+
describe('authenticationGuard', () => {
7+
const executeGuard: CanActivateFn = (...guardParameters) =>
8+
TestBed.runInInjectionContext(() => authenticationGuard(...guardParameters));
9+
10+
beforeEach(() => {
11+
TestBed.configureTestingModule({});
12+
});
13+
14+
it('should be created', () => {
15+
expect(executeGuard).toBeTruthy();
16+
});
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
2+
import { inject } from "@angular/core";
3+
import { AuthenticationService } from "../services/utils/authentication.service";
4+
5+
export const authenticationGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
6+
const authService = inject(AuthenticationService);
7+
const router = inject(Router);
8+
if (authService.currentUserToken) {
9+
return true;
10+
} else {
11+
return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } })
12+
}
13+
};
14+
15+
export const constructLoginUrlTree: (router: Router) => UrlTree = (router) => {
16+
return router.createUrlTree(['/login'], { queryParams: { returnUrl: router.url } });
17+
}

frontend/src/app/pages/article/article-comment/article-comments.component.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
<div class="row">
22
<div class="col-xs-12 col-md-8 offset-md-2">
3-
<form class="card comment-form">
3+
<form class="card comment-form" *ngIf="currentUser">
44
<div class="card-block">
55
<textarea [formControl]="newCommentControl" class="form-control" placeholder="Write a comment..." rows="3">
66
</textarea>
77
</div>
88
<div class="card-footer">
9-
<img [src]="currentUser?.image || DEFAULT_PROFILE_IMAGE" class="comment-author-img"/>
9+
<img [src]="currentUser.image || DEFAULT_PROFILE_IMAGE" class="comment-author-img"/>
1010
<button type="button" class="btn btn-sm btn-primary" (click)="addComment()">Post Comment</button>
1111
</div>
1212
</form>
1313

14+
<ng-container *ngIf="!currentUser">
15+
<p>
16+
<a routerLink="/login" [queryParams]="loginUrlTree.queryParams">Sign in</a>
17+
or
18+
<a routerLink="/register">sign up</a>
19+
to add comments on this article.
20+
</p>
21+
</ng-container>
22+
1423
<ng-container *ngIf="comments">
1524
<ng-container *ngFor="let comment of comments" [ngTemplateOutlet]="singleComment"
1625
[ngTemplateOutletContext]="{ $implicit: comment }"></ng-container>

frontend/src/app/pages/article/article-comment/article-comments.component.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Component } from '@angular/core';
22
import { ArticleService } from "../../../common/services/api/article.service";
33
import { ArticleComment } from "../../../common/models/api/comment.model";
4-
import { ActivatedRoute } from "@angular/router";
4+
import { ActivatedRoute, Router, UrlTree } from "@angular/router";
55
import { AuthenticationService } from "../../../common/services/utils/authentication.service";
66
import { FormControl } from "@angular/forms";
77
import { DEFAULT_PROFILE_IMAGE } from "../../../common/constants/default.constant";
88
import { User } from "../../../common/models/api/user.model";
9+
import { constructLoginUrlTree } from "../../../common/guards/authentication.guard";
910

1011
@Component({
1112
selector: 'app-article-comments',
@@ -17,15 +18,17 @@ export class ArticleCommentsComponent {
1718
public currentUser?: User;
1819
public comments: ArticleComment[] = [];
1920
public newCommentControl = new FormControl('');
20-
2121
private _articleSlug: string;
22+
public loginUrlTree: UrlTree;
2223

2324
constructor(
2425
private readonly _activatedRoute: ActivatedRoute,
2526
private readonly _articleService: ArticleService,
26-
private readonly _authService: AuthenticationService
27+
private readonly _authService: AuthenticationService,
28+
private readonly _router: Router
2729
) {
2830
this._articleSlug = this._activatedRoute.snapshot.params['slug'];
31+
this.loginUrlTree = constructLoginUrlTree(_router);
2932
this._loadComments();
3033
this._getCurrentUser();
3134
}

frontend/src/app/pages/article/article.component.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ArticleService } from "../../common/services/api/article.service";
44
import { ActivatedRoute, Router } from "@angular/router";
55
import { AuthenticationService } from "../../common/services/utils/authentication.service";
66
import { ProfileService } from "../../common/services/api/profile.service";
7+
import { constructLoginUrlTree } from "../../common/guards/authentication.guard";
78

89
@Component({
910
selector: 'app-article',
@@ -52,12 +53,22 @@ export class ArticleComponent {
5253
if (!this.article) return;
5354

5455
if (favorited) {
55-
this._articleService.favoriteArticle(this.article.slug).subscribe(response => {
56-
this.article = response.article;
56+
this._articleService.favoriteArticle(this.article.slug).subscribe({
57+
next: response => {
58+
this.article = response.article;
59+
},
60+
error: () => {
61+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
62+
}
5763
});
5864
} else {
59-
this._articleService.unfavoriteArticle(this.article.slug).subscribe(response => {
60-
this.article = response.article;
65+
this._articleService.unfavoriteArticle(this.article.slug).subscribe({
66+
next: response => {
67+
this.article = response.article;
68+
},
69+
error: () => {
70+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
71+
}
6172
});
6273
}
6374
}
@@ -66,12 +77,22 @@ export class ArticleComponent {
6677
if (!this.article) return;
6778

6879
if (followed) {
69-
this._profileService.followUser(this.article.author.username).subscribe(response => {
70-
this.article!.author = response?.profile;
80+
this._profileService.followUser(this.article.author.username).subscribe({
81+
next: response => {
82+
this.article!.author = response.profile;
83+
},
84+
error: () => {
85+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
86+
}
7187
});
7288
} else {
73-
this._profileService.unfollowUser(this.article.author.username).subscribe(response => {
74-
this.article!.author = response?.profile;
89+
this._profileService.unfollowUser(this.article.author.username).subscribe({
90+
next: response => {
91+
this.article!.author = response.profile;
92+
},
93+
error: () => {
94+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
95+
}
7596
});
7697
}
7798
}

frontend/src/app/pages/home/feed/feed.component.ts renamed to frontend/src/app/pages/article/feed/articles-feed.component.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { ArticleService } from "../../../common/services/api/article.service";
44
import { Article, ArticlesResponse, QueryArticlesParams } from "../../../common/models/api/article.model";
55
import { Observable } from "rxjs";
66
import { QUERY_PAGE_SIZE } from "../../../common/constants/default.constant";
7+
import { Router } from "@angular/router";
8+
import { constructLoginUrlTree } from "../../../common/guards/authentication.guard";
79

810
@Component({
911
selector: 'app-feed',
10-
templateUrl: './feed.component.html',
11-
styleUrl: './feed.component.scss'
12+
templateUrl: './articles-feed.component.html',
13+
styleUrl: './articles-feed.component.scss'
1214
})
13-
export class FeedComponent implements OnChanges {
15+
export class ArticlesFeedComponent implements OnChanges {
1416
@Input() feedMenuId?: FeedMenuEnum;
1517
@Input() queryParams?: QueryArticlesParams = {};
1618

@@ -19,7 +21,8 @@ export class FeedComponent implements OnChanges {
1921
public totalPages = 0;
2022

2123
constructor(
22-
private readonly _articleService: ArticleService
24+
private readonly _articleService: ArticleService,
25+
private readonly _router: Router
2326
) {
2427
}
2528

@@ -67,12 +70,22 @@ export class FeedComponent implements OnChanges {
6770
if (!this.articles) return;
6871

6972
if (article.favorited) {
70-
this._articleService.unfavoriteArticle(article.slug).subscribe(response => {
71-
this._setSingleArticle(response.article);
73+
this._articleService.unfavoriteArticle(article.slug).subscribe({
74+
next: (response) => {
75+
this._setSingleArticle(response.article);
76+
},
77+
error: () => {
78+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
79+
}
7280
});
7381
} else {
74-
this._articleService.favoriteArticle(article.slug).subscribe(response => {
75-
this._setSingleArticle(response.article);
82+
this._articleService.favoriteArticle(article.slug).subscribe({
83+
next: (response) => {
84+
this._setSingleArticle(response.article);
85+
},
86+
error: () => {
87+
this._router.navigateByUrl(constructLoginUrlTree(this._router));
88+
}
7689
});
7790
}
7891
}

0 commit comments

Comments
 (0)