Add ability to choose what policy we have for NSFW videos
There is a global instance setting and a per user setting
This commit is contained in:
parent
04ed10b21e
commit
0883b3245b
41 changed files with 519 additions and 86 deletions
|
@ -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,18 @@
|
|||
<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 *ngIf="formErrors.nsfwPolicy" class="form-error">
|
||||
{{ formErrors.nsfwPolicy }}
|
||||
</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
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
DEFAULT_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.DEFAULT_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.DEFAULT_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.DEFAULT_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: ''
|
||||
|
|
|
@ -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,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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Video as VideoServerModel } 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
|
||||
|
@ -83,8 +84,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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
|
@ -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)
|
||||
|
|
46
server/initializers/migrations/0205-user-nsfw-policy.ts
Normal file
46
server/initializers/migrations/0205-user-nsfw-policy.ts
Normal file
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
197
server/tests/api/videos/video-nsfw.ts
Normal file
197
server/tests/api/videos/video-nsfw.ts
Normal file
|
@ -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'
|
||||
|
||||
|
@ -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
|
||||
|
|
1
shared/models/videos/nsfw-policy.type.ts
Normal file
1
shared/models/videos/nsfw-policy.type.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export type NSFWPolicyType = 'do_not_list' | 'blur' | 'display'
|
Loading…
Reference in a new issue