Add account view

This commit is contained in:
Chocobozzz 2018-04-24 15:10:54 +02:00
parent b4d1af3dd8
commit 0626e7af82
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
33 changed files with 563 additions and 149 deletions

View File

@ -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>

View File

@ -0,0 +1,8 @@
@import '_variables';
@import '_mixins';
.small-title {
@include in-content-small-title;
margin-bottom: 20px;
}

View File

@ -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'
}
}

View File

@ -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 {}

View File

@ -0,0 +1,3 @@
.title-page-single {
margin-top: 0;
}

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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)
}
}

View File

@ -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 { }

View File

@ -0,0 +1,3 @@
export * from './account-routing.module'
export * from './account.component'
export * from './account.module'

View File

@ -7,6 +7,10 @@ const routes: Routes = [
{
path: 'admin',
loadChildren: './+admin/admin.module#AdminModule'
},
{
path: 'account',
loadChildren: './+account/account.module#AccountModule'
}
]

View File

@ -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;
}

View File

@ -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 {}

View File

@ -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()

View File

@ -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))
}
}

View File

@ -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
]
})

View File

@ -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>

View File

@ -29,6 +29,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
syndicationItems = []
loadOnInit = true
marginContent = true
pageHeight: number
videoWidth: number
videoHeight: number

View File

@ -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,

View File

@ -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>

View File

@ -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())
}

View File

@ -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;
}

View File

@ -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))
}

View File

@ -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()

View File

@ -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({

View File

@ -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'
}

View File

@ -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 => {

View File

@ -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
}

View File

@ -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
}

View File

@ -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()

View File

@ -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'),

View File

@ -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