From 0626e7af82e02f8a5bd1e74a7d4d8c916d073ceb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 24 Apr 2018 15:10:54 +0200 Subject: [PATCH] Add account view --- .../account-about.component.html | 12 +++ .../account-about.component.scss | 8 ++ .../account-about/account-about.component.ts | 39 ++++++++++ .../app/+account/account-routing.module.ts | 45 +++++++++++ .../account-videos.component.scss | 3 + .../account-videos.component.ts | 71 +++++++++++++++++ .../src/app/+account/account.component.html | 23 ++++++ .../src/app/+account/account.component.scss | 46 +++++++++++ client/src/app/+account/account.component.ts | 29 +++++++ client/src/app/+account/account.module.ts | 26 +++++++ client/src/app/+account/index.ts | 3 + client/src/app/app-routing.module.ts | 4 + .../my-account-settings.component.scss | 6 +- .../app/my-account/my-account.component.ts | 2 +- .../src/app/shared/account/account.model.ts | 15 ++++ .../src/app/shared/account/account.service.ts | 31 ++++++++ client/src/app/shared/shared.module.ts | 2 + .../app/shared/video/abstract-video-list.html | 4 +- .../app/shared/video/abstract-video-list.ts | 1 + client/src/app/shared/video/video.service.ts | 18 +++++ .../+video-watch/video-watch.component.html | 4 +- .../+video-watch/video-watch.component.ts | 7 -- client/src/sass/include/_mixins.scss | 7 ++ server/controllers/api/accounts.ts | 37 ++++++++- server/controllers/api/index.ts | 2 +- server/controllers/api/users.ts | 3 +- server/controllers/api/videos/index.ts | 19 +---- server/controllers/feeds.ts | 29 +++---- server/helpers/express-utils.ts | 77 +++++++++++++++++++ server/helpers/utils.ts | 62 +-------------- server/middlewares/servers.ts | 2 +- server/middlewares/validators/webfinger.ts | 2 +- server/models/video/video.ts | 73 +++++++++++------- 33 files changed, 563 insertions(+), 149 deletions(-) create mode 100644 client/src/app/+account/account-about/account-about.component.html create mode 100644 client/src/app/+account/account-about/account-about.component.scss create mode 100644 client/src/app/+account/account-about/account-about.component.ts create mode 100644 client/src/app/+account/account-routing.module.ts create mode 100644 client/src/app/+account/account-videos/account-videos.component.scss create mode 100644 client/src/app/+account/account-videos/account-videos.component.ts create mode 100644 client/src/app/+account/account.component.html create mode 100644 client/src/app/+account/account.component.scss create mode 100644 client/src/app/+account/account.component.ts create mode 100644 client/src/app/+account/account.module.ts create mode 100644 client/src/app/+account/index.ts create mode 100644 client/src/app/shared/account/account.service.ts create mode 100644 server/helpers/express-utils.ts 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 @@ +
+
+
Description
+
{{ getAccountDescription() }}
+
+ +
+
Stats
+ +
Joined {{ account.createdAt | date }}
+
+
\ 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 @@ +@import '_variables'; +@import '_mixins'; + +.small-title { + @include in-content-small-title; + + margin-bottom: 20px; +} 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 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { Location } from '@angular/common' +import { getParameterByName, immutableAssign } from '@app/shared/misc/utils' +import { NotificationsService } from 'angular2-notifications' +import 'rxjs/add/observable/from' +import 'rxjs/add/operator/concatAll' +import { AuthService } from '../../core/auth' +import { ConfirmService } from '../../core/confirm' +import { AbstractVideoList } from '../../shared/video/abstract-video-list' +import { VideoService } from '../../shared/video/video.service' +import { Account } from '@app/shared/account/account.model' +import { AccountService } from '@app/shared/account/account.service' + +@Component({ + selector: 'my-account-about', + templateUrl: './account-about.component.html', + styleUrls: [ './account-about.component.scss' ] +}) +export class AccountAboutComponent implements OnInit { + private account: Account + + constructor ( + protected route: ActivatedRoute, + private accountService: AccountService + ) { } + + ngOnInit () { + // Parent get the account for us + this.accountService.accountLoaded + .subscribe(account => this.account = account) + } + + getAccountDescription () { + if (this.account.description) return this.account.description + + return 'No description' + } +} 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 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' +import { MetaGuard } from '@ngx-meta/core' +import { AccountComponent } from './account.component' +import { AccountVideosComponent } from './account-videos/account-videos.component' +import { AccountAboutComponent } from '@app/+account/account-about/account-about.component' + +const accountRoutes: Routes = [ + { + path: ':accountId', + component: AccountComponent, + canActivateChild: [ MetaGuard ], + children: [ + { + path: '', + redirectTo: 'videos', + pathMatch: 'full' + }, + { + path: 'videos', + component: AccountVideosComponent, + data: { + meta: { + title: 'Account videos' + } + } + }, + { + path: 'about', + component: AccountAboutComponent, + data: { + meta: { + title: 'About account' + } + } + } + ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(accountRoutes) ], + exports: [ RouterModule ] +}) +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 @@ +.title-page-single { + margin-top: 0; +} \ 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 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { Location } from '@angular/common' +import { immutableAssign } from '@app/shared/misc/utils' +import { NotificationsService } from 'angular2-notifications' +import 'rxjs/add/observable/from' +import 'rxjs/add/operator/concatAll' +import { AuthService } from '../../core/auth' +import { ConfirmService } from '../../core/confirm' +import { AbstractVideoList } from '../../shared/video/abstract-video-list' +import { VideoService } from '../../shared/video/video.service' +import { Account } from '@app/shared/account/account.model' +import { AccountService } from '@app/shared/account/account.service' + +@Component({ + selector: 'my-account-videos', + templateUrl: '../../shared/video/abstract-video-list.html', + styleUrls: [ + '../../shared/video/abstract-video-list.scss', + './account-videos.component.scss' + ] +}) +export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { + titlePage = 'Published videos' + marginContent = false // Disable margin + currentRoute = '/account/videos' + loadOnInit = false + + private account: Account + + constructor ( + protected router: Router, + protected route: ActivatedRoute, + protected authService: AuthService, + protected notificationsService: NotificationsService, + protected confirmService: ConfirmService, + protected location: Location, + private accountService: AccountService, + private videoService: VideoService + ) { + super() + } + + ngOnInit () { + super.ngOnInit() + + // Parent get the account for us + this.accountService.accountLoaded + .subscribe(account => { + this.account = account + this.currentRoute = '/account/' + this.account.id + '/videos' + + this.loadMoreVideos(this.pagination.currentPage) + this.generateSyndicationList() + }) + } + + ngOnDestroy () { + super.ngOnDestroy() + } + + getVideosObservable (page: number) { + const newPagination = immutableAssign(this.pagination, { currentPage: page }) + + return this.videoService.getAccountVideos(this.account, newPagination, this.sort) + } + + generateSyndicationList () { + this.syndicationItems = this.videoService.getAccountFeedUrls(this.account.id) + } +} 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 @@ +
+ + +
+ +
+
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 @@ +@import '_variables'; +@import '_mixins'; + +.sub-menu { + height: 160px; + display: flex; + flex-direction: column; + align-items: start; + + .account { + display: flex; + margin-top: 20px; + margin-bottom: 20px; + + img { + @include avatar(80px); + + margin-right: 20px; + } + + .account-info { + display: flex; + flex-direction: column; + justify-content: center; + + .account-display-name { + font-size: 23px; + font-weight: $font-bold; + } + + .account-followers { + font-size: 15px; + } + } + } + + .links { + margin-top: 0; + margin-bottom: 10px; + + a { + margin-top: 0; + margin-bottom: 0; + } + } +} \ 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 @@ +import { Component, OnInit } from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { AccountService } from '@app/shared/account/account.service' +import { Account } from '@app/shared/account/account.model' + +@Component({ + selector: 'my-account', + templateUrl: './account.component.html', + styleUrls: [ './account.component.scss' ] +}) +export class AccountComponent implements OnInit { + private account: Account + + constructor ( + private route: ActivatedRoute, + private accountService: AccountService + ) {} + + ngOnInit () { + const accountId = parseInt(this.route.snapshot.params['accountId'], 10) + + this.accountService.getAccount(accountId) + .subscribe(account => this.account = account) + } + + getAvatarUrl () { + return Account.GET_ACCOUNT_AVATAR_URL(this.account) + } +} 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 @@ +import { NgModule } from '@angular/core' +import { SharedModule } from '../shared' +import { AccountRoutingModule } from './account-routing.module' +import { AccountComponent } from './account.component' +import { AccountVideosComponent } from './account-videos/account-videos.component' +import { AccountAboutComponent } from './account-about/account-about.component' + +@NgModule({ + imports: [ + AccountRoutingModule, + SharedModule + ], + + declarations: [ + AccountComponent, + AccountVideosComponent, + AccountAboutComponent + ], + + exports: [ + AccountComponent + ], + + providers: [] +}) +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 @@ +export * from './account-routing.module' +export * from './account.component' +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 = [ { path: 'admin', loadChildren: './+admin/admin.module#AdminModule' + }, + { + path: 'account', + loadChildren: './+account/account.module#AccountModule' } ] 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 @@ } .account-title { - text-transform: uppercase; - color: $orange-color; - font-weight: $font-bold; - font-size: 13px; + @include in-content-small-title; + margin-top: 55px; margin-bottom: 30px; } 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 @@ import { Component } from '@angular/core' @Component({ - selector: 'my-account', + selector: 'my-my-account', templateUrl: './my-account.component.html' }) 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 { updatedAt: Date avatar: Avatar + constructor (hash: ServerAccount) { + this.id = hash.id + this.uuid = hash.uuid + this.url = hash.url + this.name = hash.name + this.displayName = hash.displayName + this.description = hash.description + this.host = hash.host + this.followingCount = hash.followingCount + this.followersCount = hash.followersCount + this.createdAt = new Date(hash.createdAt.toString()) + this.updatedAt = new Date(hash.updatedAt.toString()) + this.avatar = hash.avatar + } + static GET_ACCOUNT_AVATAR_URL (account: Account) { const absoluteAPIUrl = getAbsoluteAPIUrl() 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 @@ +import { Injectable } from '@angular/core' +import 'rxjs/add/operator/catch' +import 'rxjs/add/operator/map' +import { environment } from '../../../environments/environment' +import { Observable } from 'rxjs/Observable' +import { Account } from '@app/shared/account/account.model' +import { RestExtractor } from '@app/shared/rest/rest-extractor.service' +import { RestService } from '@app/shared/rest/rest.service' +import { HttpClient } from '@angular/common/http' +import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' +import { ReplaySubject } from 'rxjs/ReplaySubject' + +@Injectable() +export class AccountService { + static BASE_ACCOUNT_URL = environment.apiUrl + '/api/v1/accounts/' + + accountLoaded = new ReplaySubject(1) + + constructor ( + private authHttp: HttpClient, + private restExtractor: RestExtractor, + private restService: RestService + ) {} + + getAccount (id: number): Observable { + return this.authHttp.get(AccountService.BASE_ACCOUNT_URL + id) + .map(accountHash => new Account(accountHash)) + .do(account => this.accountLoaded.next(account)) + .catch((res) => this.restExtractor.handleError(res)) + } +} 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' import { VideoFeedComponent } from './video/video-feed.component' import { VideoThumbnailComponent } from './video/video-thumbnail.component' import { VideoService } from './video/video.service' +import { AccountService } from '@app/shared/account/account.service' @NgModule({ imports: [ @@ -104,6 +105,7 @@ import { VideoService } from './video/video.service' VideoBlacklistService, UserService, VideoService, + AccountService, MarkdownService ] }) 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 @@ -
-
+
+
{{ titlePage }}
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 { syndicationItems = [] loadOnInit = true + marginContent = true pageHeight: number videoWidth: number 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' import { VideoEdit } from './video-edit.model' import { Video } from './video.model' import { objectToFormData } from '@app/shared/misc/utils' +import { Account } from '@app/shared/account/account.model' +import { AccountService } from '@app/shared/account/account.service' @Injectable() export class VideoService { @@ -97,6 +99,22 @@ export class VideoService { .catch((res) => this.restExtractor.handleError(res)) } + getAccountVideos ( + account: Account, + videoPagination: ComponentPagination, + sort: VideoSortField + ): Observable<{ videos: Video[], totalVideos: number}> { + const pagination = this.restService.componentPaginationToRestPagination(videoPagination) + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp + .get(AccountService.BASE_ACCOUNT_URL + account.id + '/videos', { params }) + .map(this.extractVideos) + .catch((res) => this.restExtractor.handleError(res)) + } + getVideos ( videoPagination: ComponentPagination, 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 @@
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 { otherVideosDisplayed: Video[] = [] - syndicationItems = {} - player: videojs.Player playerElement: HTMLVideoElement userRating: UserVideoRateType = null @@ -110,7 +108,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { const startTime = this.route.snapshot.queryParams.start this.onVideoFetched(video, startTime) .catch(err => this.handleError(err)) - this.generateSyndicationList() }, error => { @@ -247,10 +244,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return this.video.tags.join(', ') } - generateSyndicationList () { - this.syndicationItems = this.videoService.getAccountFeedUrls(this.video.account.id) - } - isVideoRemovable () { return this.video.isRemovableBy(this.authService.getUser()) } 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 @@ left: 0.25em; transform: rotate(-135deg); } + +@mixin in-content-small-title { + text-transform: uppercase; + color: $orange-color; + font-weight: $font-bold; + font-size: 13px; +} \ 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 @@ import * as express from 'express' import { getFormattedObjects } from '../../helpers/utils' -import { asyncMiddleware, paginationValidator, setDefaultSort, setDefaultPagination } from '../../middlewares' -import { accountsGetValidator, accountsSortValidator } from '../../middlewares/validators' +import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares' +import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' import { AccountModel } from '../../models/account/account' +import { VideoModel } from '../../models/video/video' +import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' +import { isNSFWHidden } from '../../helpers/express-utils' const accountsRouter = express.Router() @@ -19,6 +22,16 @@ accountsRouter.get('/:id', getAccount ) +accountsRouter.get('/:id/videos', + asyncMiddleware(accountsGetValidator), + paginationValidator, + videosSortValidator, + setDefaultSort, + setDefaultPagination, + optionalAuthenticate, + asyncMiddleware(getAccountVideos) +) + // --------------------------------------------------------------------------- export { @@ -28,7 +41,9 @@ export { // --------------------------------------------------------------------------- function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { - return res.json(res.locals.account.toFormattedJSON()) + const account: AccountModel = res.locals.account + + return res.json(account.toFormattedJSON()) } 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: return res.json(getFormattedObjects(resultList.data, resultList.total)) } + +async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { + const account: AccountModel = res.locals.account + + const resultList = await VideoModel.listForApi( + req.query.start as number, + req.query.count as number, + req.query.sort as VideoSortField, + isNSFWHidden(res), + null, + false, + account.id + ) + + return res.json(getFormattedObjects(resultList.data, resultList.total)) +} 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 @@ import * as express from 'express' -import { badRequest } from '../../helpers/utils' import { configRouter } from './config' import { jobsRouter } from './jobs' import { oauthClientsRouter } from './oauth-clients' @@ -7,6 +6,7 @@ import { serverRouter } from './server' import { usersRouter } from './users' import { accountsRouter } from './accounts' import { videosRouter } from './videos' +import { badRequest } from '../../helpers/express-utils' const apiRouter = express.Router() 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 import { retryTransactionWrapper } from '../../helpers/database-utils' import { processImage } from '../../helpers/image-utils' import { logger } from '../../helpers/logger' -import { createReqFiles, getFormattedObjects } from '../../helpers/utils' +import { getFormattedObjects } from '../../helpers/utils' import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, RATES_LIMIT, sequelizeTypescript } from '../../initializers' import { updateActorAvatarInstance } from '../../lib/activitypub' import { sendUpdateActor } from '../../lib/activitypub/send' @@ -43,6 +43,7 @@ import { UserModel } from '../../models/account/user' import { OAuthTokenModel } from '../../models/oauth/oauth-token' import { VideoModel } from '../../models/video/video' import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' +import { createReqFiles } from '../../helpers/express-utils' const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) 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' import { getVideoFileResolution } from '../../../helpers/ffmpeg-utils' import { processImage } from '../../../helpers/image-utils' import { logger } from '../../../helpers/logger' -import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' +import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' import { CONFIG, IMAGE_MIMETYPE_EXT, @@ -19,11 +19,7 @@ import { VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' -import { - fetchRemoteVideoDescription, - getVideoActivityPubUrl, - shareVideoByServerAndChannel -} from '../../../lib/activitypub' +import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServerAndChannel } from '../../../lib/activitypub' import { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send' import { JobQueue } from '../../../lib/job-queue' import { Redis } from '../../../lib/redis' @@ -49,9 +45,9 @@ import { blacklistRouter } from './blacklist' import { videoChannelRouter } from './channel' import { videoCommentRouter } from './comment' import { rateVideoRouter } from './rate' -import { User } from '../../../../shared/models/users' import { VideoFilter } from '../../../../shared/models/videos/video-query.type' import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type' +import { isNSFWHidden, createReqFiles } from '../../../helpers/express-utils' const videosRouter = express.Router() @@ -444,12 +440,3 @@ async function searchVideos (req: express.Request, res: express.Response, next: return res.json(getFormattedObjects(resultList.data, resultList.total)) } - -function isNSFWHidden (res: express.Response) { - if (res.locals.oauth) { - const user: User = res.locals.oauth.token.User - if (user) return user.nsfwPolicy === 'do_not_list' - } - - return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' -} 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: let feed = initFeed() const start = 0 - let resultList: ResultList const account: AccountModel = res.locals.account const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' - if (account) { - resultList = await VideoModel.listAccountVideosForApi( - account.id, - start, - FEEDS.COUNT, - req.query.sort as VideoSortField, - hideNSFW, - true - ) - } else { - resultList = await VideoModel.listForApi( - start, - FEEDS.COUNT, - req.query.sort as VideoSortField, - hideNSFW, - req.query.filter, - true - ) - } + const resultList = await VideoModel.listForApi( + start, + FEEDS.COUNT, + req.query.sort as VideoSortField, + hideNSFW, + req.query.filter, + true, + account ? account.id : null + ) // Adding video items to the feed, one at a time 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 @@ +import * as express from 'express' +import * as multer from 'multer' +import { CONFIG, REMOTE_SCHEME } from '../initializers' +import { logger } from './logger' +import { User } from '../../shared/models/users' +import { generateRandomString } from './utils' + +function isNSFWHidden (res: express.Response) { + if (res.locals.oauth) { + const user: User = res.locals.oauth.token.User + if (user) return user.nsfwPolicy === 'do_not_list' + } + + return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' +} + +function getHostWithPort (host: string) { + const splitted = host.split(':') + + // The port was not specified + if (splitted.length === 1) { + if (REMOTE_SCHEME.HTTP === 'https') return host + ':443' + + return host + ':80' + } + + return host +} + +function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { + return res.type('json').status(400).end() +} + +function createReqFiles ( + fieldNames: string[], + mimeTypes: { [ id: string ]: string }, + destinations: { [ fieldName: string ]: string } +) { + const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, destinations[ file.fieldname ]) + }, + + filename: async (req, file, cb) => { + const extension = mimeTypes[ file.mimetype ] + let randomString = '' + + try { + randomString = await generateRandomString(16) + } catch (err) { + logger.error('Cannot generate random string for file name.', { err }) + randomString = 'fake-random-string' + } + + cb(null, randomString + extension) + } + }) + + const fields = [] + for (const fieldName of fieldNames) { + fields.push({ + name: fieldName, + maxCount: 1 + }) + } + + return multer({ storage }).fields(fields) +} + +// --------------------------------------------------------------------------- + +export { + isNSFWHidden, + getHostWithPort, + badRequest, + createReqFiles +} 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 @@ -import * as express from 'express' -import * as multer from 'multer' import { Model } from 'sequelize-typescript' import { ResultList } from '../../shared' import { VideoResolution } from '../../shared/models/videos' -import { CONFIG, REMOTE_SCHEME } from '../initializers' +import { CONFIG } from '../initializers' import { UserModel } from '../models/account/user' import { ActorModel } from '../models/activitypub/actor' import { ApplicationModel } from '../models/application/application' import { pseudoRandomBytesPromise } from './core-utils' import { logger } from './logger' -function getHostWithPort (host: string) { - const splitted = host.split(':') - - // The port was not specified - if (splitted.length === 1) { - if (REMOTE_SCHEME.HTTP === 'https') return host + ':443' - - return host + ':80' - } - - return host -} - -function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { - return res.type('json').status(400).end() -} - -function createReqFiles ( - fieldNames: string[], - mimeTypes: { [ id: string ]: string }, - destinations: { [ fieldName: string ]: string } -) { - const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, destinations[file.fieldname]) - }, - - filename: async (req, file, cb) => { - const extension = mimeTypes[file.mimetype] - let randomString = '' - - try { - randomString = await generateRandomString(16) - } catch (err) { - logger.error('Cannot generate random string for file name.', { err }) - randomString = 'fake-random-string' - } - - cb(null, randomString + extension) - } - }) - - const fields = [] - for (const fieldName of fieldNames) { - fields.push({ - name: fieldName, - maxCount: 1 - }) - } - - return multer({ storage }).fields(fields) -} - async function generateRandomString (size: number) { const raw = await pseudoRandomBytesPromise(size) @@ -151,14 +96,11 @@ type SortType = { sortModel: any, sortValue: string } // --------------------------------------------------------------------------- export { - badRequest, generateRandomString, getFormattedObjects, isSignupAllowed, computeResolutionsToTranscode, resetSequelizeInstance, getServerActor, - SortType, - getHostWithPort, - createReqFiles + SortType } 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 @@ import * as express from 'express' import 'express-validator' -import { getHostWithPort } from '../helpers/utils' +import { getHostWithPort } from '../helpers/express-utils' function setBodyHostsPort (req: express.Request, res: express.Response, next: express.NextFunction) { 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' import { query } from 'express-validator/check' import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' import { logger } from '../../helpers/logger' -import { getHostWithPort } from '../../helpers/utils' import { ActorModel } from '../../models/activitypub/actor' import { areValidationErrors } from './utils' +import { getHostWithPort } from '../../helpers/express-utils' const webfingerValidator = [ 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 { } @Scopes({ - [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean) => { + [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => { + const accountInclude = { + attributes: [ 'name' ], + model: AccountModel.unscoped(), + required: true, + where: {}, + include: [ + { + attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], + model: ActorModel.unscoped(), + required: true, + where: VideoModel.buildActorWhereWithFilter(filter), + include: [ + { + attributes: [ 'host' ], + model: ServerModel.unscoped(), + required: false + }, + { + model: AvatarModel.unscoped(), + required: false + } + ] + } + ] + } + const query: IFindOptions = { where: { id: { @@ -125,30 +151,7 @@ enum ScopeNames { model: VideoChannelModel.unscoped(), required: true, include: [ - { - attributes: [ 'name' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], - model: ActorModel.unscoped(), - required: true, - where: VideoModel.buildActorWhereWithFilter(filter), - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - }, - { - model: AvatarModel.unscoped(), - required: false - } - ] - } - ] - } + accountInclude ] } ] @@ -166,6 +169,12 @@ enum ScopeNames { query.where['nsfw'] = false } + if (accountId) { + accountInclude.where = { + id: accountId + } + } + return query }, [ScopeNames.WITH_ACCOUNT_DETAILS]: { @@ -688,7 +697,15 @@ export class VideoModel extends Model { }) } - static async listForApi (start: number, count: number, sort: string, hideNSFW: boolean, filter?: VideoFilter, withFiles = false) { + static async listForApi ( + start: number, + count: number, + sort: string, + hideNSFW: boolean, + filter?: VideoFilter, + withFiles = false, + accountId?: number + ) { const query = { offset: start, limit: count, @@ -696,7 +713,7 @@ export class VideoModel extends Model { } const serverActor = await getServerActor() - return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] }) + return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] }) .findAndCountAll(query) .then(({ rows, count }) => { return { @@ -879,8 +896,6 @@ export class VideoModel extends Model { private static getLanguageLabel (id: string) { let languageLabel = VIDEO_LANGUAGES[id] - console.log(VIDEO_LANGUAGES) - console.log(id) if (!languageLabel) languageLabel = 'Unknown' return languageLabel