diff options
author | Chocobozzz <me@florianbigard.com> | 2018-04-24 15:10:54 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-04-24 15:13:19 +0200 |
commit | 0626e7af82e02f8a5bd1e74a7d4d8c916d073ceb (patch) | |
tree | 79b5befbc6a04c007e5919805f1514d065b30e11 | |
parent | b4d1af3dd8cdab2d58927e671d62194ca383cd75 (diff) | |
download | PeerTube-0626e7af82e02f8a5bd1e74a7d4d8c916d073ceb.tar.gz PeerTube-0626e7af82e02f8a5bd1e74a7d4d8c916d073ceb.tar.zst PeerTube-0626e7af82e02f8a5bd1e74a7d4d8c916d073ceb.zip |
Add account view
33 files changed, 563 insertions, 149 deletions
diff --git a/client/src/app/+account/account-about/account-about.component.html b/client/src/app/+account/account-about/account-about.component.html new file mode 100644 index 000000000..003a8045e --- /dev/null +++ b/client/src/app/+account/account-about/account-about.component.html | |||
@@ -0,0 +1,12 @@ | |||
1 | <div *ngIf="account" class="row"> | ||
2 | <div class="description col-md-6 col-sm-12"> | ||
3 | <div class="small-title">Description</div> | ||
4 | <div class="content">{{ getAccountDescription() }}</div> | ||
5 | </div> | ||
6 | |||
7 | <div class="stats col-md-6 col-sm-12"> | ||
8 | <div class="small-title">Stats</div> | ||
9 | |||
10 | <div class="content">Joined {{ account.createdAt | date }}</div> | ||
11 | </div> | ||
12 | </div> \ No newline at end of file | ||
diff --git a/client/src/app/+account/account-about/account-about.component.scss b/client/src/app/+account/account-about/account-about.component.scss new file mode 100644 index 000000000..b1be7d4ed --- /dev/null +++ b/client/src/app/+account/account-about/account-about.component.scss | |||
@@ -0,0 +1,8 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .small-title { | ||
5 | @include in-content-small-title; | ||
6 | |||
7 | margin-bottom: 20px; | ||
8 | } | ||
diff --git a/client/src/app/+account/account-about/account-about.component.ts b/client/src/app/+account/account-about/account-about.component.ts new file mode 100644 index 000000000..0772b844e --- /dev/null +++ b/client/src/app/+account/account-about/account-about.component.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Location } from '@angular/common' | ||
4 | import { getParameterByName, immutableAssign } from '@app/shared/misc/utils' | ||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import 'rxjs/add/observable/from' | ||
7 | import 'rxjs/add/operator/concatAll' | ||
8 | import { AuthService } from '../../core/auth' | ||
9 | import { ConfirmService } from '../../core/confirm' | ||
10 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
11 | import { VideoService } from '../../shared/video/video.service' | ||
12 | import { Account } from '@app/shared/account/account.model' | ||
13 | import { AccountService } from '@app/shared/account/account.service' | ||
14 | |||
15 | @Component({ | ||
16 | selector: 'my-account-about', | ||
17 | templateUrl: './account-about.component.html', | ||
18 | styleUrls: [ './account-about.component.scss' ] | ||
19 | }) | ||
20 | export class AccountAboutComponent implements OnInit { | ||
21 | private account: Account | ||
22 | |||
23 | constructor ( | ||
24 | protected route: ActivatedRoute, | ||
25 | private accountService: AccountService | ||
26 | ) { } | ||
27 | |||
28 | ngOnInit () { | ||
29 | // Parent get the account for us | ||
30 | this.accountService.accountLoaded | ||
31 | .subscribe(account => this.account = account) | ||
32 | } | ||
33 | |||
34 | getAccountDescription () { | ||
35 | if (this.account.description) return this.account.description | ||
36 | |||
37 | return 'No description' | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/+account/account-routing.module.ts b/client/src/app/+account/account-routing.module.ts new file mode 100644 index 000000000..534102121 --- /dev/null +++ b/client/src/app/+account/account-routing.module.ts | |||
@@ -0,0 +1,45 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { MetaGuard } from '@ngx-meta/core' | ||
4 | import { AccountComponent } from './account.component' | ||
5 | import { AccountVideosComponent } from './account-videos/account-videos.component' | ||
6 | import { AccountAboutComponent } from '@app/+account/account-about/account-about.component' | ||
7 | |||
8 | const accountRoutes: Routes = [ | ||
9 | { | ||
10 | path: ':accountId', | ||
11 | component: AccountComponent, | ||
12 | canActivateChild: [ MetaGuard ], | ||
13 | children: [ | ||
14 | { | ||
15 | path: '', | ||
16 | redirectTo: 'videos', | ||
17 | pathMatch: 'full' | ||
18 | }, | ||
19 | { | ||
20 | path: 'videos', | ||
21 | component: AccountVideosComponent, | ||
22 | data: { | ||
23 | meta: { | ||
24 | title: 'Account videos' | ||
25 | } | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | path: 'about', | ||
30 | component: AccountAboutComponent, | ||
31 | data: { | ||
32 | meta: { | ||
33 | title: 'About account' | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | ] | ||
38 | } | ||
39 | ] | ||
40 | |||
41 | @NgModule({ | ||
42 | imports: [ RouterModule.forChild(accountRoutes) ], | ||
43 | exports: [ RouterModule ] | ||
44 | }) | ||
45 | export class AccountRoutingModule {} | ||
diff --git a/client/src/app/+account/account-videos/account-videos.component.scss b/client/src/app/+account/account-videos/account-videos.component.scss new file mode 100644 index 000000000..2ba85c031 --- /dev/null +++ b/client/src/app/+account/account-videos/account-videos.component.scss | |||
@@ -0,0 +1,3 @@ | |||
1 | .title-page-single { | ||
2 | margin-top: 0; | ||
3 | } \ No newline at end of file | ||
diff --git a/client/src/app/+account/account-videos/account-videos.component.ts b/client/src/app/+account/account-videos/account-videos.component.ts new file mode 100644 index 000000000..6c0f0bb52 --- /dev/null +++ b/client/src/app/+account/account-videos/account-videos.component.ts | |||
@@ -0,0 +1,71 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Location } from '@angular/common' | ||
4 | import { immutableAssign } from '@app/shared/misc/utils' | ||
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import 'rxjs/add/observable/from' | ||
7 | import 'rxjs/add/operator/concatAll' | ||
8 | import { AuthService } from '../../core/auth' | ||
9 | import { ConfirmService } from '../../core/confirm' | ||
10 | import { AbstractVideoList } from '../../shared/video/abstract-video-list' | ||
11 | import { VideoService } from '../../shared/video/video.service' | ||
12 | import { Account } from '@app/shared/account/account.model' | ||
13 | import { AccountService } from '@app/shared/account/account.service' | ||
14 | |||
15 | @Component({ | ||
16 | selector: 'my-account-videos', | ||
17 | templateUrl: '../../shared/video/abstract-video-list.html', | ||
18 | styleUrls: [ | ||
19 | '../../shared/video/abstract-video-list.scss', | ||
20 | './account-videos.component.scss' | ||
21 | ] | ||
22 | }) | ||
23 | export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
24 | titlePage = 'Published videos' | ||
25 | marginContent = false // Disable margin | ||
26 | currentRoute = '/account/videos' | ||
27 | loadOnInit = false | ||
28 | |||
29 | private account: Account | ||
30 | |||
31 | constructor ( | ||
32 | protected router: Router, | ||
33 | protected route: ActivatedRoute, | ||
34 | protected authService: AuthService, | ||
35 | protected notificationsService: NotificationsService, | ||
36 | protected confirmService: ConfirmService, | ||
37 | protected location: Location, | ||
38 | private accountService: AccountService, | ||
39 | private videoService: VideoService | ||
40 | ) { | ||
41 | super() | ||
42 | } | ||
43 | |||
44 | ngOnInit () { | ||
45 | super.ngOnInit() | ||
46 | |||
47 | // Parent get the account for us | ||
48 | this.accountService.accountLoaded | ||
49 | .subscribe(account => { | ||
50 | this.account = account | ||
51 | this.currentRoute = '/account/' + this.account.id + '/videos' | ||
52 | |||
53 | this.loadMoreVideos(this.pagination.currentPage) | ||
54 | this.generateSyndicationList() | ||
55 | }) | ||
56 | } | ||
57 | |||
58 | ngOnDestroy () { | ||
59 | super.ngOnDestroy() | ||
60 | } | ||
61 | |||
62 | getVideosObservable (page: number) { | ||
63 | const newPagination = immutableAssign(this.pagination, { currentPage: page }) | ||
64 | |||
65 | return this.videoService.getAccountVideos(this.account, newPagination, this.sort) | ||
66 | } | ||
67 | |||
68 | generateSyndicationList () { | ||
69 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) | ||
70 | } | ||
71 | } | ||
diff --git a/client/src/app/+account/account.component.html b/client/src/app/+account/account.component.html new file mode 100644 index 000000000..f875b37a4 --- /dev/null +++ b/client/src/app/+account/account.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <div *ngIf="account" class="row"> | ||
2 | <div class="sub-menu"> | ||
3 | |||
4 | <div class="account"> | ||
5 | <img [src]="getAvatarUrl()" alt="Avatar" /> | ||
6 | |||
7 | <div class="account-info"> | ||
8 | <div class="account-display-name">{{ account.displayName }}</div> | ||
9 | <div class="account-followers">{{ account.followersCount }} subscribers</div> | ||
10 | </div> | ||
11 | </div> | ||
12 | |||
13 | <div class="links"> | ||
14 | <a routerLink="videos" routerLinkActive="active" class="title-page">Videos</a> | ||
15 | |||
16 | <a routerLink="about" routerLinkActive="active" class="title-page">About</a> | ||
17 | </div> | ||
18 | </div> | ||
19 | |||
20 | <div class="margin-content"> | ||
21 | <router-outlet></router-outlet> | ||
22 | </div> | ||
23 | </div> | ||
diff --git a/client/src/app/+account/account.component.scss b/client/src/app/+account/account.component.scss new file mode 100644 index 000000000..c7b8f038f --- /dev/null +++ b/client/src/app/+account/account.component.scss | |||
@@ -0,0 +1,46 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .sub-menu { | ||
5 | height: 160px; | ||
6 | display: flex; | ||
7 | flex-direction: column; | ||
8 | align-items: start; | ||
9 | |||
10 | .account { | ||
11 | display: flex; | ||
12 | margin-top: 20px; | ||
13 | margin-bottom: 20px; | ||
14 | |||
15 | img { | ||
16 | @include avatar(80px); | ||
17 | |||
18 | margin-right: 20px; | ||
19 | } | ||
20 | |||
21 | .account-info { | ||
22 | display: flex; | ||
23 | flex-direction: column; | ||
24 | justify-content: center; | ||
25 | |||
26 | .account-display-name { | ||
27 | font-size: 23px; | ||
28 | font-weight: $font-bold; | ||
29 | } | ||
30 | |||
31 | .account-followers { | ||
32 | font-size: 15px; | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
37 | .links { | ||
38 | margin-top: 0; | ||
39 | margin-bottom: 10px; | ||
40 | |||
41 | a { | ||
42 | margin-top: 0; | ||
43 | margin-bottom: 0; | ||
44 | } | ||
45 | } | ||
46 | } \ No newline at end of file | ||
diff --git a/client/src/app/+account/account.component.ts b/client/src/app/+account/account.component.ts new file mode 100644 index 000000000..1c3e528a7 --- /dev/null +++ b/client/src/app/+account/account.component.ts | |||
@@ -0,0 +1,29 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute } from '@angular/router' | ||
3 | import { AccountService } from '@app/shared/account/account.service' | ||
4 | import { Account } from '@app/shared/account/account.model' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-account', | ||
8 | templateUrl: './account.component.html', | ||
9 | styleUrls: [ './account.component.scss' ] | ||
10 | }) | ||
11 | export class AccountComponent implements OnInit { | ||
12 | private account: Account | ||
13 | |||
14 | constructor ( | ||
15 | private route: ActivatedRoute, | ||
16 | private accountService: AccountService | ||
17 | ) {} | ||
18 | |||
19 | ngOnInit () { | ||
20 | const accountId = parseInt(this.route.snapshot.params['accountId'], 10) | ||
21 | |||
22 | this.accountService.getAccount(accountId) | ||
23 | .subscribe(account => this.account = account) | ||
24 | } | ||
25 | |||
26 | getAvatarUrl () { | ||
27 | return Account.GET_ACCOUNT_AVATAR_URL(this.account) | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/+account/account.module.ts b/client/src/app/+account/account.module.ts new file mode 100644 index 000000000..2fe67f3c5 --- /dev/null +++ b/client/src/app/+account/account.module.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { SharedModule } from '../shared' | ||
3 | import { AccountRoutingModule } from './account-routing.module' | ||
4 | import { AccountComponent } from './account.component' | ||
5 | import { AccountVideosComponent } from './account-videos/account-videos.component' | ||
6 | import { AccountAboutComponent } from './account-about/account-about.component' | ||
7 | |||
8 | @NgModule({ | ||
9 | imports: [ | ||
10 | AccountRoutingModule, | ||
11 | SharedModule | ||
12 | ], | ||
13 | |||
14 | declarations: [ | ||
15 | AccountComponent, | ||
16 | AccountVideosComponent, | ||
17 | AccountAboutComponent | ||
18 | ], | ||
19 | |||
20 | exports: [ | ||
21 | AccountComponent | ||
22 | ], | ||
23 | |||
24 | providers: [] | ||
25 | }) | ||
26 | export class AccountModule { } | ||
diff --git a/client/src/app/+account/index.ts b/client/src/app/+account/index.ts new file mode 100644 index 000000000..dc56ffdbd --- /dev/null +++ b/client/src/app/+account/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './account-routing.module' | ||
2 | export * from './account.component' | ||
3 | export * from './account.module' | ||
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 2ee3cf974..1d55b4cea 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts | |||
@@ -7,6 +7,10 @@ const routes: Routes = [ | |||
7 | { | 7 | { |
8 | path: 'admin', | 8 | path: 'admin', |
9 | loadChildren: './+admin/admin.module#AdminModule' | 9 | loadChildren: './+admin/admin.module#AdminModule' |
10 | }, | ||
11 | { | ||
12 | path: 'account', | ||
13 | loadChildren: './+account/account.module#AccountModule' | ||
10 | } | 14 | } |
11 | ] | 15 | ] |
12 | 16 | ||
diff --git a/client/src/app/my-account/my-account-settings/my-account-settings.component.scss b/client/src/app/my-account/my-account-settings/my-account-settings.component.scss index 1cc00ca49..85079d620 100644 --- a/client/src/app/my-account/my-account-settings/my-account-settings.component.scss +++ b/client/src/app/my-account/my-account-settings/my-account-settings.component.scss | |||
@@ -47,10 +47,8 @@ | |||
47 | } | 47 | } |
48 | 48 | ||
49 | .account-title { | 49 | .account-title { |
50 | text-transform: uppercase; | 50 | @include in-content-small-title; |
51 | color: $orange-color; | 51 | |
52 | font-weight: $font-bold; | ||
53 | font-size: 13px; | ||
54 | margin-top: 55px; | 52 | margin-top: 55px; |
55 | margin-bottom: 30px; | 53 | margin-bottom: 30px; |
56 | } | 54 | } |
diff --git a/client/src/app/my-account/my-account.component.ts b/client/src/app/my-account/my-account.component.ts index 0955e2b7b..7bb461d3c 100644 --- a/client/src/app/my-account/my-account.component.ts +++ b/client/src/app/my-account/my-account.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component } from '@angular/core' |
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-account', | 4 | selector: 'my-my-account', |
5 | templateUrl: './my-account.component.html' | 5 | templateUrl: './my-account.component.html' |
6 | }) | 6 | }) |
7 | export class MyAccountComponent {} | 7 | export class MyAccountComponent {} |
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts index 0bdc76478..3d5176bdd 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/account/account.model.ts | |||
@@ -16,6 +16,21 @@ export class Account implements ServerAccount { | |||
16 | updatedAt: Date | 16 | updatedAt: Date |
17 | avatar: Avatar | 17 | avatar: Avatar |
18 | 18 | ||
19 | constructor (hash: ServerAccount) { | ||
20 | this.id = hash.id | ||
21 | this.uuid = hash.uuid | ||
22 | this.url = hash.url | ||
23 | this.name = hash.name | ||
24 | this.displayName = hash.displayName | ||
25 | this.description = hash.description | ||
26 | this.host = hash.host | ||
27 | this.followingCount = hash.followingCount | ||
28 | this.followersCount = hash.followersCount | ||
29 | this.createdAt = new Date(hash.createdAt.toString()) | ||
30 | this.updatedAt = new Date(hash.updatedAt.toString()) | ||
31 | this.avatar = hash.avatar | ||
32 | } | ||
33 | |||
19 | static GET_ACCOUNT_AVATAR_URL (account: Account) { | 34 | static GET_ACCOUNT_AVATAR_URL (account: Account) { |
20 | const absoluteAPIUrl = getAbsoluteAPIUrl() | 35 | const absoluteAPIUrl = getAbsoluteAPIUrl() |
21 | 36 | ||
diff --git a/client/src/app/shared/account/account.service.ts b/client/src/app/shared/account/account.service.ts new file mode 100644 index 000000000..8c66ae04a --- /dev/null +++ b/client/src/app/shared/account/account.service.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import 'rxjs/add/operator/catch' | ||
3 | import 'rxjs/add/operator/map' | ||
4 | import { environment } from '../../../environments/environment' | ||
5 | import { Observable } from 'rxjs/Observable' | ||
6 | import { Account } from '@app/shared/account/account.model' | ||
7 | import { RestExtractor } from '@app/shared/rest/rest-extractor.service' | ||
8 | import { RestService } from '@app/shared/rest/rest.service' | ||
9 | import { HttpClient } from '@angular/common/http' | ||
10 | import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' | ||
11 | import { ReplaySubject } from 'rxjs/ReplaySubject' | ||
12 | |||
13 | @Injectable() | ||
14 | export class AccountService { | ||
15 | static BASE_ACCOUNT_URL = environment.apiUrl + '/api/v1/accounts/' | ||
16 | |||
17 | accountLoaded = new ReplaySubject<Account>(1) | ||
18 | |||
19 | constructor ( | ||
20 | private authHttp: HttpClient, | ||
21 | private restExtractor: RestExtractor, | ||
22 | private restService: RestService | ||
23 | ) {} | ||
24 | |||
25 | getAccount (id: number): Observable<Account> { | ||
26 | return this.authHttp.get<ServerAccount>(AccountService.BASE_ACCOUNT_URL + id) | ||
27 | .map(accountHash => new Account(accountHash)) | ||
28 | .do(account => this.accountLoaded.next(account)) | ||
29 | .catch((res) => this.restExtractor.handleError(res)) | ||
30 | } | ||
31 | } | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 74730e2aa..2178eebc8 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -31,6 +31,7 @@ import { VideoMiniatureComponent } from './video/video-miniature.component' | |||
31 | import { VideoFeedComponent } from './video/video-feed.component' | 31 | import { VideoFeedComponent } from './video/video-feed.component' |
32 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' | 32 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' |
33 | import { VideoService } from './video/video.service' | 33 | import { VideoService } from './video/video.service' |
34 | import { AccountService } from '@app/shared/account/account.service' | ||
34 | 35 | ||
35 | @NgModule({ | 36 | @NgModule({ |
36 | imports: [ | 37 | imports: [ |
@@ -104,6 +105,7 @@ import { VideoService } from './video/video.service' | |||
104 | VideoBlacklistService, | 105 | VideoBlacklistService, |
105 | UserService, | 106 | UserService, |
106 | VideoService, | 107 | VideoService, |
108 | AccountService, | ||
107 | MarkdownService | 109 | MarkdownService |
108 | ] | 110 | ] |
109 | }) | 111 | }) |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index cb04e07b4..690529dcf 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="margin-content"> | 1 | <div [ngClass]="{ 'margin-content': marginContent }"> |
2 | <div class="title-page title-page-single"> | 2 | <div *ngIf="titlePage" class="title-page title-page-single"> |
3 | {{ titlePage }} | 3 | {{ titlePage }} |
4 | </div> | 4 | </div> |
5 | <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> | 5 | <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index 728c864e9..642a85f65 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -29,6 +29,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
29 | syndicationItems = [] | 29 | syndicationItems = [] |
30 | 30 | ||
31 | loadOnInit = true | 31 | loadOnInit = true |
32 | marginContent = true | ||
32 | pageHeight: number | 33 | pageHeight: number |
33 | videoWidth: number | 34 | videoWidth: number |
34 | videoHeight: number | 35 | videoHeight: number |
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index ef8babd55..f82aa7389 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -21,6 +21,8 @@ import { VideoDetails } from './video-details.model' | |||
21 | import { VideoEdit } from './video-edit.model' | 21 | import { VideoEdit } from './video-edit.model' |
22 | import { Video } from './video.model' | 22 | import { Video } from './video.model' |
23 | import { objectToFormData } from '@app/shared/misc/utils' | 23 | import { objectToFormData } from '@app/shared/misc/utils' |
24 | import { Account } from '@app/shared/account/account.model' | ||
25 | import { AccountService } from '@app/shared/account/account.service' | ||
24 | 26 | ||
25 | @Injectable() | 27 | @Injectable() |
26 | export class VideoService { | 28 | export class VideoService { |
@@ -97,6 +99,22 @@ export class VideoService { | |||
97 | .catch((res) => this.restExtractor.handleError(res)) | 99 | .catch((res) => this.restExtractor.handleError(res)) |
98 | } | 100 | } |
99 | 101 | ||
102 | getAccountVideos ( | ||
103 | account: Account, | ||
104 | videoPagination: ComponentPagination, | ||
105 | sort: VideoSortField | ||
106 | ): Observable<{ videos: Video[], totalVideos: number}> { | ||
107 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | ||
108 | |||
109 | let params = new HttpParams() | ||
110 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
111 | |||
112 | return this.authHttp | ||
113 | .get(AccountService.BASE_ACCOUNT_URL + account.id + '/videos', { params }) | ||
114 | .map(this.extractVideos) | ||
115 | .catch((res) => this.restExtractor.handleError(res)) | ||
116 | } | ||
117 | |||
100 | getVideos ( | 118 | getVideos ( |
101 | videoPagination: ComponentPagination, | 119 | videoPagination: ComponentPagination, |
102 | sort: VideoSortField, | 120 | sort: VideoSortField, |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 91e590094..abda5043e 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -22,12 +22,10 @@ | |||
22 | </div> | 22 | </div> |
23 | 23 | ||
24 | <div class="video-info-by"> | 24 | <div class="video-info-by"> |
25 | <a [routerLink]="[ '/videos', 'search' ]" [queryParams]="{ search: video.account.name }" title="Search videos of this account"> | 25 | <a [routerLink]="[ '/account', video.account.id ]" title="Go the account page"> |
26 | By {{ video.by }} | 26 | By {{ video.by }} |
27 | <img [src]="getAvatarPath()" alt="Account avatar" /> | 27 | <img [src]="getAvatarPath()" alt="Account avatar" /> |
28 | </a> | 28 | </a> |
29 | |||
30 | <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> | ||
31 | </div> | 29 | </div> |
32 | </div> | 30 | </div> |
33 | 31 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 6f6f02378..4b0c49583 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -39,8 +39,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
39 | 39 | ||
40 | otherVideosDisplayed: Video[] = [] | 40 | otherVideosDisplayed: Video[] = [] |
41 | 41 | ||
42 | syndicationItems = {} | ||
43 | |||
44 | player: videojs.Player | 42 | player: videojs.Player |
45 | playerElement: HTMLVideoElement | 43 | playerElement: HTMLVideoElement |
46 | userRating: UserVideoRateType = null | 44 | userRating: UserVideoRateType = null |
@@ -110,7 +108,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
110 | const startTime = this.route.snapshot.queryParams.start | 108 | const startTime = this.route.snapshot.queryParams.start |
111 | this.onVideoFetched(video, startTime) | 109 | this.onVideoFetched(video, startTime) |
112 | .catch(err => this.handleError(err)) | 110 | .catch(err => this.handleError(err)) |
113 | this.generateSyndicationList() | ||
114 | }, | 111 | }, |
115 | 112 | ||
116 | error => { | 113 | error => { |
@@ -247,10 +244,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
247 | return this.video.tags.join(', ') | 244 | return this.video.tags.join(', ') |
248 | } | 245 | } |
249 | 246 | ||
250 | generateSyndicationList () { | ||
251 | this.syndicationItems = this.videoService.getAccountFeedUrls(this.video.account.id) | ||
252 | } | ||
253 | |||
254 | isVideoRemovable () { | 247 | isVideoRemovable () { |
255 | return this.video.isRemovableBy(this.authService.getUser()) | 248 | return this.video.isRemovableBy(this.authService.getUser()) |
256 | } | 249 | } |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 7e7a38bbd..cbf9b566a 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -314,3 +314,10 @@ | |||
314 | left: 0.25em; | 314 | left: 0.25em; |
315 | transform: rotate(-135deg); | 315 | transform: rotate(-135deg); |
316 | } | 316 | } |
317 | |||
318 | @mixin in-content-small-title { | ||
319 | text-transform: uppercase; | ||
320 | color: $orange-color; | ||
321 | font-weight: $font-bold; | ||
322 | font-size: 13px; | ||
323 | } \ No newline at end of file | ||
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 4dc0cc16d..06ab04033 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,8 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { asyncMiddleware, paginationValidator, setDefaultSort, setDefaultPagination } from '../../middlewares' | 3 | import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares' |
4 | import { accountsGetValidator, accountsSortValidator } from '../../middlewares/validators' | 4 | import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' |
5 | import { AccountModel } from '../../models/account/account' | 5 | import { AccountModel } from '../../models/account/account' |
6 | import { VideoModel } from '../../models/video/video' | ||
7 | import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' | ||
8 | import { isNSFWHidden } from '../../helpers/express-utils' | ||
6 | 9 | ||
7 | const accountsRouter = express.Router() | 10 | const accountsRouter = express.Router() |
8 | 11 | ||
@@ -19,6 +22,16 @@ accountsRouter.get('/:id', | |||
19 | getAccount | 22 | getAccount |
20 | ) | 23 | ) |
21 | 24 | ||
25 | accountsRouter.get('/:id/videos', | ||
26 | asyncMiddleware(accountsGetValidator), | ||
27 | paginationValidator, | ||
28 | videosSortValidator, | ||
29 | setDefaultSort, | ||
30 | setDefaultPagination, | ||
31 | optionalAuthenticate, | ||
32 | asyncMiddleware(getAccountVideos) | ||
33 | ) | ||
34 | |||
22 | // --------------------------------------------------------------------------- | 35 | // --------------------------------------------------------------------------- |
23 | 36 | ||
24 | export { | 37 | export { |
@@ -28,7 +41,9 @@ export { | |||
28 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
29 | 42 | ||
30 | function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { | 43 | function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { |
31 | return res.json(res.locals.account.toFormattedJSON()) | 44 | const account: AccountModel = res.locals.account |
45 | |||
46 | return res.json(account.toFormattedJSON()) | ||
32 | } | 47 | } |
33 | 48 | ||
34 | async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { | 49 | async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -36,3 +51,19 @@ async function listAccounts (req: express.Request, res: express.Response, next: | |||
36 | 51 | ||
37 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 52 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
38 | } | 53 | } |
54 | |||
55 | async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
56 | const account: AccountModel = res.locals.account | ||
57 | |||
58 | const resultList = await VideoModel.listForApi( | ||
59 | req.query.start as number, | ||
60 | req.query.count as number, | ||
61 | req.query.sort as VideoSortField, | ||
62 | isNSFWHidden(res), | ||
63 | null, | ||
64 | false, | ||
65 | account.id | ||
66 | ) | ||
67 | |||
68 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
69 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 3b499f3b7..964d5d04c 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { badRequest } from '../../helpers/utils' | ||
3 | import { configRouter } from './config' | 2 | import { configRouter } from './config' |
4 | import { jobsRouter } from './jobs' | 3 | import { jobsRouter } from './jobs' |
5 | import { oauthClientsRouter } from './oauth-clients' | 4 | import { oauthClientsRouter } from './oauth-clients' |
@@ -7,6 +6,7 @@ import { serverRouter } from './server' | |||
7 | import { usersRouter } from './users' | 6 | import { usersRouter } from './users' |
8 | import { accountsRouter } from './accounts' | 7 | import { accountsRouter } from './accounts' |
9 | import { videosRouter } from './videos' | 8 | import { videosRouter } from './videos' |
9 | import { badRequest } from '../../helpers/express-utils' | ||
10 | 10 | ||
11 | const apiRouter = express.Router() | 11 | const apiRouter = express.Router() |
12 | 12 | ||
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index 6540adb1c..474329b58 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -7,7 +7,7 @@ import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRat | |||
7 | import { retryTransactionWrapper } from '../../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../../helpers/database-utils' |
8 | import { processImage } from '../../helpers/image-utils' | 8 | import { processImage } from '../../helpers/image-utils' |
9 | import { logger } from '../../helpers/logger' | 9 | import { logger } from '../../helpers/logger' |
10 | import { createReqFiles, getFormattedObjects } from '../../helpers/utils' | 10 | import { getFormattedObjects } from '../../helpers/utils' |
11 | import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers' | 11 | import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers' |
12 | import { updateActorAvatarInstance } from '../../lib/activitypub' | 12 | import { updateActorAvatarInstance } from '../../lib/activitypub' |
13 | import { sendUpdateActor } from '../../lib/activitypub/send' | 13 | import { sendUpdateActor } from '../../lib/activitypub/send' |
@@ -43,6 +43,7 @@ import { UserModel } from '../../models/account/user' | |||
43 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' | 43 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' |
44 | import { VideoModel } from '../../models/video/video' | 44 | import { VideoModel } from '../../models/video/video' |
45 | import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' | 45 | import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' |
46 | import { createReqFiles } from '../../helpers/express-utils' | ||
46 | 47 | ||
47 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) | 48 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) |
48 | const loginRateLimiter = new RateLimit({ | 49 | const loginRateLimiter = new RateLimit({ |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 6e8601fa1..61b6c5826 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -6,7 +6,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
6 | import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 6 | import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
7 | import { processImage } from '../../../helpers/image-utils' | 7 | import { processImage } from '../../../helpers/image-utils' |
8 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
9 | import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' | 9 | import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' |
10 | import { | 10 | import { |
11 | CONFIG, | 11 | CONFIG, |
12 | IMAGE_MIMETYPE_EXT, | 12 | IMAGE_MIMETYPE_EXT, |
@@ -19,11 +19,7 @@ import { | |||
19 | VIDEO_MIMETYPE_EXT, | 19 | VIDEO_MIMETYPE_EXT, |
20 | VIDEO_PRIVACIES | 20 | VIDEO_PRIVACIES |
21 | } from '../../../initializers' | 21 | } from '../../../initializers' |
22 | import { | 22 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' |
23 | fetchRemoteVideoDescription, | ||
24 | getVideoActivityPubUrl, | ||
25 | shareVideoByServerAndChannel | ||
26 | } from '../../../lib/activitypub' | ||
27 | import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send' | 23 | import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send' |
28 | import { JobQueue } from '../../../lib/job-queue' | 24 | import { JobQueue } from '../../../lib/job-queue' |
29 | import { Redis } from '../../../lib/redis' | 25 | import { Redis } from '../../../lib/redis' |
@@ -49,9 +45,9 @@ import { blacklistRouter } from './blacklist' | |||
49 | import { videoChannelRouter } from './channel' | 45 | import { videoChannelRouter } from './channel' |
50 | import { videoCommentRouter } from './comment' | 46 | import { videoCommentRouter } from './comment' |
51 | import { rateVideoRouter } from './rate' | 47 | import { rateVideoRouter } from './rate' |
52 | import { User } from '../../../../shared/models/users' | ||
53 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 48 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
54 | import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' | 49 | import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' |
50 | import { isNSFWHidden, createReqFiles } from '../../../helpers/express-utils' | ||
55 | 51 | ||
56 | const videosRouter = express.Router() | 52 | const videosRouter = express.Router() |
57 | 53 | ||
@@ -444,12 +440,3 @@ async function searchVideos (req: express.Request, res: express.Response, next: | |||
444 | 440 | ||
445 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 441 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
446 | } | 442 | } |
447 | |||
448 | function isNSFWHidden (res: express.Response) { | ||
449 | if (res.locals.oauth) { | ||
450 | const user: User = res.locals.oauth.token.User | ||
451 | if (user) return user.nsfwPolicy === 'do_not_list' | ||
452 | } | ||
453 | |||
454 | return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' | ||
455 | } | ||
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 4a4dc3820..6a6af3e09 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -30,29 +30,18 @@ async function generateFeed (req: express.Request, res: express.Response, next: | |||
30 | let feed = initFeed() | 30 | let feed = initFeed() |
31 | const start = 0 | 31 | const start = 0 |
32 | 32 | ||
33 | let resultList: ResultList<VideoModel> | ||
34 | const account: AccountModel = res.locals.account | 33 | const account: AccountModel = res.locals.account |
35 | const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' | 34 | const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' |
36 | 35 | ||
37 | if (account) { | 36 | const resultList = await VideoModel.listForApi( |
38 | resultList = await VideoModel.listAccountVideosForApi( | 37 | start, |
39 | account.id, | 38 | FEEDS.COUNT, |
40 | start, | 39 | req.query.sort as VideoSortField, |
41 | FEEDS.COUNT, | 40 | hideNSFW, |
42 | req.query.sort as VideoSortField, | 41 | req.query.filter, |
43 | hideNSFW, | 42 | true, |
44 | true | 43 | account ? account.id : null |
45 | ) | 44 | ) |
46 | } else { | ||
47 | resultList = await VideoModel.listForApi( | ||
48 | start, | ||
49 | FEEDS.COUNT, | ||
50 | req.query.sort as VideoSortField, | ||
51 | hideNSFW, | ||
52 | req.query.filter, | ||
53 | true | ||
54 | ) | ||
55 | } | ||
56 | 45 | ||
57 | // Adding video items to the feed, one at a time | 46 | // Adding video items to the feed, one at a time |
58 | resultList.data.forEach(video => { | 47 | resultList.data.forEach(video => { |
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts new file mode 100644 index 000000000..d023117a8 --- /dev/null +++ b/server/helpers/express-utils.ts | |||
@@ -0,0 +1,77 @@ | |||
1 | import * as express from 'express' | ||
2 | import * as multer from 'multer' | ||
3 | import { CONFIG, REMOTE_SCHEME } from '../initializers' | ||
4 | import { logger } from './logger' | ||
5 | import { User } from '../../shared/models/users' | ||
6 | import { generateRandomString } from './utils' | ||
7 | |||
8 | function isNSFWHidden (res: express.Response) { | ||
9 | if (res.locals.oauth) { | ||
10 | const user: User = res.locals.oauth.token.User | ||
11 | if (user) return user.nsfwPolicy === 'do_not_list' | ||
12 | } | ||
13 | |||
14 | return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' | ||
15 | } | ||
16 | |||
17 | function getHostWithPort (host: string) { | ||
18 | const splitted = host.split(':') | ||
19 | |||
20 | // The port was not specified | ||
21 | if (splitted.length === 1) { | ||
22 | if (REMOTE_SCHEME.HTTP === 'https') return host + ':443' | ||
23 | |||
24 | return host + ':80' | ||
25 | } | ||
26 | |||
27 | return host | ||
28 | } | ||
29 | |||
30 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
31 | return res.type('json').status(400).end() | ||
32 | } | ||
33 | |||
34 | function createReqFiles ( | ||
35 | fieldNames: string[], | ||
36 | mimeTypes: { [ id: string ]: string }, | ||
37 | destinations: { [ fieldName: string ]: string } | ||
38 | ) { | ||
39 | const storage = multer.diskStorage({ | ||
40 | destination: (req, file, cb) => { | ||
41 | cb(null, destinations[ file.fieldname ]) | ||
42 | }, | ||
43 | |||
44 | filename: async (req, file, cb) => { | ||
45 | const extension = mimeTypes[ file.mimetype ] | ||
46 | let randomString = '' | ||
47 | |||
48 | try { | ||
49 | randomString = await generateRandomString(16) | ||
50 | } catch (err) { | ||
51 | logger.error('Cannot generate random string for file name.', { err }) | ||
52 | randomString = 'fake-random-string' | ||
53 | } | ||
54 | |||
55 | cb(null, randomString + extension) | ||
56 | } | ||
57 | }) | ||
58 | |||
59 | const fields = [] | ||
60 | for (const fieldName of fieldNames) { | ||
61 | fields.push({ | ||
62 | name: fieldName, | ||
63 | maxCount: 1 | ||
64 | }) | ||
65 | } | ||
66 | |||
67 | return multer({ storage }).fields(fields) | ||
68 | } | ||
69 | |||
70 | // --------------------------------------------------------------------------- | ||
71 | |||
72 | export { | ||
73 | isNSFWHidden, | ||
74 | getHostWithPort, | ||
75 | badRequest, | ||
76 | createReqFiles | ||
77 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index c58117219..058c3211e 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,68 +1,13 @@ | |||
1 | import * as express from 'express' | ||
2 | import * as multer from 'multer' | ||
3 | import { Model } from 'sequelize-typescript' | 1 | import { Model } from 'sequelize-typescript' |
4 | import { ResultList } from '../../shared' | 2 | import { ResultList } from '../../shared' |
5 | import { VideoResolution } from '../../shared/models/videos' | 3 | import { VideoResolution } from '../../shared/models/videos' |
6 | import { CONFIG, REMOTE_SCHEME } from '../initializers' | 4 | import { CONFIG } from '../initializers' |
7 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
8 | import { ActorModel } from '../models/activitypub/actor' | 6 | import { ActorModel } from '../models/activitypub/actor' |
9 | import { ApplicationModel } from '../models/application/application' | 7 | import { ApplicationModel } from '../models/application/application' |
10 | import { pseudoRandomBytesPromise } from './core-utils' | 8 | import { pseudoRandomBytesPromise } from './core-utils' |
11 | import { logger } from './logger' | 9 | import { logger } from './logger' |
12 | 10 | ||
13 | function getHostWithPort (host: string) { | ||
14 | const splitted = host.split(':') | ||
15 | |||
16 | // The port was not specified | ||
17 | if (splitted.length === 1) { | ||
18 | if (REMOTE_SCHEME.HTTP === 'https') return host + ':443' | ||
19 | |||
20 | return host + ':80' | ||
21 | } | ||
22 | |||
23 | return host | ||
24 | } | ||
25 | |||
26 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
27 | return res.type('json').status(400).end() | ||
28 | } | ||
29 | |||
30 | function createReqFiles ( | ||
31 | fieldNames: string[], | ||
32 | mimeTypes: { [ id: string ]: string }, | ||
33 | destinations: { [ fieldName: string ]: string } | ||
34 | ) { | ||
35 | const storage = multer.diskStorage({ | ||
36 | destination: (req, file, cb) => { | ||
37 | cb(null, destinations[file.fieldname]) | ||
38 | }, | ||
39 | |||
40 | filename: async (req, file, cb) => { | ||
41 | const extension = mimeTypes[file.mimetype] | ||
42 | let randomString = '' | ||
43 | |||
44 | try { | ||
45 | randomString = await generateRandomString(16) | ||
46 | } catch (err) { | ||
47 | logger.error('Cannot generate random string for file name.', { err }) | ||
48 | randomString = 'fake-random-string' | ||
49 | } | ||
50 | |||
51 | cb(null, randomString + extension) | ||
52 | } | ||
53 | }) | ||
54 | |||
55 | const fields = [] | ||
56 | for (const fieldName of fieldNames) { | ||
57 | fields.push({ | ||
58 | name: fieldName, | ||
59 | maxCount: 1 | ||
60 | }) | ||
61 | } | ||
62 | |||
63 | return multer({ storage }).fields(fields) | ||
64 | } | ||
65 | |||
66 | async function generateRandomString (size: number) { | 11 | async function generateRandomString (size: number) { |
67 | const raw = await pseudoRandomBytesPromise(size) | 12 | const raw = await pseudoRandomBytesPromise(size) |
68 | 13 | ||
@@ -151,14 +96,11 @@ type SortType = { sortModel: any, sortValue: string } | |||
151 | // --------------------------------------------------------------------------- | 96 | // --------------------------------------------------------------------------- |
152 | 97 | ||
153 | export { | 98 | export { |
154 | badRequest, | ||
155 | generateRandomString, | 99 | generateRandomString, |
156 | getFormattedObjects, | 100 | getFormattedObjects, |
157 | isSignupAllowed, | 101 | isSignupAllowed, |
158 | computeResolutionsToTranscode, | 102 | computeResolutionsToTranscode, |
159 | resetSequelizeInstance, | 103 | resetSequelizeInstance, |
160 | getServerActor, | 104 | getServerActor, |
161 | SortType, | 105 | SortType |
162 | getHostWithPort, | ||
163 | createReqFiles | ||
164 | } | 106 | } |
diff --git a/server/middlewares/servers.ts b/server/middlewares/servers.ts index a9dcad2d4..c52f4685b 100644 --- a/server/middlewares/servers.ts +++ b/server/middlewares/servers.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { getHostWithPort } from '../helpers/utils' | 3 | import { getHostWithPort } from '../helpers/express-utils' |
4 | 4 | ||
5 | function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) { | 5 | function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) { |
6 | if (!req.body.hosts) return next() | 6 | if (!req.body.hosts) return next() |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index 3dbec6e44..3b9645048 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -2,9 +2,9 @@ import * as express from 'express' | |||
2 | import { query } from 'express-validator/check' | 2 | import { query } from 'express-validator/check' |
3 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' | 3 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { getHostWithPort } from '../../helpers/utils' | ||
6 | import { ActorModel } from '../../models/activitypub/actor' | 5 | import { ActorModel } from '../../models/activitypub/actor' |
7 | import { areValidationErrors } from './utils' | 6 | import { areValidationErrors } from './utils' |
7 | import { getHostWithPort } from '../../helpers/express-utils' | ||
8 | 8 | ||
9 | const webfingerValidator = [ | 9 | const webfingerValidator = [ |
10 | query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), | 10 | query('resource').custom(isWebfingerResourceValid).withMessage('Should have a valid webfinger resource'), |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b0fff6526..2ad9c00dd 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -95,7 +95,33 @@ enum ScopeNames { | |||
95 | } | 95 | } |
96 | 96 | ||
97 | @Scopes({ | 97 | @Scopes({ |
98 | [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean) => { | 98 | [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => { |
99 | const accountInclude = { | ||
100 | attributes: [ 'name' ], | ||
101 | model: AccountModel.unscoped(), | ||
102 | required: true, | ||
103 | where: {}, | ||
104 | include: [ | ||
105 | { | ||
106 | attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
107 | model: ActorModel.unscoped(), | ||
108 | required: true, | ||
109 | where: VideoModel.buildActorWhereWithFilter(filter), | ||
110 | include: [ | ||
111 | { | ||
112 | attributes: [ 'host' ], | ||
113 | model: ServerModel.unscoped(), | ||
114 | required: false | ||
115 | }, | ||
116 | { | ||
117 | model: AvatarModel.unscoped(), | ||
118 | required: false | ||
119 | } | ||
120 | ] | ||
121 | } | ||
122 | ] | ||
123 | } | ||
124 | |||
99 | const query: IFindOptions<VideoModel> = { | 125 | const query: IFindOptions<VideoModel> = { |
100 | where: { | 126 | where: { |
101 | id: { | 127 | id: { |
@@ -125,30 +151,7 @@ enum ScopeNames { | |||
125 | model: VideoChannelModel.unscoped(), | 151 | model: VideoChannelModel.unscoped(), |
126 | required: true, | 152 | required: true, |
127 | include: [ | 153 | include: [ |
128 | { | 154 | accountInclude |
129 | attributes: [ 'name' ], | ||
130 | model: AccountModel.unscoped(), | ||
131 | required: true, | ||
132 | include: [ | ||
133 | { | ||
134 | attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
135 | model: ActorModel.unscoped(), | ||
136 | required: true, | ||
137 | where: VideoModel.buildActorWhereWithFilter(filter), | ||
138 | include: [ | ||
139 | { | ||
140 | attributes: [ 'host' ], | ||
141 | model: ServerModel.unscoped(), | ||
142 | required: false | ||
143 | }, | ||
144 | { | ||
145 | model: AvatarModel.unscoped(), | ||
146 | required: false | ||
147 | } | ||
148 | ] | ||
149 | } | ||
150 | ] | ||
151 | } | ||
152 | ] | 155 | ] |
153 | } | 156 | } |
154 | ] | 157 | ] |
@@ -166,6 +169,12 @@ enum ScopeNames { | |||
166 | query.where['nsfw'] = false | 169 | query.where['nsfw'] = false |
167 | } | 170 | } |
168 | 171 | ||
172 | if (accountId) { | ||
173 | accountInclude.where = { | ||
174 | id: accountId | ||
175 | } | ||
176 | } | ||
177 | |||
169 | return query | 178 | return query |
170 | }, | 179 | }, |
171 | [ScopeNames.WITH_ACCOUNT_DETAILS]: { | 180 | [ScopeNames.WITH_ACCOUNT_DETAILS]: { |
@@ -688,7 +697,15 @@ export class VideoModel extends Model<VideoModel> { | |||
688 | }) | 697 | }) |
689 | } | 698 | } |
690 | 699 | ||
691 | static async listForApi (start: number, count: number, sort: string, hideNSFW: boolean, filter?: VideoFilter, withFiles = false) { | 700 | static async listForApi ( |
701 | start: number, | ||
702 | count: number, | ||
703 | sort: string, | ||
704 | hideNSFW: boolean, | ||
705 | filter?: VideoFilter, | ||
706 | withFiles = false, | ||
707 | accountId?: number | ||
708 | ) { | ||
692 | const query = { | 709 | const query = { |
693 | offset: start, | 710 | offset: start, |
694 | limit: count, | 711 | limit: count, |
@@ -696,7 +713,7 @@ export class VideoModel extends Model<VideoModel> { | |||
696 | } | 713 | } |
697 | 714 | ||
698 | const serverActor = await getServerActor() | 715 | const serverActor = await getServerActor() |
699 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] }) | 716 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] }) |
700 | .findAndCountAll(query) | 717 | .findAndCountAll(query) |
701 | .then(({ rows, count }) => { | 718 | .then(({ rows, count }) => { |
702 | return { | 719 | return { |
@@ -879,8 +896,6 @@ export class VideoModel extends Model<VideoModel> { | |||
879 | 896 | ||
880 | private static getLanguageLabel (id: string) { | 897 | private static getLanguageLabel (id: string) { |
881 | let languageLabel = VIDEO_LANGUAGES[id] | 898 | let languageLabel = VIDEO_LANGUAGES[id] |
882 | console.log(VIDEO_LANGUAGES) | ||
883 | console.log(id) | ||
884 | if (!languageLabel) languageLabel = 'Unknown' | 899 | if (!languageLabel) languageLabel = 'Unknown' |
885 | 900 | ||
886 | return languageLabel | 901 | return languageLabel |