diff options
Diffstat (limited to 'client/src/app')
9 files changed, 78 insertions, 55 deletions
diff --git a/client/src/app/+page-not-found/page-not-found.component.ts b/client/src/app/+page-not-found/page-not-found.component.ts index 94b4c8d27..695568898 100644 --- a/client/src/app/+page-not-found/page-not-found.component.ts +++ b/client/src/app/+page-not-found/page-not-found.component.ts | |||
@@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser' | |||
3 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
4 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | 4 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
5 | 5 | ||
6 | |||
6 | @Component({ | 7 | @Component({ |
7 | selector: 'my-page-not-found', | 8 | selector: 'my-page-not-found', |
8 | templateUrl: './page-not-found.component.html', | 9 | templateUrl: './page-not-found.component.html', |
@@ -10,7 +11,7 @@ import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | |||
10 | }) | 11 | }) |
11 | export class PageNotFoundComponent implements OnInit { | 12 | export class PageNotFoundComponent implements OnInit { |
12 | status = HttpStatusCode.NOT_FOUND_404 | 13 | status = HttpStatusCode.NOT_FOUND_404 |
13 | type: string | 14 | type: 'video' | 'other' = 'other' |
14 | 15 | ||
15 | public constructor ( | 16 | public constructor ( |
16 | private titleService: Title, | 17 | private titleService: Title, |
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 4619c4046..444b6f134 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts | |||
@@ -5,7 +5,8 @@ import { MenuGuards } from '@app/core/routing/menu-guard.service' | |||
5 | import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n' | 5 | import { POSSIBLE_LOCALES } from '@shared/core-utils/i18n' |
6 | import { MetaGuard, PreloadSelectedModulesList } from './core' | 6 | import { MetaGuard, PreloadSelectedModulesList } from './core' |
7 | import { EmptyComponent } from './empty.component' | 7 | import { EmptyComponent } from './empty.component' |
8 | import { RootComponent } from './root.component' | 8 | import { USER_USERNAME_REGEX_CHARACTERS } from './shared/form-validators/user-validators' |
9 | import { ActorRedirectGuard } from './shared/shared-main' | ||
9 | 10 | ||
10 | const routes: Routes = [ | 11 | const routes: Routes = [ |
11 | { | 12 | { |
@@ -17,7 +18,8 @@ const routes: Routes = [ | |||
17 | }, | 18 | }, |
18 | { | 19 | { |
19 | path: 'home', | 20 | path: 'home', |
20 | loadChildren: () => import('./+home/home.module').then(m => m.HomeModule) | 21 | loadChildren: () => import('./+home/home.module').then(m => m.HomeModule), |
22 | canActivateChild: [ MetaGuard ] | ||
21 | }, | 23 | }, |
22 | { | 24 | { |
23 | path: 'my-account', | 25 | path: 'my-account', |
@@ -94,18 +96,22 @@ const routes: Routes = [ | |||
94 | { | 96 | { |
95 | matcher: (url): UrlMatchResult => { | 97 | matcher: (url): UrlMatchResult => { |
96 | // Matches /@:actorName | 98 | // Matches /@:actorName |
97 | if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) { | 99 | const regex = new RegExp(`^@(${USER_USERNAME_REGEX_CHARACTERS}+)$`) |
98 | return { | 100 | if (url.length !== 1) return null |
99 | consumed: url, | 101 | |
100 | posParams: { | 102 | const matchResult = url[0].path.match(regex) |
101 | actorName: new UrlSegment(url[0].path.substr(1), {}) | 103 | if (!matchResult) return null |
102 | } | 104 | |
105 | return { | ||
106 | consumed: url, | ||
107 | posParams: { | ||
108 | actorName: new UrlSegment(matchResult[1], {}) | ||
103 | } | 109 | } |
104 | } | 110 | } |
105 | |||
106 | return null | ||
107 | }, | 111 | }, |
108 | component: RootComponent | 112 | pathMatch: 'full', |
113 | canActivate: [ ActorRedirectGuard ], | ||
114 | component: EmptyComponent | ||
109 | }, | 115 | }, |
110 | { | 116 | { |
111 | path: '', | 117 | path: '', |
diff --git a/client/src/app/shared/form-validators/user-validators.ts b/client/src/app/shared/form-validators/user-validators.ts index fee37e95f..976c97b87 100644 --- a/client/src/app/shared/form-validators/user-validators.ts +++ b/client/src/app/shared/form-validators/user-validators.ts | |||
@@ -1,12 +1,14 @@ | |||
1 | import { Validators } from '@angular/forms' | 1 | import { Validators } from '@angular/forms' |
2 | import { BuildFormValidator } from './form-validator.model' | 2 | import { BuildFormValidator } from './form-validator.model' |
3 | 3 | ||
4 | export const USER_USERNAME_REGEX_CHARACTERS = '[a-z0-9][a-z0-9._]' | ||
5 | |||
4 | export const USER_USERNAME_VALIDATOR: BuildFormValidator = { | 6 | export const USER_USERNAME_VALIDATOR: BuildFormValidator = { |
5 | VALIDATORS: [ | 7 | VALIDATORS: [ |
6 | Validators.required, | 8 | Validators.required, |
7 | Validators.minLength(1), | 9 | Validators.minLength(1), |
8 | Validators.maxLength(50), | 10 | Validators.maxLength(50), |
9 | Validators.pattern(/^[a-z0-9][a-z0-9._]*$/) | 11 | Validators.pattern(new RegExp(`^${USER_USERNAME_REGEX_CHARACTERS}*$`)) |
10 | ], | 12 | ], |
11 | MESSAGES: { | 13 | MESSAGES: { |
12 | 'required': $localize`Username is required.`, | 14 | 'required': $localize`Username is required.`, |
diff --git a/client/src/app/shared/shared-main/account/actor.service.ts b/client/src/app/shared/shared-main/account/actor.service.ts deleted file mode 100644 index 464ed4519..000000000 --- a/client/src/app/shared/shared-main/account/actor.service.ts +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import { Observable, ReplaySubject } from 'rxjs' | ||
2 | import { catchError, map, tap } from 'rxjs/operators' | ||
3 | import { HttpClient } from '@angular/common/http' | ||
4 | import { Injectable } from '@angular/core' | ||
5 | import { RestExtractor } from '@app/core' | ||
6 | import { Account as ServerAccount, VideoChannel as ServerVideoChannel } from '@shared/models' | ||
7 | import { environment } from '../../../../environments/environment' | ||
8 | |||
9 | type KeysOfUnion<T> = T extends T ? keyof T: never | ||
10 | type ServerActor = KeysOfUnion<ServerAccount | ServerVideoChannel> | ||
11 | |||
12 | @Injectable() | ||
13 | export class ActorService { | ||
14 | static BASE_ACTOR_API_URL = environment.apiUrl + '/api/v1/actors/' | ||
15 | |||
16 | actorLoaded = new ReplaySubject<string>(1) | ||
17 | |||
18 | constructor ( | ||
19 | private authHttp: HttpClient, | ||
20 | private restExtractor: RestExtractor | ||
21 | ) {} | ||
22 | |||
23 | getActorType (actorName: string): Observable<string> { | ||
24 | return this.authHttp.get<ServerActor>(ActorService.BASE_ACTOR_API_URL + actorName) | ||
25 | .pipe( | ||
26 | map(actorHash => { | ||
27 | if (actorHash[ 'userId' ]) { | ||
28 | return 'Account' | ||
29 | } | ||
30 | |||
31 | return 'VideoChannel' | ||
32 | }), | ||
33 | tap(actor => this.actorLoaded.next(actor)), | ||
34 | catchError(res => this.restExtractor.handleError(res)) | ||
35 | ) | ||
36 | } | ||
37 | } | ||
diff --git a/client/src/app/shared/shared-main/account/index.ts b/client/src/app/shared/shared-main/account/index.ts index c6cdcd574..b80ddb9f5 100644 --- a/client/src/app/shared/shared-main/account/index.ts +++ b/client/src/app/shared/shared-main/account/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './account.model' | 1 | export * from './account.model' |
2 | export * from './account.service' | 2 | export * from './account.service' |
3 | export * from './actor.model' | 3 | export * from './actor.model' |
4 | export * from './actor.service' | ||
diff --git a/client/src/app/shared/shared-main/index.ts b/client/src/app/shared/shared-main/index.ts index a4d813c06..3a7fd4c34 100644 --- a/client/src/app/shared/shared-main/index.ts +++ b/client/src/app/shared/shared-main/index.ts | |||
@@ -5,6 +5,9 @@ export * from './date' | |||
5 | export * from './feeds' | 5 | export * from './feeds' |
6 | export * from './loaders' | 6 | export * from './loaders' |
7 | export * from './misc' | 7 | export * from './misc' |
8 | export * from './peertube-modal' | ||
9 | export * from './plugins' | ||
10 | export * from './router' | ||
8 | export * from './users' | 11 | export * from './users' |
9 | export * from './video' | 12 | export * from './video' |
10 | export * from './video-caption' | 13 | export * from './video-caption' |
diff --git a/client/src/app/shared/shared-main/router/actor-redirect-guard.service.ts b/client/src/app/shared/shared-main/router/actor-redirect-guard.service.ts new file mode 100644 index 000000000..49d61f945 --- /dev/null +++ b/client/src/app/shared/shared-main/router/actor-redirect-guard.service.ts | |||
@@ -0,0 +1,46 @@ | |||
1 | import { forkJoin, of } from 'rxjs' | ||
2 | import { catchError, map } from 'rxjs/operators' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router' | ||
5 | import { AccountService } from '../account' | ||
6 | import { VideoChannelService } from '../video-channel' | ||
7 | |||
8 | @Injectable() | ||
9 | export class ActorRedirectGuard implements CanActivate { | ||
10 | |||
11 | constructor ( | ||
12 | private router: Router, | ||
13 | private accountService: AccountService, | ||
14 | private channelService: VideoChannelService | ||
15 | ) {} | ||
16 | |||
17 | canActivate (route: ActivatedRouteSnapshot) { | ||
18 | const actorName = route.params.actorName | ||
19 | |||
20 | return forkJoin([ | ||
21 | this.accountService.getAccount(actorName).pipe(this.orUndefined()), | ||
22 | this.channelService.getVideoChannel(actorName).pipe(this.orUndefined()) | ||
23 | ]).pipe( | ||
24 | map(([ account, channel ]) => { | ||
25 | if (!account && !channel) { | ||
26 | this.router.navigate([ '/404' ]) | ||
27 | return false | ||
28 | } | ||
29 | |||
30 | if (account) { | ||
31 | this.router.navigate([ `/a/${actorName}` ], { skipLocationChange: true }) | ||
32 | } | ||
33 | |||
34 | if (channel) { | ||
35 | this.router.navigate([ `/c/${actorName}` ], { skipLocationChange: true }) | ||
36 | } | ||
37 | |||
38 | return true | ||
39 | }) | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | private orUndefined () { | ||
44 | return catchError(() => of(undefined)) | ||
45 | } | ||
46 | } | ||
diff --git a/client/src/app/shared/shared-main/router/index.ts b/client/src/app/shared/shared-main/router/index.ts new file mode 100644 index 000000000..f4000b674 --- /dev/null +++ b/client/src/app/shared/shared-main/router/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './actor-redirect-guard.service' | |||
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index f06f25ca5..c8dd01429 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts | |||
@@ -4,7 +4,7 @@ import { CommonModule, DatePipe } from '@angular/common' | |||
4 | import { HttpClientModule } from '@angular/common/http' | 4 | import { HttpClientModule } from '@angular/common/http' |
5 | import { NgModule } from '@angular/core' | 5 | import { NgModule } from '@angular/core' |
6 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | 6 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
7 | import { RouterModule } from '@angular/router' | 7 | import { ActivatedRouteSnapshot, RouterModule } from '@angular/router' |
8 | import { | 8 | import { |
9 | NgbButtonsModule, | 9 | NgbButtonsModule, |
10 | NgbCollapseModule, | 10 | NgbCollapseModule, |
@@ -17,7 +17,7 @@ import { | |||
17 | import { LoadingBarModule } from '@ngx-loading-bar/core' | 17 | import { LoadingBarModule } from '@ngx-loading-bar/core' |
18 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' | 18 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' |
19 | import { SharedGlobalIconModule } from '../shared-icons' | 19 | import { SharedGlobalIconModule } from '../shared-icons' |
20 | import { AccountService, ActorService } from './account' | 20 | import { AccountService } from './account' |
21 | import { | 21 | import { |
22 | AutofocusDirective, | 22 | AutofocusDirective, |
23 | BytesPipe, | 23 | BytesPipe, |
@@ -39,6 +39,7 @@ import { UserHistoryService, UserNotificationsComponent, UserNotificationService | |||
39 | import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' | 39 | import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' |
40 | import { VideoCaptionService } from './video-caption' | 40 | import { VideoCaptionService } from './video-caption' |
41 | import { VideoChannelService } from './video-channel' | 41 | import { VideoChannelService } from './video-channel' |
42 | import { ActorRedirectGuard } from './router' | ||
42 | 43 | ||
43 | @NgModule({ | 44 | @NgModule({ |
44 | imports: [ | 45 | imports: [ |
@@ -161,7 +162,6 @@ import { VideoChannelService } from './video-channel' | |||
161 | AUTH_INTERCEPTOR_PROVIDER, | 162 | AUTH_INTERCEPTOR_PROVIDER, |
162 | 163 | ||
163 | AccountService, | 164 | AccountService, |
164 | ActorService, | ||
165 | 165 | ||
166 | UserHistoryService, | 166 | UserHistoryService, |
167 | UserNotificationService, | 167 | UserNotificationService, |
@@ -175,7 +175,9 @@ import { VideoChannelService } from './video-channel' | |||
175 | 175 | ||
176 | VideoChannelService, | 176 | VideoChannelService, |
177 | 177 | ||
178 | CustomPageService | 178 | CustomPageService, |
179 | |||
180 | ActorRedirectGuard | ||
179 | ] | 181 | ] |
180 | }) | 182 | }) |
181 | export class SharedMainModule { } | 183 | export class SharedMainModule { } |