Add account view
This commit is contained in:
parent
b4d1af3dd8
commit
0626e7af82
|
@ -0,0 +1,12 @@
|
|||
<div *ngIf="account" class="row">
|
||||
<div class="description col-md-6 col-sm-12">
|
||||
<div class="small-title">Description</div>
|
||||
<div class="content">{{ getAccountDescription() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stats col-md-6 col-sm-12">
|
||||
<div class="small-title">Stats</div>
|
||||
|
||||
<div class="content">Joined {{ account.createdAt | date }}</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
.small-title {
|
||||
@include in-content-small-title;
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -0,0 +1,3 @@
|
|||
.title-page-single {
|
||||
margin-top: 0;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<div *ngIf="account" class="row">
|
||||
<div class="sub-menu">
|
||||
|
||||
<div class="account">
|
||||
<img [src]="getAvatarUrl()" alt="Avatar" />
|
||||
|
||||
<div class="account-info">
|
||||
<div class="account-display-name">{{ account.displayName }}</div>
|
||||
<div class="account-followers">{{ account.followersCount }} subscribers</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
|
||||
|
||||
<a routerLink="about" routerLinkActive="active" class="title-page">About</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -0,0 +1,3 @@
|
|||
export * from './account-routing.module'
|
||||
export * from './account.component'
|
||||
export * from './account.module'
|
|
@ -7,6 +7,10 @@ const routes: Routes = [
|
|||
{
|
||||
path: 'admin',
|
||||
loadChildren: './+admin/admin.module#AdminModule'
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: './+account/account.module#AccountModule'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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<Account>(1)
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor,
|
||||
private restService: RestService
|
||||
) {}
|
||||
|
||||
getAccount (id: number): Observable<Account> {
|
||||
return this.authHttp.get<ServerAccount>(AccountService.BASE_ACCOUNT_URL + id)
|
||||
.map(accountHash => new Account(accountHash))
|
||||
.do(account => this.accountLoaded.next(account))
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
]
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="margin-content">
|
||||
<div class="title-page title-page-single">
|
||||
<div [ngClass]="{ 'margin-content': marginContent }">
|
||||
<div *ngIf="titlePage" class="title-page title-page-single">
|
||||
{{ titlePage }}
|
||||
</div>
|
||||
<my-video-feed [syndicationItems]="syndicationItems"></my-video-feed>
|
||||
|
|
|
@ -29,6 +29,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
|
|||
syndicationItems = []
|
||||
|
||||
loadOnInit = true
|
||||
marginContent = true
|
||||
pageHeight: number
|
||||
videoWidth: number
|
||||
videoHeight: number
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -22,12 +22,10 @@
|
|||
</div>
|
||||
|
||||
<div class="video-info-by">
|
||||
<a [routerLink]="[ '/videos', 'search' ]" [queryParams]="{ search: video.account.name }" title="Search videos of this account">
|
||||
<a [routerLink]="[ '/account', video.account.id ]" title="Go the account page">
|
||||
By {{ video.by }}
|
||||
<img [src]="getAvatarPath()" alt="Account avatar" />
|
||||
</a>
|
||||
|
||||
<my-video-feed [syndicationItems]="syndicationItems"></my-video-feed>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -30,29 +30,18 @@ async function generateFeed (req: express.Request, res: express.Response, next:
|
|||
let feed = initFeed()
|
||||
const start = 0
|
||||
|
||||
let resultList: ResultList<VideoModel>
|
||||
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 => {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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<VideoModel> = {
|
||||
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<VideoModel> {
|
|||
})
|
||||
}
|
||||
|
||||
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<VideoModel> {
|
|||
}
|
||||
|
||||
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<VideoModel> {
|
|||
|
||||
private static getLanguageLabel (id: string) {
|
||||
let languageLabel = VIDEO_LANGUAGES[id]
|
||||
console.log(VIDEO_LANGUAGES)
|
||||
console.log(id)
|
||||
if (!languageLabel) languageLabel = 'Unknown'
|
||||
|
||||
return languageLabel
|
||||
|
|
Loading…
Reference in New Issue