Merge branch 'develop' of framagit.org:chocobozzz/PeerTube into develop
This commit is contained in:
commit
0db1a22650
|
@ -146,3 +146,9 @@ Build the application and run the unit/integration tests:
|
|||
$ npm run build
|
||||
$ npm test
|
||||
```
|
||||
|
||||
If you just want to run 1 test:
|
||||
|
||||
```
|
||||
$ npm run mocha -- --exit --require ts-node/register/type-check --bail server/tests/api/index.ts
|
||||
```
|
||||
|
|
|
@ -62,6 +62,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
|
||||
<my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy">
|
||||
<option value="do_not_list">Do not list</option>
|
||||
<option value="blur">Blur thumbnails</option>
|
||||
<option value="display">Display</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error">
|
||||
{{ formErrors.instanceDefaultNSFWPolicy }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inner-form-title">Cache</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -48,6 +48,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
instanceDescription: '',
|
||||
instanceTerms: '',
|
||||
instanceDefaultClientRoute: '',
|
||||
instanceDefaultNSFWPolicy: '',
|
||||
cachePreviewsSize: '',
|
||||
signupLimit: '',
|
||||
adminEmail: '',
|
||||
|
@ -90,6 +91,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
instanceDescription: [ '' ],
|
||||
instanceTerms: [ '' ],
|
||||
instanceDefaultClientRoute: [ '' ],
|
||||
instanceDefaultNSFWPolicy: [ '' ],
|
||||
cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ],
|
||||
signupEnabled: [ ],
|
||||
signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ],
|
||||
|
@ -167,6 +169,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
description: this.form.value['instanceDescription'],
|
||||
terms: this.form.value['instanceTerms'],
|
||||
defaultClientRoute: this.form.value['instanceDefaultClientRoute'],
|
||||
defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'],
|
||||
customizations: {
|
||||
javascript: this.form.value['customizationJavascript'],
|
||||
css: this.form.value['customizationCSS']
|
||||
|
@ -224,6 +227,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
instanceDescription: this.customConfig.instance.description,
|
||||
instanceTerms: this.customConfig.instance.terms,
|
||||
instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute,
|
||||
instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy,
|
||||
cachePreviewsSize: this.customConfig.cache.previews.size,
|
||||
signupEnabled: this.customConfig.signup.enabled,
|
||||
signupLimit: this.customConfig.signup.limit,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="checkbox" id="displayNSFW"
|
||||
formControlName="displayNSFW"
|
||||
>
|
||||
<label for="displayNSFW"></label>
|
||||
<label for="displayNSFW">Display videos that contain mature or explicit content</label>
|
||||
<label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
|
||||
<my-help helpType="custom" customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."></my-help>
|
||||
|
||||
<div class="peertube-select-container">
|
||||
<select id="nsfwPolicy" formControlName="nsfwPolicy">
|
||||
<option value="do_not_list">Do not list</option>
|
||||
<option value="blur">Blur thumbnails</option>
|
||||
<option value="display">Display</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -12,3 +12,9 @@ input[type=submit] {
|
|||
display: block;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.peertube-select-container {
|
||||
@include peertube-select-container(340px);
|
||||
|
||||
margin-bottom: 30px;
|
||||
}
|
|
@ -29,7 +29,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
|
|||
|
||||
buildForm () {
|
||||
this.form = this.formBuilder.group({
|
||||
displayNSFW: [ this.user.displayNSFW ],
|
||||
nsfwPolicy: [ this.user.nsfwPolicy ],
|
||||
autoPlayVideo: [ this.user.autoPlayVideo ]
|
||||
})
|
||||
|
||||
|
@ -41,10 +41,10 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
|
|||
}
|
||||
|
||||
updateDetails () {
|
||||
const displayNSFW = this.form.value['displayNSFW']
|
||||
const nsfwPolicy = this.form.value['nsfwPolicy']
|
||||
const autoPlayVideo = this.form.value['autoPlayVideo']
|
||||
const details: UserUpdateMe = {
|
||||
displayNSFW,
|
||||
nsfwPolicy,
|
||||
autoPlayVideo
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<div class="video-info">
|
||||
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
|
||||
<span class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
|
||||
<div class="video-info-private">{{ video.privacy.label }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Display only once -->
|
||||
|
|
|
@ -79,8 +79,12 @@
|
|||
font-weight: $font-semibold;
|
||||
}
|
||||
|
||||
.video-info-date-views {
|
||||
.video-info-date-views, .video-info-private {
|
||||
font-size: 13px;
|
||||
|
||||
&.video-info-private {
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { UserRight } from '../../../../../shared/models/users/user-right.enum'
|
|||
// Do not use the barrel (dependency loop)
|
||||
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
|
||||
import { User, UserConstructorHash } from '../../shared/users/user.model'
|
||||
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
|
||||
|
||||
export type TokenOptions = {
|
||||
accessToken: string
|
||||
|
@ -70,7 +71,7 @@ export class AuthUser extends User {
|
|||
ROLE: 'role',
|
||||
EMAIL: 'email',
|
||||
USERNAME: 'username',
|
||||
DISPLAY_NSFW: 'display_nsfw',
|
||||
NSFW_POLICY: 'nsfw_policy',
|
||||
AUTO_PLAY_VIDEO: 'auto_play_video'
|
||||
}
|
||||
|
||||
|
@ -85,7 +86,7 @@ export class AuthUser extends User {
|
|||
username: peertubeLocalStorage.getItem(this.KEYS.USERNAME),
|
||||
email: peertubeLocalStorage.getItem(this.KEYS.EMAIL),
|
||||
role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole,
|
||||
displayNSFW: peertubeLocalStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true',
|
||||
nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType,
|
||||
autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true'
|
||||
},
|
||||
Tokens.load()
|
||||
|
@ -99,7 +100,7 @@ export class AuthUser extends User {
|
|||
peertubeLocalStorage.removeItem(this.KEYS.USERNAME)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.ID)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.ROLE)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.DISPLAY_NSFW)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO)
|
||||
peertubeLocalStorage.removeItem(this.KEYS.EMAIL)
|
||||
Tokens.flush()
|
||||
|
@ -136,7 +137,7 @@ export class AuthUser extends User {
|
|||
peertubeLocalStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
|
||||
peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
|
||||
peertubeLocalStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString())
|
||||
peertubeLocalStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
|
||||
peertubeLocalStorage.setItem(AuthUser.KEYS.NSFW_POLICY, this.nsfwPolicy.toString())
|
||||
peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo))
|
||||
this.tokens.save()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'rxjs/add/operator/do'
|
|||
import { ReplaySubject } from 'rxjs/ReplaySubject'
|
||||
import { ServerConfig } from '../../../../../shared'
|
||||
import { About } from '../../../../../shared/models/server/about.model'
|
||||
import { ServerStats } from '../../../../../shared/models/server/server-stats.model'
|
||||
import { environment } from '../../../environments/environment'
|
||||
|
||||
@Injectable()
|
||||
|
@ -26,6 +25,7 @@ export class ServerService {
|
|||
shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' +
|
||||
'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
|
||||
defaultClientRoute: '',
|
||||
defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
|
||||
customizations: {
|
||||
javascript: '',
|
||||
css: ''
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
</ng-template>
|
||||
|
||||
<span
|
||||
class="help-tooltip-button" containerClass="help-tooltip" title="Click to get help"
|
||||
#tooltipDirective="bs-tooltip" [tooltip]="tooltipTemplate" triggers="click"
|
||||
class="help-tooltip-button"
|
||||
title="Get help"
|
||||
[popover]="tooltipTemplate"
|
||||
[placement]="tooltipPlacement"
|
||||
[outsideClick]="true"
|
||||
></span>
|
||||
|
|
|
@ -12,20 +12,16 @@
|
|||
}
|
||||
|
||||
/deep/ {
|
||||
.help-tooltip {
|
||||
opacity: 1 !important;
|
||||
.popover-body {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
max-width: 300px;
|
||||
|
||||
.tooltip-inner {
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
max-width: 300px;
|
||||
|
||||
font-size: 13px;
|
||||
font-family: $main-fonts;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
font-size: 13px;
|
||||
font-family: $main-fonts;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, ElementRef, HostListener, Input, OnInit, ViewChild, OnChanges } from '@angular/core'
|
||||
import { Component, Input, OnChanges, OnInit } from '@angular/core'
|
||||
import { MarkdownService } from '@app/videos/shared'
|
||||
import { TooltipDirective } from 'ngx-bootstrap/tooltip'
|
||||
|
||||
@Component({
|
||||
selector: 'my-help',
|
||||
|
@ -9,16 +8,14 @@ import { TooltipDirective } from 'ngx-bootstrap/tooltip'
|
|||
})
|
||||
|
||||
export class HelpComponent implements OnInit, OnChanges {
|
||||
@ViewChild('tooltipDirective') tooltipDirective: TooltipDirective
|
||||
@Input() preHtml = ''
|
||||
@Input() postHtml = ''
|
||||
@Input() customHtml = ''
|
||||
@Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
|
||||
@Input() tooltipPlacement = 'right'
|
||||
|
||||
mainHtml = ''
|
||||
|
||||
constructor (private elementRef: ElementRef) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.init()
|
||||
}
|
||||
|
@ -27,15 +24,6 @@ export class HelpComponent implements OnInit, OnChanges {
|
|||
this.init()
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event.target'])
|
||||
public onClick (targetElement) {
|
||||
const clickedInside = this.elementRef.nativeElement.contains(targetElement)
|
||||
|
||||
if (this.tooltipDirective.isOpen && !clickedInside) {
|
||||
this.tooltipDirective.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private init () {
|
||||
if (this.helpType === 'custom') {
|
||||
this.mainHtml = this.customHtml
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared'
|
||||
import { Account } from '../account/account.model'
|
||||
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
|
||||
|
||||
export type UserConstructorHash = {
|
||||
id: number,
|
||||
|
@ -7,7 +8,7 @@ export type UserConstructorHash = {
|
|||
email: string,
|
||||
role: UserRole,
|
||||
videoQuota?: number,
|
||||
displayNSFW?: boolean,
|
||||
nsfwPolicy?: NSFWPolicyType,
|
||||
autoPlayVideo?: boolean,
|
||||
createdAt?: Date,
|
||||
account?: Account,
|
||||
|
@ -18,7 +19,7 @@ export class User implements UserServerModel {
|
|||
username: string
|
||||
email: string
|
||||
role: UserRole
|
||||
displayNSFW: boolean
|
||||
nsfwPolicy: NSFWPolicyType
|
||||
autoPlayVideo: boolean
|
||||
videoQuota: number
|
||||
account: Account
|
||||
|
@ -40,8 +41,8 @@ export class User implements UserServerModel {
|
|||
this.videoQuota = hash.videoQuota
|
||||
}
|
||||
|
||||
if (hash.displayNSFW !== undefined) {
|
||||
this.displayNSFW = hash.displayNSFW
|
||||
if (hash.nsfwPolicy !== undefined) {
|
||||
this.nsfwPolicy = hash.nsfwPolicy
|
||||
}
|
||||
|
||||
if (hash.autoPlayVideo !== undefined) {
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
import {
|
||||
UserRight,
|
||||
VideoChannel,
|
||||
VideoDetails as VideoDetailsServerModel,
|
||||
VideoFile,
|
||||
VideoPrivacy
|
||||
} from '../../../../../shared'
|
||||
import { UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile } from '../../../../../shared'
|
||||
import { Account } from '../../../../../shared/models/actors'
|
||||
import { VideoConstant } from '../../../../../shared/models/videos/video.model'
|
||||
import { AuthUser } from '../../core'
|
||||
import { Video } from '../../shared/video/video.model'
|
||||
|
||||
export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||
privacy: VideoConstant<VideoPrivacy>
|
||||
descriptionPath: string
|
||||
support: string
|
||||
channel: VideoChannel
|
||||
|
@ -26,7 +18,6 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
|||
constructor (hash: VideoDetailsServerModel) {
|
||||
super(hash)
|
||||
|
||||
this.privacy = hash.privacy
|
||||
this.descriptionPath = hash.descriptionPath
|
||||
this.files = hash.files
|
||||
this.channel = hash.channel
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<div class="video-miniature">
|
||||
<my-video-thumbnail [video]="video" [nsfw]="isVideoNSFWForThisUser()"></my-video-thumbnail>
|
||||
<my-video-thumbnail [video]="video" [nsfw]="isVideoBlur()"></my-video-thumbnail>
|
||||
|
||||
<div class="video-miniature-information">
|
||||
<span class="video-miniature-name">
|
||||
<a
|
||||
class="video-miniature-name"
|
||||
[routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoNSFWForThisUser() }"
|
||||
[routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }"
|
||||
>
|
||||
{{ video.name }}
|
||||
</a>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
import { User } from '../users'
|
||||
import { Video } from './video.model'
|
||||
import { ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-miniature',
|
||||
|
@ -11,7 +12,9 @@ export class VideoMiniatureComponent {
|
|||
@Input() user: User
|
||||
@Input() video: Video
|
||||
|
||||
isVideoNSFWForThisUser () {
|
||||
return this.video.isVideoNSFWForUser(this.user)
|
||||
constructor (private serverService: ServerService) { }
|
||||
|
||||
isVideoBlur () {
|
||||
return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Account } from '@app/shared/account/account.model'
|
||||
import { User } from '../'
|
||||
import { Video as VideoServerModel } from '../../../../../shared'
|
||||
import { Video as VideoServerModel, VideoPrivacy } from '../../../../../shared'
|
||||
import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
|
||||
import { VideoConstant } from '../../../../../shared/models/videos/video.model'
|
||||
import { getAbsoluteAPIUrl } from '../misc/utils'
|
||||
import { ServerConfig } from '../../../../../shared/models'
|
||||
|
||||
export class Video implements VideoServerModel {
|
||||
by: string
|
||||
|
@ -13,6 +14,7 @@ export class Video implements VideoServerModel {
|
|||
category: VideoConstant<number>
|
||||
licence: VideoConstant<number>
|
||||
language: VideoConstant<number>
|
||||
privacy: VideoConstant<VideoPrivacy>
|
||||
description: string
|
||||
duration: number
|
||||
durationLabel: string
|
||||
|
@ -61,6 +63,7 @@ export class Video implements VideoServerModel {
|
|||
this.category = hash.category
|
||||
this.licence = hash.licence
|
||||
this.language = hash.language
|
||||
this.privacy = hash.privacy
|
||||
this.description = hash.description
|
||||
this.duration = hash.duration
|
||||
this.durationLabel = Video.createDurationString(hash.duration)
|
||||
|
@ -83,8 +86,14 @@ export class Video implements VideoServerModel {
|
|||
this.by = Account.CREATE_BY_STRING(hash.account.name, hash.account.host)
|
||||
}
|
||||
|
||||
isVideoNSFWForUser (user: User) {
|
||||
// If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
|
||||
return (this.nsfw && (!user || user.displayNSFW === false))
|
||||
isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
|
||||
// Video is not NSFW, skip
|
||||
if (this.nsfw === false) return false
|
||||
|
||||
// Return user setting if logged in
|
||||
if (user) return user.nsfwPolicy !== 'display'
|
||||
|
||||
// Return default instance config
|
||||
return serverConfig.instance.defaultNSFWPolicy !== 'display'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
<input type="checkbox" id="nsfw" formControlName="nsfw" />
|
||||
<label for="nsfw"></label>
|
||||
<label for="nsfw">This video contains mature or explicit content</label>
|
||||
<my-help tooltipPlacement="top" helpType="custom" customHtml="Some instances do not list NSFW videos by default."></my-help>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-group-checkbox">
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
@include peertube-select-disabled-container(auto);
|
||||
}
|
||||
|
||||
.form-group-checkbox {
|
||||
my-help { margin-left: 5px }
|
||||
}
|
||||
|
||||
.video-edit {
|
||||
height: 100%;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { VideoDownloadComponent } from './modal/video-download.component'
|
|||
import { VideoReportComponent } from './modal/video-report.component'
|
||||
import { VideoShareComponent } from './modal/video-share.component'
|
||||
import { getVideojsOptions } from '../../../assets/player/peertube-player'
|
||||
import { ServerService } from '@app/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch',
|
||||
|
@ -66,6 +67,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
private confirmService: ConfirmService,
|
||||
private metaService: MetaService,
|
||||
private authService: AuthService,
|
||||
private serverService: ServerService,
|
||||
private notificationsService: NotificationsService,
|
||||
private markdownService: MarkdownService,
|
||||
private zone: NgZone,
|
||||
|
@ -335,7 +337,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.updateOtherVideosDisplayed()
|
||||
|
||||
if (this.video.isVideoNSFWForUser(this.user)) {
|
||||
if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
|
||||
const res = await this.confirmService.confirm(
|
||||
'This video contains mature or explicit content. Are you sure you want to watch it?',
|
||||
'Mature or explicit content'
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
|
||||
<body>
|
||||
|
||||
<div id="error-block">
|
||||
<h1 id="error-title">Sorry</h1>
|
||||
|
||||
<div id="error-content"></div>
|
||||
</div>
|
||||
|
||||
<video id="video-container" class="video-js vjs-peertube-skin">
|
||||
</video>
|
||||
|
||||
|
|
|
@ -4,6 +4,16 @@
|
|||
@import '~videojs-dock/dist/videojs-dock.css';
|
||||
@import '../../sass/video-js-custom.scss';
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $main-fonts;
|
||||
font-weight: $font-regular;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 99%;
|
||||
}
|
||||
|
@ -43,3 +53,38 @@ html, body {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#error-block {
|
||||
display: none;
|
||||
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background-color: #141313;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
|
||||
#error-title {
|
||||
font-size: 45px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#error-content {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 300px) {
|
||||
#error-block {
|
||||
font-size: 36px;
|
||||
|
||||
#error-content {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,19 +9,53 @@ function getVideoUrl (id: string) {
|
|||
return window.location.origin + '/api/v1/videos/' + id
|
||||
}
|
||||
|
||||
async function loadVideoInfo (videoId: string): Promise<VideoDetails> {
|
||||
const response = await fetch(getVideoUrl(videoId))
|
||||
return response.json()
|
||||
function loadVideoInfo (videoId: string): Promise<Response> {
|
||||
return fetch(getVideoUrl(videoId))
|
||||
}
|
||||
|
||||
function removeElement (element: HTMLElement) {
|
||||
element.parentElement.removeChild(element)
|
||||
}
|
||||
|
||||
function displayError (videoElement: HTMLVideoElement, text: string) {
|
||||
// Remove video element
|
||||
removeElement(videoElement)
|
||||
|
||||
document.title = 'Sorry - ' + text
|
||||
|
||||
const errorBlock = document.getElementById('error-block')
|
||||
errorBlock.style.display = 'flex'
|
||||
|
||||
const errorText = document.getElementById('error-content')
|
||||
errorText.innerHTML = text
|
||||
}
|
||||
|
||||
function videoNotFound (videoElement: HTMLVideoElement) {
|
||||
const text = 'This video does not exist.'
|
||||
displayError(videoElement, text)
|
||||
}
|
||||
|
||||
function videoFetchError (videoElement: HTMLVideoElement) {
|
||||
const text = 'We cannot fetch the video. Please try again later.'
|
||||
displayError(videoElement, text)
|
||||
}
|
||||
|
||||
const urlParts = window.location.href.split('/')
|
||||
const videoId = urlParts[urlParts.length - 1]
|
||||
|
||||
loadVideoInfo(videoId)
|
||||
.then(videoInfo => {
|
||||
.then(async response => {
|
||||
const videoContainerId = 'video-container'
|
||||
|
||||
const videoElement = document.getElementById(videoContainerId) as HTMLVideoElement
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) return videoNotFound(videoElement)
|
||||
|
||||
return videoFetchError(videoElement)
|
||||
}
|
||||
|
||||
const videoInfo: VideoDetails = await response.json()
|
||||
|
||||
let autoplay = false
|
||||
let startTime = 0
|
||||
|
||||
|
|
|
@ -84,6 +84,9 @@ instance:
|
|||
description: 'Welcome to this PeerTube instance!' # Support markdown
|
||||
terms: 'No terms for now.' # Support markdown
|
||||
default_client_route: '/videos/trending'
|
||||
# By default, "do_not_list" or "blur" or "display" NSFW videos
|
||||
# Could be overridden per user with a setting
|
||||
default_nsfw_policy: 'do_not_list'
|
||||
customizations:
|
||||
javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
|
||||
css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
|
||||
|
|
|
@ -100,6 +100,9 @@ instance:
|
|||
description: '' # Support markdown
|
||||
terms: '' # Support markdown
|
||||
default_client_route: '/videos/trending'
|
||||
# By default, "do_not_list" or "blur" or "display" NSFW videos
|
||||
# Could be overridden per user with a setting
|
||||
default_nsfw_policy: 'do_not_list'
|
||||
customizations:
|
||||
javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
|
||||
css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
|
||||
|
|
|
@ -32,3 +32,6 @@ transcoding:
|
|||
480p: true
|
||||
720p: true
|
||||
1080p: true
|
||||
|
||||
instance:
|
||||
default_nsfw_policy: 'display'
|
|
@ -47,6 +47,7 @@
|
|||
"nodemon": "nodemon",
|
||||
"ts-node": "ts-node",
|
||||
"tslint": "tslint",
|
||||
"mocha": "mocha",
|
||||
"travis": "scripty",
|
||||
"release": "scripty",
|
||||
"client-report": "scripty"
|
||||
|
|
|
@ -46,6 +46,7 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
|
|||
name: CONFIG.INSTANCE.NAME,
|
||||
shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
|
||||
defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
|
||||
defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||
customizations: {
|
||||
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
|
||||
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
|
||||
|
@ -128,6 +129,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
|
|||
toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
|
||||
toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
|
||||
toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
|
||||
toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy
|
||||
|
||||
await writeFilePromise(CONFIG.CUSTOM_FILE, JSON.stringify(toUpdateJSON, undefined, 2))
|
||||
|
||||
|
@ -153,6 +155,7 @@ function customConfig (): CustomConfig {
|
|||
description: CONFIG.INSTANCE.DESCRIPTION,
|
||||
terms: CONFIG.INSTANCE.TERMS,
|
||||
defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
|
||||
defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||
customizations: {
|
||||
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
|
||||
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
|
||||
|
|
|
@ -42,6 +42,7 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
|||
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'
|
||||
|
||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
|
||||
const loginRateLimiter = new RateLimit({
|
||||
|
@ -161,7 +162,13 @@ export {
|
|||
|
||||
async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const user = res.locals.oauth.token.User as UserModel
|
||||
const resultList = await VideoModel.listAccountVideosForApi(user.Account.id ,req.query.start, req.query.count, req.query.sort)
|
||||
const resultList = await VideoModel.listAccountVideosForApi(
|
||||
user.Account.id,
|
||||
req.query.start as number,
|
||||
req.query.count as number,
|
||||
req.query.sort as VideoSortField,
|
||||
false // Display my NSFW videos
|
||||
)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
@ -188,7 +195,7 @@ async function createUser (req: express.Request) {
|
|||
username: body.username,
|
||||
password: body.password,
|
||||
email: body.email,
|
||||
displayNSFW: false,
|
||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||
autoPlayVideo: true,
|
||||
role: body.role,
|
||||
videoQuota: body.videoQuota
|
||||
|
@ -219,7 +226,7 @@ async function registerUser (req: express.Request) {
|
|||
username: body.username,
|
||||
password: body.password,
|
||||
email: body.email,
|
||||
displayNSFW: false,
|
||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||
autoPlayVideo: true,
|
||||
role: UserRole.USER,
|
||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
||||
|
@ -286,7 +293,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
|
|||
|
||||
if (body.password !== undefined) user.password = body.password
|
||||
if (body.email !== undefined) user.email = body.email
|
||||
if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
|
||||
if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
|
||||
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
|
|
|
@ -19,13 +19,18 @@ 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'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
optionalAuthenticate,
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
setDefaultSort,
|
||||
|
@ -44,6 +49,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'
|
||||
|
||||
const videosRouter = express.Router()
|
||||
|
||||
|
@ -81,6 +89,7 @@ videosRouter.get('/',
|
|||
videosSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
optionalAuthenticate,
|
||||
asyncMiddleware(listVideos)
|
||||
)
|
||||
videosRouter.get('/search',
|
||||
|
@ -89,6 +98,7 @@ videosRouter.get('/search',
|
|||
videosSortValidator,
|
||||
setDefaultSort,
|
||||
setDefaultPagination,
|
||||
optionalAuthenticate,
|
||||
asyncMiddleware(searchVideos)
|
||||
)
|
||||
videosRouter.put('/:id',
|
||||
|
@ -391,7 +401,13 @@ async function getVideoDescription (req: express.Request, res: express.Response)
|
|||
}
|
||||
|
||||
async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.filter)
|
||||
const resultList = await VideoModel.listForApi(
|
||||
req.query.start as number,
|
||||
req.query.count as number,
|
||||
req.query.sort as VideoSortField,
|
||||
isNSFWHidden(res),
|
||||
req.query.filter as VideoFilter
|
||||
)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
@ -419,11 +435,21 @@ async function removeVideo (req: express.Request, res: express.Response) {
|
|||
|
||||
async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const resultList = await VideoModel.searchAndPopulateAccountAndServer(
|
||||
req.query.search,
|
||||
req.query.start,
|
||||
req.query.count,
|
||||
req.query.sort
|
||||
req.query.search as string,
|
||||
req.query.start as number,
|
||||
req.query.count as number,
|
||||
req.query.sort as VideoSortField,
|
||||
isNSFWHidden(res)
|
||||
)
|
||||
|
||||
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'
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as Feed from 'pfeed'
|
|||
import { ResultList } from '../../shared/models'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { cacheRoute } from '../middlewares/cache'
|
||||
import { VideoSortField } from '../../client/src/app/shared/video/sort-field.type'
|
||||
|
||||
const feedsRouter = express.Router()
|
||||
|
||||
|
@ -31,20 +32,22 @@ async function generateFeed (req: express.Request, res: express.Response, next:
|
|||
|
||||
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,
|
||||
true
|
||||
req.query.sort as VideoSortField,
|
||||
hideNSFW
|
||||
)
|
||||
} else {
|
||||
resultList = await VideoModel.listForApi(
|
||||
start,
|
||||
FEEDS.COUNT,
|
||||
req.query.sort,
|
||||
req.query.sort as VideoSortField,
|
||||
hideNSFW,
|
||||
req.query.filter,
|
||||
true
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'express-validator'
|
||||
import * as validator from 'validator'
|
||||
import { UserRole } from '../../../shared'
|
||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers'
|
||||
|
||||
import { exists, isFileValid } from './misc'
|
||||
import { values } from 'lodash'
|
||||
|
||||
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
|
||||
|
||||
|
@ -29,8 +30,9 @@ function isBoolean (value: any) {
|
|||
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
|
||||
}
|
||||
|
||||
function isUserDisplayNSFWValid (value: any) {
|
||||
return isBoolean(value)
|
||||
const nsfwPolicies = values(NSFW_POLICY_TYPES)
|
||||
function isUserNSFWPolicyValid (value: any) {
|
||||
return exists(value) && nsfwPolicies.indexOf(value) !== -1
|
||||
}
|
||||
|
||||
function isUserAutoPlayVideoValid (value: any) {
|
||||
|
@ -56,7 +58,7 @@ export {
|
|||
isUserRoleValid,
|
||||
isUserVideoQuotaValid,
|
||||
isUserUsernameValid,
|
||||
isUserDisplayNSFWValid,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserDescriptionValid,
|
||||
isAvatarFile
|
||||
|
|
|
@ -5,12 +5,12 @@ import { ApplicationModel } from '../models/application/application'
|
|||
import { OAuthClientModel } from '../models/oauth/oauth-client'
|
||||
|
||||
// Some checks on configuration files
|
||||
// Return an error message, or null if everything is okay
|
||||
function checkConfig () {
|
||||
if (config.has('webserver.host')) {
|
||||
let errorMessage = '`host` config key was renamed to `hostname` but it seems you still have a `host` key in your configuration files!'
|
||||
errorMessage += ' Please ensure to rename your `host` configuration to `hostname`.'
|
||||
const defaultNSFWPolicy = config.get<string>('instance.default_nsfw_policy')
|
||||
|
||||
return errorMessage
|
||||
if ([ 'do_not_list', 'blur', 'display' ].indexOf(defaultNSFWPolicy) === -1) {
|
||||
return 'NSFW policy setting should be "do_not_list" or "blur" or "display" instead of ' + defaultNSFWPolicy
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -28,7 +28,8 @@ function checkMissedConfig () {
|
|||
'log.level',
|
||||
'user.video_quota',
|
||||
'cache.previews.size', 'admin.email', 'signup.enabled', 'signup.limit', 'transcoding.enabled', 'transcoding.threads',
|
||||
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route'
|
||||
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
||||
'instance.default_nsfw_policy'
|
||||
]
|
||||
const miss: string[] = []
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@ import { FollowState } from '../../shared/models/actors'
|
|||
import { VideoPrivacy } from '../../shared/models/videos'
|
||||
// Do not use barrels, remain constants as independent as possible
|
||||
import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
|
||||
import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
|
||||
|
||||
// Use a variable to reload the configuration if we need
|
||||
let config: IConfig = require('config')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 200
|
||||
const LAST_MIGRATION_VERSION = 205
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -167,6 +168,7 @@ const CONFIG = {
|
|||
get DESCRIPTION () { return config.get<string>('instance.description') },
|
||||
get TERMS () { return config.get<string>('instance.terms') },
|
||||
get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
|
||||
get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
|
||||
CUSTOMIZATIONS: {
|
||||
get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
|
||||
get CSS () { return config.get<string>('instance.customizations.css') }
|
||||
|
@ -378,6 +380,12 @@ const BCRYPT_SALT_SIZE = 10
|
|||
|
||||
const USER_PASSWORD_RESET_LIFETIME = 60000 * 5 // 5 minutes
|
||||
|
||||
const NSFW_POLICY_TYPES: { [ id: string]: NSFWPolicyType } = {
|
||||
DO_NOT_LIST: 'do_not_list',
|
||||
BLUR: 'blur',
|
||||
DISPLAY: 'display'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Express static paths (router)
|
||||
|
@ -474,6 +482,7 @@ export {
|
|||
PRIVATE_RSA_KEY_SIZE,
|
||||
SORTABLE_COLUMNS,
|
||||
FEEDS,
|
||||
NSFW_POLICY_TYPES,
|
||||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS,
|
||||
ACTIVITY_PUB,
|
||||
|
|
|
@ -120,6 +120,7 @@ async function createOAuthAdminIfNotExist () {
|
|||
email,
|
||||
password,
|
||||
role,
|
||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||
videoQuota: -1
|
||||
}
|
||||
const user = new UserModel(userData)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.ENUM('do_not_list', 'blur', 'display'),
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
await utils.queryInterface.addColumn('user', 'nsfwPolicy', data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "user" SET "nsfwPolicy" = \'do_not_list\''
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "user" SET "nsfwPolicy" = \'display\' WHERE "displayNSFW" = true'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'ALTER TABLE "user" ALTER COLUMN "nsfwPolicy" SET NOT NULL'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.removeColumn('user', 'displayNSFW')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -2,6 +2,7 @@ import * as express from 'express'
|
|||
import * as OAuthServer from 'express-oauth-server'
|
||||
import 'express-validator'
|
||||
import { OAUTH_LIFETIME } from '../initializers'
|
||||
import { logger } from '../helpers/logger'
|
||||
|
||||
const oAuthServer = new OAuthServer({
|
||||
useErrorHandler: true,
|
||||
|
@ -13,6 +14,8 @@ const oAuthServer = new OAuthServer({
|
|||
function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
oAuthServer.authenticate()(req, res, err => {
|
||||
if (err) {
|
||||
logger.warn('Cannot authenticate.', { err })
|
||||
|
||||
return res.status(err.status)
|
||||
.json({
|
||||
error: 'Token is invalid.',
|
||||
|
@ -25,6 +28,12 @@ function authenticate (req: express.Request, res: express.Response, next: expres
|
|||
})
|
||||
}
|
||||
|
||||
function optionalAuthenticate (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (req.header('authorization')) return authenticate(req, res, next)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function token (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
return oAuthServer.token()(req, res, err => {
|
||||
if (err) {
|
||||
|
@ -44,5 +53,6 @@ function token (req: express.Request, res: express.Response, next: express.NextF
|
|||
|
||||
export {
|
||||
authenticate,
|
||||
optionalAuthenticate,
|
||||
token
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as express from 'express'
|
||||
import { body } from 'express-validator/check'
|
||||
import { isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
||||
import { isUserNSFWPolicyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
||||
|
@ -9,6 +9,7 @@ const customConfigUpdateValidator = [
|
|||
body('instance.description').exists().withMessage('Should have a valid instance description'),
|
||||
body('instance.terms').exists().withMessage('Should have a valid instance terms'),
|
||||
body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'),
|
||||
body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'),
|
||||
body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'),
|
||||
body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'),
|
||||
body('cache.previews.size').isInt().withMessage('Should have a valid previews size'),
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
isAvatarFile,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserDescriptionValid,
|
||||
isUserDisplayNSFWValid,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserPasswordValid,
|
||||
isUserRoleValid,
|
||||
isUserUsernameValid,
|
||||
|
@ -101,7 +101,7 @@ const usersUpdateMeValidator = [
|
|||
body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
|
||||
body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
||||
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
||||
body('displayNSFW').optional().custom(isUserDisplayNSFWValid).withMessage('Should have a valid display Not Safe For Work attribute'),
|
||||
body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
|
||||
body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
|
|
@ -21,7 +21,7 @@ import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
|
|||
import { User, UserRole } from '../../../shared/models/users'
|
||||
import {
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserDisplayNSFWValid,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserPasswordValid,
|
||||
isUserRoleValid,
|
||||
isUserUsernameValid,
|
||||
|
@ -32,6 +32,9 @@ import { OAuthTokenModel } from '../oauth/oauth-token'
|
|||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { VideoChannelModel } from '../video/video-channel'
|
||||
import { AccountModel } from './account'
|
||||
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
|
||||
import { values } from 'lodash'
|
||||
import { NSFW_POLICY_TYPES } from '../../initializers'
|
||||
|
||||
@DefaultScope({
|
||||
include: [
|
||||
|
@ -83,10 +86,9 @@ export class UserModel extends Model<UserModel> {
|
|||
email: string
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(false)
|
||||
@Is('UserDisplayNSFW', value => throwIfNotValid(value, isUserDisplayNSFWValid, 'display NSFW boolean'))
|
||||
@Column
|
||||
displayNSFW: boolean
|
||||
@Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
|
||||
@Column(DataType.ENUM(values(NSFW_POLICY_TYPES)))
|
||||
nsfwPolicy: NSFWPolicyType
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(true)
|
||||
|
@ -265,7 +267,7 @@ export class UserModel extends Model<UserModel> {
|
|||
id: this.id,
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
displayNSFW: this.displayNSFW,
|
||||
nsfwPolicy: this.nsfwPolicy,
|
||||
autoPlayVideo: this.autoPlayVideo,
|
||||
role: this.role,
|
||||
roleLabel: USER_ROLE_LABELS[ this.role ],
|
||||
|
|
|
@ -95,7 +95,7 @@ enum ScopeNames {
|
|||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter, withFiles?: boolean) => {
|
||||
[ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean) => {
|
||||
const query: IFindOptions<VideoModel> = {
|
||||
where: {
|
||||
id: {
|
||||
|
@ -161,6 +161,11 @@ enum ScopeNames {
|
|||
})
|
||||
}
|
||||
|
||||
// Hide nsfw videos?
|
||||
if (hideNSFW === true) {
|
||||
query.where['nsfw'] = false
|
||||
}
|
||||
|
||||
return query
|
||||
},
|
||||
[ScopeNames.WITH_ACCOUNT_DETAILS]: {
|
||||
|
@ -640,7 +645,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
})
|
||||
}
|
||||
|
||||
static listAccountVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
|
||||
static listAccountVideosForApi (accountId: number, start: number, count: number, sort: string, hideNSFW: boolean, withFiles = false) {
|
||||
const query: IFindOptions<VideoModel> = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
|
@ -669,6 +674,12 @@ export class VideoModel extends Model<VideoModel> {
|
|||
})
|
||||
}
|
||||
|
||||
if (hideNSFW === true) {
|
||||
query.where = {
|
||||
nsfw: false
|
||||
}
|
||||
}
|
||||
|
||||
return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
|
@ -677,7 +688,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
})
|
||||
}
|
||||
|
||||
static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter, withFiles = false) {
|
||||
static async listForApi (start: number, count: number, sort: string, hideNSFW: boolean, filter?: VideoFilter, withFiles = false) {
|
||||
const query = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
|
@ -685,8 +696,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
}
|
||||
|
||||
const serverActor = await getServerActor()
|
||||
|
||||
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter, withFiles ] })
|
||||
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] })
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
|
@ -696,7 +706,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
})
|
||||
}
|
||||
|
||||
static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string) {
|
||||
static async searchAndPopulateAccountAndServer (value: string, start: number, count: number, sort: string, hideNSFW: boolean) {
|
||||
const query: IFindOptions<VideoModel> = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
|
@ -724,7 +734,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
|
||||
const serverActor = await getServerActor()
|
||||
|
||||
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] })
|
||||
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW ] })
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return {
|
||||
|
@ -874,6 +884,13 @@ export class VideoModel extends Model<VideoModel> {
|
|||
return languageLabel
|
||||
}
|
||||
|
||||
private static getPrivacyLabel (id: number) {
|
||||
let privacyLabel = VIDEO_PRIVACIES[id]
|
||||
if (!privacyLabel) privacyLabel = 'Unknown'
|
||||
|
||||
return privacyLabel
|
||||
}
|
||||
|
||||
getOriginalFile () {
|
||||
if (Array.isArray(this.VideoFiles) === false) return undefined
|
||||
|
||||
|
@ -927,8 +944,11 @@ export class VideoModel extends Model<VideoModel> {
|
|||
return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
|
||||
}
|
||||
|
||||
createTorrentAndSetInfoHash = async function (videoFile: VideoFileModel) {
|
||||
async createTorrentAndSetInfoHash (videoFile: VideoFileModel) {
|
||||
const options = {
|
||||
// Keep the extname, it's used by the client to stream the file inside a web browser
|
||||
name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`,
|
||||
createdBy: 'PeerTube',
|
||||
announceList: [
|
||||
[ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ],
|
||||
[ CONFIG.WEBSERVER.URL + '/tracker/announce' ]
|
||||
|
@ -980,6 +1000,10 @@ export class VideoModel extends Model<VideoModel> {
|
|||
id: this.language,
|
||||
label: VideoModel.getLanguageLabel(this.language)
|
||||
},
|
||||
privacy: {
|
||||
id: this.privacy,
|
||||
label: VideoModel.getPrivacyLabel(this.privacy)
|
||||
},
|
||||
nsfw: this.nsfw,
|
||||
description: this.getTruncatedDescription(),
|
||||
isLocal: this.isOwned(),
|
||||
|
@ -1006,15 +1030,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
toFormattedDetailsJSON (): VideoDetails {
|
||||
const formattedJson = this.toFormattedJSON()
|
||||
|
||||
// Maybe our server is not up to date and there are new privacy settings since our version
|
||||
let privacyLabel = VIDEO_PRIVACIES[this.privacy]
|
||||
if (!privacyLabel) privacyLabel = 'Unknown'
|
||||
|
||||
const detailsJson = {
|
||||
privacy: {
|
||||
id: this.privacy,
|
||||
label: privacyLabel
|
||||
},
|
||||
support: this.support,
|
||||
descriptionPath: this.getDescriptionPath(),
|
||||
channel: this.VideoChannel.toFormattedJSON(),
|
||||
|
@ -1227,7 +1243,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
return peertubeTruncate(this.description, maxLength)
|
||||
}
|
||||
|
||||
optimizeOriginalVideofile = async function () {
|
||||
async optimizeOriginalVideofile () {
|
||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||
const newExtname = '.mp4'
|
||||
const inputVideoFile = this.getOriginalFile()
|
||||
|
@ -1264,7 +1280,7 @@ export class VideoModel extends Model<VideoModel> {
|
|||
}
|
||||
}
|
||||
|
||||
transcodeOriginalVideofile = async function (resolution: VideoResolution, isPortraitMode: boolean) {
|
||||
async transcodeOriginalVideofile (resolution: VideoResolution, isPortraitMode: boolean) {
|
||||
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
|
||||
const extname = '.mp4'
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { CustomConfig } from '../../../../shared/models/server/custom-config.mod
|
|||
|
||||
import {
|
||||
createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
|
||||
setAccessTokensToServers, userLogin
|
||||
setAccessTokensToServers, userLogin, immutableAssign
|
||||
} from '../../utils'
|
||||
|
||||
describe('Test config API validators', function () {
|
||||
|
@ -20,6 +20,7 @@ describe('Test config API validators', function () {
|
|||
description: 'my super description',
|
||||
terms: 'my super terms',
|
||||
defaultClientRoute: '/videos/recently-added',
|
||||
defaultNSFWPolicy: 'blur',
|
||||
customizations: {
|
||||
javascript: 'alert("coucou")',
|
||||
css: 'body { background-color: red; }'
|
||||
|
@ -122,6 +123,22 @@ describe('Test config API validators', function () {
|
|||
})
|
||||
})
|
||||
|
||||
it('Should fail with a bad default NSFW policy', async function () {
|
||||
const newUpdateParams = immutableAssign(updateParams, {
|
||||
instance: {
|
||||
defaultNSFWPolicy: 'hello'
|
||||
}
|
||||
})
|
||||
|
||||
await makePutBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: newUpdateParams,
|
||||
token: server.accessToken,
|
||||
statusCodeExpected: 400
|
||||
})
|
||||
})
|
||||
|
||||
it('Should success with the correct parameters', async function () {
|
||||
await makePutBodyRequest({
|
||||
url: server.url,
|
||||
|
|
|
@ -231,9 +231,9 @@ describe('Test users API validators', function () {
|
|||
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
|
||||
})
|
||||
|
||||
it('Should fail with an invalid display NSFW attribute', async function () {
|
||||
it('Should fail with an invalid NSFW policy attribute', async function () {
|
||||
const fields = {
|
||||
displayNSFW: -1
|
||||
nsfwPolicy: 'hello'
|
||||
}
|
||||
|
||||
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
|
||||
|
@ -266,7 +266,7 @@ describe('Test users API validators', function () {
|
|||
it('Should succeed with the correct params', async function () {
|
||||
const fields = {
|
||||
password: 'my super password',
|
||||
displayNSFW: true,
|
||||
nsfwPolicy: 'blur',
|
||||
autoPlayVideo: false,
|
||||
email: 'super_email@example.com'
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import './videos/video-abuse'
|
|||
import './videos/video-blacklist'
|
||||
import './videos/video-blacklist-management'
|
||||
import './videos/video-description'
|
||||
import './videos/video-nsfw'
|
||||
import './videos/video-privacy'
|
||||
import './videos/services'
|
||||
import './server/email'
|
||||
|
|
|
@ -59,6 +59,7 @@ describe('Test config', function () {
|
|||
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
|
||||
expect(data.instance.terms).to.equal('No terms for now.')
|
||||
expect(data.instance.defaultClientRoute).to.equal('/videos/trending')
|
||||
expect(data.instance.defaultNSFWPolicy).to.equal('display')
|
||||
expect(data.instance.customizations.css).to.be.empty
|
||||
expect(data.instance.customizations.javascript).to.be.empty
|
||||
expect(data.cache.previews.size).to.equal(1)
|
||||
|
@ -83,6 +84,7 @@ describe('Test config', function () {
|
|||
description: 'my super description',
|
||||
terms: 'my super terms',
|
||||
defaultClientRoute: '/videos/recently-added',
|
||||
defaultNSFWPolicy: 'blur' as 'blur',
|
||||
customizations: {
|
||||
javascript: 'alert("coucou")',
|
||||
css: 'body { background-color: red; }'
|
||||
|
@ -125,6 +127,7 @@ describe('Test config', function () {
|
|||
expect(data.instance.description).to.equal('my super description')
|
||||
expect(data.instance.terms).to.equal('my super terms')
|
||||
expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added')
|
||||
expect(data.instance.defaultNSFWPolicy).to.equal('blur')
|
||||
expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
|
||||
expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
|
||||
expect(data.cache.previews.size).to.equal(2)
|
||||
|
@ -156,6 +159,7 @@ describe('Test config', function () {
|
|||
expect(data.instance.description).to.equal('my super description')
|
||||
expect(data.instance.terms).to.equal('my super terms')
|
||||
expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added')
|
||||
expect(data.instance.defaultNSFWPolicy).to.equal('blur')
|
||||
expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
|
||||
expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
|
||||
expect(data.cache.previews.size).to.equal(2)
|
||||
|
@ -198,6 +202,7 @@ describe('Test config', function () {
|
|||
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
|
||||
expect(data.instance.terms).to.equal('No terms for now.')
|
||||
expect(data.instance.defaultClientRoute).to.equal('/videos/trending')
|
||||
expect(data.instance.defaultNSFWPolicy).to.equal('display')
|
||||
expect(data.instance.customizations.css).to.be.empty
|
||||
expect(data.instance.customizations.javascript).to.be.empty
|
||||
expect(data.cache.previews.size).to.equal(1)
|
||||
|
|
|
@ -168,7 +168,7 @@ describe('Test users', function () {
|
|||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('user_1@example.com')
|
||||
expect(user.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.roleLabel).to.equal('User')
|
||||
expect(user.id).to.be.a('number')
|
||||
|
@ -215,12 +215,12 @@ describe('Test users', function () {
|
|||
const user = users[ 0 ]
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('user_1@example.com')
|
||||
expect(user.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
|
||||
const rootUser = users[ 1 ]
|
||||
expect(rootUser.username).to.equal('root')
|
||||
expect(rootUser.email).to.equal('admin1@example.com')
|
||||
expect(rootUser.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
|
||||
userId = user.id
|
||||
})
|
||||
|
@ -239,7 +239,7 @@ describe('Test users', function () {
|
|||
expect(user.username).to.equal('root')
|
||||
expect(user.email).to.equal('admin1@example.com')
|
||||
expect(user.roleLabel).to.equal('Administrator')
|
||||
expect(user.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
})
|
||||
|
||||
it('Should list only the first user by username desc', async function () {
|
||||
|
@ -254,7 +254,7 @@ describe('Test users', function () {
|
|||
const user = users[ 0 ]
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('user_1@example.com')
|
||||
expect(user.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
})
|
||||
|
||||
it('Should list only the second user by createdAt desc', async function () {
|
||||
|
@ -269,7 +269,7 @@ describe('Test users', function () {
|
|||
const user = users[ 0 ]
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('user_1@example.com')
|
||||
expect(user.displayNSFW).to.be.false
|
||||
expect(user.nsfwPolicy).to.equal('display')
|
||||
})
|
||||
|
||||
it('Should list all the users by createdAt asc', async function () {
|
||||
|
@ -283,11 +283,11 @@ describe('Test users', function () {
|
|||
|
||||
expect(users[ 0 ].username).to.equal('root')
|
||||
expect(users[ 0 ].email).to.equal('admin1@example.com')
|
||||
expect(users[ 0 ].displayNSFW).to.be.false
|
||||
expect(users[ 0 ].nsfwPolicy).to.equal('display')
|
||||
|
||||
expect(users[ 1 ].username).to.equal('user_1')
|
||||
expect(users[ 1 ].email).to.equal('user_1@example.com')
|
||||
expect(users[ 1 ].displayNSFW).to.be.false
|
||||
expect(users[ 1 ].nsfwPolicy).to.equal('display')
|
||||
})
|
||||
|
||||
it('Should update my password', async function () {
|
||||
|
@ -305,7 +305,7 @@ describe('Test users', function () {
|
|||
await updateMyUser({
|
||||
url: server.url,
|
||||
accessToken: accessTokenUser,
|
||||
displayNSFW: true
|
||||
nsfwPolicy: 'do_not_list'
|
||||
})
|
||||
|
||||
const res = await getMyUserInformation(server.url, accessTokenUser)
|
||||
|
@ -313,7 +313,7 @@ describe('Test users', function () {
|
|||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('user_1@example.com')
|
||||
expect(user.displayNSFW).to.be.ok
|
||||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.description).to.be.null
|
||||
|
@ -344,7 +344,7 @@ describe('Test users', function () {
|
|||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('updated@example.com')
|
||||
expect(user.displayNSFW).to.be.ok
|
||||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.description).to.be.null
|
||||
|
@ -377,7 +377,7 @@ describe('Test users', function () {
|
|||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('updated@example.com')
|
||||
expect(user.displayNSFW).to.be.ok
|
||||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.description).to.equal('my super description updated')
|
||||
|
@ -398,7 +398,7 @@ describe('Test users', function () {
|
|||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('updated2@example.com')
|
||||
expect(user.displayNSFW).to.be.ok
|
||||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(42)
|
||||
expect(user.roleLabel).to.equal('Moderator')
|
||||
expect(user.id).to.be.a('number')
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/* tslint:disable:no-unused-expression */
|
||||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import { flushTests, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils/index'
|
||||
import { userLogin } from '../../utils/users/login'
|
||||
import { createUser } from '../../utils/users/users'
|
||||
import { getMyVideos } from '../../utils/videos/videos'
|
||||
import {
|
||||
getConfig, getCustomConfig,
|
||||
getMyUserInformation,
|
||||
getVideosListWithToken,
|
||||
runServer,
|
||||
searchVideo,
|
||||
searchVideoWithToken, updateCustomConfig,
|
||||
updateMyUser
|
||||
} from '../../utils'
|
||||
import { ServerConfig } from '../../../../shared/models'
|
||||
import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Test video NSFW policy', function () {
|
||||
let server: ServerInfo
|
||||
let userAccessToken: string
|
||||
let customConfig: CustomConfig
|
||||
|
||||
before(async function () {
|
||||
this.timeout(50000)
|
||||
|
||||
await flushTests()
|
||||
server = await runServer(1)
|
||||
|
||||
// Get the access tokens
|
||||
await setAccessTokensToServers([ server ])
|
||||
|
||||
{
|
||||
const attributes = { name: 'nsfw', nsfw: true }
|
||||
await uploadVideo(server.url, server.accessToken, attributes)
|
||||
}
|
||||
|
||||
{
|
||||
const attributes = { name: 'normal', nsfw: false }
|
||||
await uploadVideo(server.url, server.accessToken, attributes)
|
||||
}
|
||||
|
||||
{
|
||||
const res = await getCustomConfig(server.url, server.accessToken)
|
||||
customConfig = res.body
|
||||
}
|
||||
})
|
||||
|
||||
describe('Instance default NSFW policy', function () {
|
||||
it('Should display NSFW videos with display default NSFW policy', async function () {
|
||||
const resConfig = await getConfig(server.url)
|
||||
const serverConfig: ServerConfig = resConfig.body
|
||||
expect(serverConfig.instance.defaultNSFWPolicy).to.equal('display')
|
||||
|
||||
for (const res of [ await getVideosList(server.url), await searchVideo(server.url, 'n') ]) {
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
expect(videos[ 1 ].name).to.equal('nsfw')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not display NSFW videos with do_not_list default NSFW policy', async function () {
|
||||
customConfig.instance.defaultNSFWPolicy = 'do_not_list'
|
||||
await updateCustomConfig(server.url, server.accessToken, customConfig)
|
||||
|
||||
const resConfig = await getConfig(server.url)
|
||||
const serverConfig: ServerConfig = resConfig.body
|
||||
expect(serverConfig.instance.defaultNSFWPolicy).to.equal('do_not_list')
|
||||
|
||||
for (const res of [ await getVideosList(server.url), await searchVideo(server.url, 'n') ]) {
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(1)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should display NSFW videos with blur default NSFW policy', async function () {
|
||||
customConfig.instance.defaultNSFWPolicy = 'blur'
|
||||
await updateCustomConfig(server.url, server.accessToken, customConfig)
|
||||
|
||||
const resConfig = await getConfig(server.url)
|
||||
const serverConfig: ServerConfig = resConfig.body
|
||||
expect(serverConfig.instance.defaultNSFWPolicy).to.equal('blur')
|
||||
|
||||
for (const res of [ await getVideosList(server.url), await searchVideo(server.url, 'n') ]) {
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
expect(videos[ 1 ].name).to.equal('nsfw')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('User NSFW policy', function () {
|
||||
|
||||
it('Should create a user having the default nsfw policy', async function () {
|
||||
const username = 'user1'
|
||||
const password = 'my super password'
|
||||
await createUser(server.url, server.accessToken, username, password)
|
||||
|
||||
userAccessToken = await userLogin(server, { username, password })
|
||||
|
||||
const res = await getMyUserInformation(server.url, userAccessToken)
|
||||
const user = res.body
|
||||
|
||||
expect(user.nsfwPolicy).to.equal('blur')
|
||||
})
|
||||
|
||||
it('Should display NSFW videos with blur user NSFW policy', async function () {
|
||||
const results = [
|
||||
await getVideosListWithToken(server.url, userAccessToken),
|
||||
await searchVideoWithToken(server.url, 'n', userAccessToken)
|
||||
]
|
||||
|
||||
for (const res of results) {
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
expect(videos[ 1 ].name).to.equal('nsfw')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should display NSFW videos with display user NSFW policy', async function () {
|
||||
await updateMyUser({
|
||||
url: server.url,
|
||||
accessToken: server.accessToken,
|
||||
nsfwPolicy: 'display'
|
||||
})
|
||||
|
||||
const results = [
|
||||
await getVideosListWithToken(server.url, server.accessToken),
|
||||
await searchVideoWithToken(server.url, 'n', server.accessToken)
|
||||
]
|
||||
|
||||
for (const res of results) {
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
expect(videos[ 1 ].name).to.equal('nsfw')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not display NSFW videos with do_not_list user NSFW policy', async function () {
|
||||
await updateMyUser({
|
||||
url: server.url,
|
||||
accessToken: server.accessToken,
|
||||
nsfwPolicy: 'do_not_list'
|
||||
})
|
||||
|
||||
const results = [
|
||||
await getVideosListWithToken(server.url, server.accessToken),
|
||||
await searchVideoWithToken(server.url, 'n', server.accessToken)
|
||||
]
|
||||
for (const res of results) {
|
||||
expect(res.body.total).to.equal(1)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(1)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
}
|
||||
})
|
||||
|
||||
it('Should be able to see my NSFW videos even with do_not_list user NSFW policy', async function () {
|
||||
const res = await getMyVideos(server.url, server.accessToken, 0, 5)
|
||||
expect(res.body.total).to.equal(2)
|
||||
|
||||
const videos = res.body.data
|
||||
expect(videos).to.have.lengthOf(2)
|
||||
expect(videos[ 0 ].name).to.equal('normal')
|
||||
expect(videos[ 1 ].name).to.equal('nsfw')
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
killallServers([ server ])
|
||||
|
||||
// Keep the logs if the test failed
|
||||
if (this['ok']) {
|
||||
await flushTests()
|
||||
}
|
||||
})
|
||||
})
|
|
@ -3,6 +3,7 @@ import * as request from 'supertest'
|
|||
import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
|
||||
|
||||
import { UserRole } from '../../../../shared/index'
|
||||
import { NSFWPolicyType } from '../../../../shared/models/videos/nsfw-policy.type'
|
||||
|
||||
function createUser (
|
||||
url: string,
|
||||
|
@ -128,7 +129,7 @@ function updateMyUser (options: {
|
|||
url: string
|
||||
accessToken: string,
|
||||
newPassword?: string,
|
||||
displayNSFW?: boolean,
|
||||
nsfwPolicy?: NSFWPolicyType,
|
||||
email?: string,
|
||||
autoPlayVideo?: boolean
|
||||
description?: string
|
||||
|
@ -137,7 +138,7 @@ function updateMyUser (options: {
|
|||
|
||||
const toSend = {}
|
||||
if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword
|
||||
if (options.displayNSFW !== undefined && options.displayNSFW !== null) toSend['displayNSFW'] = options.displayNSFW
|
||||
if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend['nsfwPolicy'] = options.nsfwPolicy
|
||||
if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
|
||||
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
|
||||
if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
|
||||
|
|
|
@ -128,6 +128,18 @@ function getVideosList (url: string) {
|
|||
.expect('Content-Type', /json/)
|
||||
}
|
||||
|
||||
function getVideosListWithToken (url: string, token: string) {
|
||||
const path = '/api/v1/videos'
|
||||
|
||||
return request(url)
|
||||
.get(path)
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.query({ sort: 'name' })
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
}
|
||||
|
||||
function getLocalVideos (url: string) {
|
||||
const path = '/api/v1/videos'
|
||||
|
||||
|
@ -202,6 +214,18 @@ function searchVideo (url: string, search: string) {
|
|||
.expect('Content-Type', /json/)
|
||||
}
|
||||
|
||||
function searchVideoWithToken (url: string, search: string, token: string) {
|
||||
const path = '/api/v1/videos'
|
||||
const req = request(url)
|
||||
.get(path + '/search')
|
||||
.set('Authorization', 'Bearer ' + token)
|
||||
.query({ search })
|
||||
.set('Accept', 'application/json')
|
||||
|
||||
return req.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
}
|
||||
|
||||
function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/videos'
|
||||
|
||||
|
@ -418,6 +442,8 @@ async function completeVideoCheck (
|
|||
expect(video.licence.label).to.equal(VIDEO_LICENCES[attributes.licence] || 'Unknown')
|
||||
expect(video.language.id).to.equal(attributes.language)
|
||||
expect(video.language.label).to.equal(VIDEO_LANGUAGES[attributes.language] || 'Unknown')
|
||||
expect(video.privacy.id).to.deep.equal(attributes.privacy)
|
||||
expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
|
||||
expect(video.nsfw).to.equal(attributes.nsfw)
|
||||
expect(video.description).to.equal(attributes.description)
|
||||
expect(video.account.host).to.equal(attributes.account.host)
|
||||
|
@ -435,8 +461,6 @@ async function completeVideoCheck (
|
|||
|
||||
expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
|
||||
expect(videoDetails.tags).to.deep.equal(attributes.tags)
|
||||
expect(videoDetails.privacy.id).to.deep.equal(attributes.privacy)
|
||||
expect(videoDetails.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
|
||||
expect(videoDetails.account.name).to.equal(attributes.account.name)
|
||||
expect(videoDetails.account.host).to.equal(attributes.account.host)
|
||||
expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
|
||||
|
@ -490,6 +514,7 @@ export {
|
|||
getVideoPrivacies,
|
||||
getVideoLanguages,
|
||||
getMyVideos,
|
||||
searchVideoWithToken,
|
||||
getVideo,
|
||||
getVideoWithToken,
|
||||
getVideosList,
|
||||
|
@ -499,6 +524,7 @@ export {
|
|||
searchVideo,
|
||||
searchVideoWithPagination,
|
||||
searchVideoWithSort,
|
||||
getVideosListWithToken,
|
||||
uploadVideo,
|
||||
updateVideo,
|
||||
rateVideo,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
|
||||
export interface CustomConfig {
|
||||
instance: {
|
||||
name: string
|
||||
|
@ -5,6 +7,7 @@ export interface CustomConfig {
|
|||
description: string
|
||||
terms: string
|
||||
defaultClientRoute: string
|
||||
defaultNSFWPolicy: NSFWPolicyType
|
||||
customizations: {
|
||||
javascript?: string
|
||||
css?: string
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
|
||||
export interface ServerConfig {
|
||||
serverVersion: string
|
||||
|
||||
|
@ -5,6 +7,7 @@ export interface ServerConfig {
|
|||
name: string
|
||||
shortDescription: string
|
||||
defaultClientRoute: string
|
||||
defaultNSFWPolicy: NSFWPolicyType
|
||||
customizations: {
|
||||
javascript: string
|
||||
css: string
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
|
||||
export interface UserUpdateMe {
|
||||
description?: string
|
||||
displayNSFW?: boolean
|
||||
nsfwPolicy?: NSFWPolicyType
|
||||
autoPlayVideo?: boolean
|
||||
email?: string
|
||||
password?: string
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Account } from '../actors'
|
||||
import { VideoChannel } from '../videos/video-channel.model'
|
||||
import { UserRole } from './user-role'
|
||||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
displayNSFW: boolean
|
||||
nsfwPolicy: NSFWPolicyType
|
||||
autoPlayVideo: boolean
|
||||
role: UserRole
|
||||
videoQuota: number
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export type NSFWPolicyType = 'do_not_list' | 'blur' | 'display'
|
|
@ -26,6 +26,7 @@ export interface Video {
|
|||
category: VideoConstant<number>
|
||||
licence: VideoConstant<number>
|
||||
language: VideoConstant<number>
|
||||
privacy: VideoConstant<VideoPrivacy>
|
||||
description: string
|
||||
duration: number
|
||||
isLocal: boolean
|
||||
|
@ -48,7 +49,6 @@ export interface Video {
|
|||
}
|
||||
|
||||
export interface VideoDetails extends Video {
|
||||
privacy: VideoConstant<VideoPrivacy>
|
||||
descriptionPath: string
|
||||
support: string
|
||||
channel: VideoChannel
|
||||
|
|
Loading…
Reference in New Issue