1
0
Fork 0

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:
Chocobozzz 2018-04-19 11:01:34 +02:00
parent 04ed10b21e
commit 0883b3245b
No known key found for this signature in database
GPG key ID: 583A612D890159BE
41 changed files with 519 additions and 86 deletions

View file

@ -62,6 +62,22 @@
</div> </div>
</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="inner-form-title">Cache</div>
<div class="form-group"> <div class="form-group">

View file

@ -48,6 +48,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
instanceDescription: '', instanceDescription: '',
instanceTerms: '', instanceTerms: '',
instanceDefaultClientRoute: '', instanceDefaultClientRoute: '',
instanceDefaultNSFWPolicy: '',
cachePreviewsSize: '', cachePreviewsSize: '',
signupLimit: '', signupLimit: '',
adminEmail: '', adminEmail: '',
@ -90,6 +91,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
instanceDescription: [ '' ], instanceDescription: [ '' ],
instanceTerms: [ '' ], instanceTerms: [ '' ],
instanceDefaultClientRoute: [ '' ], instanceDefaultClientRoute: [ '' ],
instanceDefaultNSFWPolicy: [ '' ],
cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ], cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ],
signupEnabled: [ ], signupEnabled: [ ],
signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ], signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ],
@ -167,6 +169,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
description: this.form.value['instanceDescription'], description: this.form.value['instanceDescription'],
terms: this.form.value['instanceTerms'], terms: this.form.value['instanceTerms'],
defaultClientRoute: this.form.value['instanceDefaultClientRoute'], defaultClientRoute: this.form.value['instanceDefaultClientRoute'],
defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'],
customizations: { customizations: {
javascript: this.form.value['customizationJavascript'], javascript: this.form.value['customizationJavascript'],
css: this.form.value['customizationCSS'] css: this.form.value['customizationCSS']
@ -224,6 +227,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
instanceDescription: this.customConfig.instance.description, instanceDescription: this.customConfig.instance.description,
instanceTerms: this.customConfig.instance.terms, instanceTerms: this.customConfig.instance.terms,
instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute,
instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy,
cachePreviewsSize: this.customConfig.cache.previews.size, cachePreviewsSize: this.customConfig.cache.previews.size,
signupEnabled: this.customConfig.signup.enabled, signupEnabled: this.customConfig.signup.enabled,
signupLimit: this.customConfig.signup.limit, signupLimit: this.customConfig.signup.limit,

View file

@ -1,11 +1,18 @@
<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
<div class="form-group"> <div class="form-group">
<input <label for="nsfwPolicy">Default policy on videos containing sensitive content</label>
type="checkbox" id="displayNSFW" <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>
formControlName="displayNSFW"
> <div class="peertube-select-container">
<label for="displayNSFW"></label> <select id="nsfwPolicy" formControlName="nsfwPolicy">
<label for="displayNSFW">Display videos that contain mature or explicit content</label> <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>
<div class="form-group"> <div class="form-group">

View file

@ -12,3 +12,9 @@ input[type=submit] {
display: block; display: block;
margin-top: 15px; margin-top: 15px;
} }
.peertube-select-container {
@include peertube-select-container(340px);
margin-bottom: 30px;
}

View file

@ -29,7 +29,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
buildForm () { buildForm () {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
displayNSFW: [ this.user.displayNSFW ], nsfwPolicy: [ this.user.nsfwPolicy ],
autoPlayVideo: [ this.user.autoPlayVideo ] autoPlayVideo: [ this.user.autoPlayVideo ]
}) })
@ -41,10 +41,10 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
} }
updateDetails () { updateDetails () {
const displayNSFW = this.form.value['displayNSFW'] const nsfwPolicy = this.form.value['nsfwPolicy']
const autoPlayVideo = this.form.value['autoPlayVideo'] const autoPlayVideo = this.form.value['autoPlayVideo']
const details: UserUpdateMe = { const details: UserUpdateMe = {
displayNSFW, nsfwPolicy,
autoPlayVideo autoPlayVideo
} }

View file

@ -3,6 +3,7 @@ import { UserRight } from '../../../../../shared/models/users/user-right.enum'
// Do not use the barrel (dependency loop) // Do not use the barrel (dependency loop)
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
import { User, UserConstructorHash } from '../../shared/users/user.model' import { User, UserConstructorHash } from '../../shared/users/user.model'
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
export type TokenOptions = { export type TokenOptions = {
accessToken: string accessToken: string
@ -70,7 +71,7 @@ export class AuthUser extends User {
ROLE: 'role', ROLE: 'role',
EMAIL: 'email', EMAIL: 'email',
USERNAME: 'username', USERNAME: 'username',
DISPLAY_NSFW: 'display_nsfw', DEFAULT_NSFW_POLICY: 'nsfw_policy',
AUTO_PLAY_VIDEO: 'auto_play_video' AUTO_PLAY_VIDEO: 'auto_play_video'
} }
@ -85,7 +86,7 @@ export class AuthUser extends User {
username: peertubeLocalStorage.getItem(this.KEYS.USERNAME), username: peertubeLocalStorage.getItem(this.KEYS.USERNAME),
email: peertubeLocalStorage.getItem(this.KEYS.EMAIL), email: peertubeLocalStorage.getItem(this.KEYS.EMAIL),
role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole, 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' autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true'
}, },
Tokens.load() Tokens.load()
@ -99,7 +100,7 @@ export class AuthUser extends User {
peertubeLocalStorage.removeItem(this.KEYS.USERNAME) peertubeLocalStorage.removeItem(this.KEYS.USERNAME)
peertubeLocalStorage.removeItem(this.KEYS.ID) peertubeLocalStorage.removeItem(this.KEYS.ID)
peertubeLocalStorage.removeItem(this.KEYS.ROLE) 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.AUTO_PLAY_VIDEO)
peertubeLocalStorage.removeItem(this.KEYS.EMAIL) peertubeLocalStorage.removeItem(this.KEYS.EMAIL)
Tokens.flush() Tokens.flush()
@ -136,7 +137,7 @@ export class AuthUser extends User {
peertubeLocalStorage.setItem(AuthUser.KEYS.USERNAME, this.username) peertubeLocalStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email) peertubeLocalStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
peertubeLocalStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString()) 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)) peertubeLocalStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo))
this.tokens.save() this.tokens.save()
} }

View file

@ -5,7 +5,6 @@ import 'rxjs/add/operator/do'
import { ReplaySubject } from 'rxjs/ReplaySubject' import { ReplaySubject } from 'rxjs/ReplaySubject'
import { ServerConfig } from '../../../../../shared' import { ServerConfig } from '../../../../../shared'
import { About } from '../../../../../shared/models/server/about.model' import { About } from '../../../../../shared/models/server/about.model'
import { ServerStats } from '../../../../../shared/models/server/server-stats.model'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
@Injectable() @Injectable()
@ -26,6 +25,7 @@ export class ServerService {
shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' + shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform ' +
'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.', 'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
defaultClientRoute: '', defaultClientRoute: '',
defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
customizations: { customizations: {
javascript: '', javascript: '',
css: '' css: ''

View file

@ -1,5 +1,6 @@
import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared' import { hasUserRight, User as UserServerModel, UserRight, UserRole, VideoChannel } from '../../../../../shared'
import { Account } from '../account/account.model' import { Account } from '../account/account.model'
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
export type UserConstructorHash = { export type UserConstructorHash = {
id: number, id: number,
@ -7,7 +8,7 @@ export type UserConstructorHash = {
email: string, email: string,
role: UserRole, role: UserRole,
videoQuota?: number, videoQuota?: number,
displayNSFW?: boolean, nsfwPolicy?: NSFWPolicyType,
autoPlayVideo?: boolean, autoPlayVideo?: boolean,
createdAt?: Date, createdAt?: Date,
account?: Account, account?: Account,
@ -18,7 +19,7 @@ export class User implements UserServerModel {
username: string username: string
email: string email: string
role: UserRole role: UserRole
displayNSFW: boolean nsfwPolicy: NSFWPolicyType
autoPlayVideo: boolean autoPlayVideo: boolean
videoQuota: number videoQuota: number
account: Account account: Account
@ -40,8 +41,8 @@ export class User implements UserServerModel {
this.videoQuota = hash.videoQuota this.videoQuota = hash.videoQuota
} }
if (hash.displayNSFW !== undefined) { if (hash.nsfwPolicy !== undefined) {
this.displayNSFW = hash.displayNSFW this.nsfwPolicy = hash.nsfwPolicy
} }
if (hash.autoPlayVideo !== undefined) { if (hash.autoPlayVideo !== undefined) {

View file

@ -1,11 +1,11 @@
<div class="video-miniature"> <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"> <div class="video-miniature-information">
<span class="video-miniature-name"> <span class="video-miniature-name">
<a <a
class="video-miniature-name" 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 }} {{ video.name }}
</a> </a>

View file

@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { User } from '../users' import { User } from '../users'
import { Video } from './video.model' import { Video } from './video.model'
import { ServerService } from '@app/core'
@Component({ @Component({
selector: 'my-video-miniature', selector: 'my-video-miniature',
@ -11,7 +12,9 @@ export class VideoMiniatureComponent {
@Input() user: User @Input() user: User
@Input() video: Video @Input() video: Video
isVideoNSFWForThisUser () { constructor (private serverService: ServerService) { }
return this.video.isVideoNSFWForUser(this.user)
isVideoBlur () {
return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
} }
} }

View file

@ -4,6 +4,7 @@ import { Video as VideoServerModel } from '../../../../../shared'
import { Avatar } from '../../../../../shared/models/avatars/avatar.model' import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
import { VideoConstant } from '../../../../../shared/models/videos/video.model' import { VideoConstant } from '../../../../../shared/models/videos/video.model'
import { getAbsoluteAPIUrl } from '../misc/utils' import { getAbsoluteAPIUrl } from '../misc/utils'
import { ServerConfig } from '../../../../../shared/models'
export class Video implements VideoServerModel { export class Video implements VideoServerModel {
by: string by: string
@ -83,8 +84,14 @@ export class Video implements VideoServerModel {
this.by = Account.CREATE_BY_STRING(hash.account.name, hash.account.host) this.by = Account.CREATE_BY_STRING(hash.account.name, hash.account.host)
} }
isVideoNSFWForUser (user: User) { isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
// If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... // Video is not NSFW, skip
return (this.nsfw && (!user || user.displayNSFW === false)) 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'
} }
} }

View file

@ -22,6 +22,7 @@ import { VideoDownloadComponent } from './modal/video-download.component'
import { VideoReportComponent } from './modal/video-report.component' import { VideoReportComponent } from './modal/video-report.component'
import { VideoShareComponent } from './modal/video-share.component' import { VideoShareComponent } from './modal/video-share.component'
import { getVideojsOptions } from '../../../assets/player/peertube-player' import { getVideojsOptions } from '../../../assets/player/peertube-player'
import { ServerService } from '@app/core'
@Component({ @Component({
selector: 'my-video-watch', selector: 'my-video-watch',
@ -66,6 +67,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private confirmService: ConfirmService, private confirmService: ConfirmService,
private metaService: MetaService, private metaService: MetaService,
private authService: AuthService, private authService: AuthService,
private serverService: ServerService,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private markdownService: MarkdownService, private markdownService: MarkdownService,
private zone: NgZone, private zone: NgZone,
@ -335,7 +337,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.updateOtherVideosDisplayed() this.updateOtherVideosDisplayed()
if (this.video.isVideoNSFWForUser(this.user)) { if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
const res = await this.confirmService.confirm( const res = await this.confirmService.confirm(
'This video contains mature or explicit content. Are you sure you want to watch it?', 'This video contains mature or explicit content. Are you sure you want to watch it?',
'Mature or explicit content' 'Mature or explicit content'

View file

@ -84,6 +84,9 @@ instance:
description: 'Welcome to this PeerTube instance!' # Support markdown description: 'Welcome to this PeerTube instance!' # Support markdown
terms: 'No terms for now.' # Support markdown terms: 'No terms for now.' # Support markdown
default_client_route: '/videos/trending' 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: customizations:
javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime 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 css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime

View file

@ -100,6 +100,9 @@ instance:
description: '' # Support markdown description: '' # Support markdown
terms: '' # Support markdown terms: '' # Support markdown
default_client_route: '/videos/trending' 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: customizations:
javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime 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 css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime

View file

@ -32,3 +32,6 @@ transcoding:
480p: true 480p: true
720p: true 720p: true
1080p: true 1080p: true
instance:
default_nsfw_policy: 'display'

View file

@ -46,6 +46,7 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
name: CONFIG.INSTANCE.NAME, name: CONFIG.INSTANCE.NAME,
shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
customizations: { customizations: {
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS 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.user['video_quota'] = toUpdate.user.videoQuota
toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription 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)) await writeFilePromise(CONFIG.CUSTOM_FILE, JSON.stringify(toUpdateJSON, undefined, 2))
@ -153,6 +155,7 @@ function customConfig (): CustomConfig {
description: CONFIG.INSTANCE.DESCRIPTION, description: CONFIG.INSTANCE.DESCRIPTION,
terms: CONFIG.INSTANCE.TERMS, terms: CONFIG.INSTANCE.TERMS,
defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
customizations: { customizations: {
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS, css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT

View file

@ -42,6 +42,7 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
import { UserModel } from '../../models/account/user' import { UserModel } from '../../models/account/user'
import { OAuthTokenModel } from '../../models/oauth/oauth-token' import { OAuthTokenModel } from '../../models/oauth/oauth-token'
import { VideoModel } from '../../models/video/video' 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 reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
const loginRateLimiter = new RateLimit({ const loginRateLimiter = new RateLimit({
@ -161,7 +162,13 @@ export {
async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
const user = res.locals.oauth.token.User as UserModel 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)) return res.json(getFormattedObjects(resultList.data, resultList.total))
} }
@ -188,7 +195,7 @@ async function createUser (req: express.Request) {
username: body.username, username: body.username,
password: body.password, password: body.password,
email: body.email, email: body.email,
displayNSFW: false, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
autoPlayVideo: true, autoPlayVideo: true,
role: body.role, role: body.role,
videoQuota: body.videoQuota videoQuota: body.videoQuota
@ -219,7 +226,7 @@ async function registerUser (req: express.Request) {
username: body.username, username: body.username,
password: body.password, password: body.password,
email: body.email, email: body.email,
displayNSFW: false, nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
autoPlayVideo: true, autoPlayVideo: true,
role: UserRole.USER, role: UserRole.USER,
videoQuota: CONFIG.USER.VIDEO_QUOTA 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.password !== undefined) user.password = body.password
if (body.email !== undefined) user.email = body.email 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 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
await sequelizeTypescript.transaction(async t => { await sequelizeTypescript.transaction(async t => {

View file

@ -19,13 +19,18 @@ import {
VIDEO_MIMETYPE_EXT, VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES VIDEO_PRIVACIES
} from '../../../initializers' } 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 { sendCreateVideo, sendCreateView, sendUpdateVideo } from '../../../lib/activitypub/send'
import { JobQueue } from '../../../lib/job-queue' import { JobQueue } from '../../../lib/job-queue'
import { Redis } from '../../../lib/redis' import { Redis } from '../../../lib/redis'
import { import {
asyncMiddleware, asyncMiddleware,
authenticate, authenticate,
optionalAuthenticate,
paginationValidator, paginationValidator,
setDefaultPagination, setDefaultPagination,
setDefaultSort, setDefaultSort,
@ -44,6 +49,9 @@ import { blacklistRouter } from './blacklist'
import { videoChannelRouter } from './channel' import { videoChannelRouter } from './channel'
import { videoCommentRouter } from './comment' import { videoCommentRouter } from './comment'
import { rateVideoRouter } from './rate' 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() const videosRouter = express.Router()
@ -81,6 +89,7 @@ videosRouter.get('/',
videosSortValidator, videosSortValidator,
setDefaultSort, setDefaultSort,
setDefaultPagination, setDefaultPagination,
optionalAuthenticate,
asyncMiddleware(listVideos) asyncMiddleware(listVideos)
) )
videosRouter.get('/search', videosRouter.get('/search',
@ -89,6 +98,7 @@ videosRouter.get('/search',
videosSortValidator, videosSortValidator,
setDefaultSort, setDefaultSort,
setDefaultPagination, setDefaultPagination,
optionalAuthenticate,
asyncMiddleware(searchVideos) asyncMiddleware(searchVideos)
) )
videosRouter.put('/:id', 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) { 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)) 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) { async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
const resultList = await VideoModel.searchAndPopulateAccountAndServer( const resultList = await VideoModel.searchAndPopulateAccountAndServer(
req.query.search, req.query.search as string,
req.query.start, req.query.start as number,
req.query.count, req.query.count as number,
req.query.sort req.query.sort as VideoSortField,
isNSFWHidden(res)
) )
return res.json(getFormattedObjects(resultList.data, resultList.total)) return res.json(getFormattedObjects(resultList.data, resultList.total))
} }
function isNSFWHidden (res: express.Response) {
if (res.locals.oauth) {
const user: User = res.locals.oauth.token.User
if (user) return user.nsfwPolicy === 'do_not_list'
}
return CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
}

View file

@ -6,6 +6,7 @@ import * as Feed from 'pfeed'
import { ResultList } from '../../shared/models' import { ResultList } from '../../shared/models'
import { AccountModel } from '../models/account/account' import { AccountModel } from '../models/account/account'
import { cacheRoute } from '../middlewares/cache' import { cacheRoute } from '../middlewares/cache'
import { VideoSortField } from '../../client/src/app/shared/video/sort-field.type'
const feedsRouter = express.Router() const feedsRouter = express.Router()
@ -31,20 +32,22 @@ async function generateFeed (req: express.Request, res: express.Response, next:
let resultList: ResultList<VideoModel> let resultList: ResultList<VideoModel>
const account: AccountModel = res.locals.account const account: AccountModel = res.locals.account
const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
if (account) { if (account) {
resultList = await VideoModel.listAccountVideosForApi( resultList = await VideoModel.listAccountVideosForApi(
account.id, account.id,
start, start,
FEEDS.COUNT, FEEDS.COUNT,
req.query.sort, req.query.sort as VideoSortField,
true hideNSFW
) )
} else { } else {
resultList = await VideoModel.listForApi( resultList = await VideoModel.listForApi(
start, start,
FEEDS.COUNT, FEEDS.COUNT,
req.query.sort, req.query.sort as VideoSortField,
hideNSFW,
req.query.filter, req.query.filter,
true true
) )

View file

@ -1,9 +1,10 @@
import 'express-validator' import 'express-validator'
import * as validator from 'validator' import * as validator from 'validator'
import { UserRole } from '../../../shared' import { UserRole } from '../../../shared'
import { CONSTRAINTS_FIELDS } from '../../initializers' import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers'
import { exists, isFileValid } from './misc' import { exists, isFileValid } from './misc'
import { values } from 'lodash'
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS 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)) return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
} }
function isUserDisplayNSFWValid (value: any) { const nsfwPolicies = values(NSFW_POLICY_TYPES)
return isBoolean(value) function isUserNSFWPolicyValid (value: any) {
return exists(value) && nsfwPolicies.indexOf(value) !== -1
} }
function isUserAutoPlayVideoValid (value: any) { function isUserAutoPlayVideoValid (value: any) {
@ -56,7 +58,7 @@ export {
isUserRoleValid, isUserRoleValid,
isUserVideoQuotaValid, isUserVideoQuotaValid,
isUserUsernameValid, isUserUsernameValid,
isUserDisplayNSFWValid, isUserNSFWPolicyValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserDescriptionValid, isUserDescriptionValid,
isAvatarFile isAvatarFile

View file

@ -5,12 +5,12 @@ import { ApplicationModel } from '../models/application/application'
import { OAuthClientModel } from '../models/oauth/oauth-client' import { OAuthClientModel } from '../models/oauth/oauth-client'
// Some checks on configuration files // Some checks on configuration files
// Return an error message, or null if everything is okay
function checkConfig () { function checkConfig () {
if (config.has('webserver.host')) { const defaultNSFWPolicy = config.get<string>('instance.default_nsfw_policy')
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`.'
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 return null
@ -28,7 +28,8 @@ function checkMissedConfig () {
'log.level', 'log.level',
'user.video_quota', 'user.video_quota',
'cache.previews.size', 'admin.email', 'signup.enabled', 'signup.limit', 'transcoding.enabled', 'transcoding.threads', '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[] = [] const miss: string[] = []

View file

@ -6,13 +6,14 @@ import { FollowState } from '../../shared/models/actors'
import { VideoPrivacy } from '../../shared/models/videos' import { VideoPrivacy } from '../../shared/models/videos'
// Do not use barrels, remain constants as independent as possible // Do not use barrels, remain constants as independent as possible
import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' 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 // Use a variable to reload the configuration if we need
let config: IConfig = require('config') 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 DESCRIPTION () { return config.get<string>('instance.description') },
get TERMS () { return config.get<string>('instance.terms') }, get TERMS () { return config.get<string>('instance.terms') },
get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, 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: { CUSTOMIZATIONS: {
get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') }, get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
get CSS () { return config.get<string>('instance.customizations.css') } 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 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) // Express static paths (router)
@ -474,6 +482,7 @@ export {
PRIVATE_RSA_KEY_SIZE, PRIVATE_RSA_KEY_SIZE,
SORTABLE_COLUMNS, SORTABLE_COLUMNS,
FEEDS, FEEDS,
NSFW_POLICY_TYPES,
STATIC_MAX_AGE, STATIC_MAX_AGE,
STATIC_PATHS, STATIC_PATHS,
ACTIVITY_PUB, ACTIVITY_PUB,

View file

@ -120,6 +120,7 @@ async function createOAuthAdminIfNotExist () {
email, email,
password, password,
role, role,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
videoQuota: -1 videoQuota: -1
} }
const user = new UserModel(userData) const user = new UserModel(userData)

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

View file

@ -2,6 +2,7 @@ import * as express from 'express'
import * as OAuthServer from 'express-oauth-server' import * as OAuthServer from 'express-oauth-server'
import 'express-validator' import 'express-validator'
import { OAUTH_LIFETIME } from '../initializers' import { OAUTH_LIFETIME } from '../initializers'
import { logger } from '../helpers/logger'
const oAuthServer = new OAuthServer({ const oAuthServer = new OAuthServer({
useErrorHandler: true, useErrorHandler: true,
@ -13,6 +14,8 @@ const oAuthServer = new OAuthServer({
function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) { function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) {
oAuthServer.authenticate()(req, res, err => { oAuthServer.authenticate()(req, res, err => {
if (err) { if (err) {
logger.warn('Cannot authenticate.', { err })
return res.status(err.status) return res.status(err.status)
.json({ .json({
error: 'Token is invalid.', 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) { function token (req: express.Request, res: express.Response, next: express.NextFunction) {
return oAuthServer.token()(req, res, err => { return oAuthServer.token()(req, res, err => {
if (err) { if (err) {
@ -44,5 +53,6 @@ function token (req: express.Request, res: express.Response, next: express.NextF
export { export {
authenticate, authenticate,
optionalAuthenticate,
token token
} }

View file

@ -1,6 +1,6 @@
import * as express from 'express' import * as express from 'express'
import { body } from 'express-validator/check' 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 { logger } from '../../helpers/logger'
import { areValidationErrors } from './utils' import { areValidationErrors } from './utils'
@ -9,6 +9,7 @@ const customConfigUpdateValidator = [
body('instance.description').exists().withMessage('Should have a valid instance description'), body('instance.description').exists().withMessage('Should have a valid instance description'),
body('instance.terms').exists().withMessage('Should have a valid instance terms'), 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.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.css').exists().withMessage('Should have a valid instance CSS customization'),
body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript 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'), body('cache.previews.size').isInt().withMessage('Should have a valid previews size'),

View file

@ -8,7 +8,7 @@ import {
isAvatarFile, isAvatarFile,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserDescriptionValid, isUserDescriptionValid,
isUserDisplayNSFWValid, isUserNSFWPolicyValid,
isUserPasswordValid, isUserPasswordValid,
isUserRoleValid, isUserRoleValid,
isUserUsernameValid, isUserUsernameValid,
@ -101,7 +101,7 @@ const usersUpdateMeValidator = [
body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').optional().isEmail().withMessage('Should have a valid email attribute'), 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'), body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
(req: express.Request, res: express.Response, next: express.NextFunction) => { (req: express.Request, res: express.Response, next: express.NextFunction) => {

View file

@ -21,7 +21,7 @@ import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
import { User, UserRole } from '../../../shared/models/users' import { User, UserRole } from '../../../shared/models/users'
import { import {
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
isUserDisplayNSFWValid, isUserNSFWPolicyValid,
isUserPasswordValid, isUserPasswordValid,
isUserRoleValid, isUserRoleValid,
isUserUsernameValid, isUserUsernameValid,
@ -32,6 +32,9 @@ import { OAuthTokenModel } from '../oauth/oauth-token'
import { getSort, throwIfNotValid } from '../utils' import { getSort, throwIfNotValid } from '../utils'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from './account' import { AccountModel } from './account'
import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
import { values } from 'lodash'
import { NSFW_POLICY_TYPES } from '../../initializers'
@DefaultScope({ @DefaultScope({
include: [ include: [
@ -83,10 +86,9 @@ export class UserModel extends Model<UserModel> {
email: string email: string
@AllowNull(false) @AllowNull(false)
@Default(false) @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
@Is('UserDisplayNSFW', value => throwIfNotValid(value, isUserDisplayNSFWValid, 'display NSFW boolean')) @Column(DataType.ENUM(values(NSFW_POLICY_TYPES)))
@Column nsfwPolicy: NSFWPolicyType
displayNSFW: boolean
@AllowNull(false) @AllowNull(false)
@Default(true) @Default(true)
@ -265,7 +267,7 @@ export class UserModel extends Model<UserModel> {
id: this.id, id: this.id,
username: this.username, username: this.username,
email: this.email, email: this.email,
displayNSFW: this.displayNSFW, nsfwPolicy: this.nsfwPolicy,
autoPlayVideo: this.autoPlayVideo, autoPlayVideo: this.autoPlayVideo,
role: this.role, role: this.role,
roleLabel: USER_ROLE_LABELS[ this.role ], roleLabel: USER_ROLE_LABELS[ this.role ],

View file

@ -95,7 +95,7 @@ enum ScopeNames {
} }
@Scopes({ @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> = { const query: IFindOptions<VideoModel> = {
where: { where: {
id: { id: {
@ -161,6 +161,11 @@ enum ScopeNames {
}) })
} }
// Hide nsfw videos?
if (hideNSFW === true) {
query.where['nsfw'] = false
}
return query return query
}, },
[ScopeNames.WITH_ACCOUNT_DETAILS]: { [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> = { const query: IFindOptions<VideoModel> = {
offset: start, offset: start,
limit: count, 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 VideoModel.findAndCountAll(query).then(({ rows, count }) => {
return { return {
data: rows, 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 = { const query = {
offset: start, offset: start,
limit: count, limit: count,
@ -685,8 +696,7 @@ export class VideoModel extends Model<VideoModel> {
} }
const serverActor = await getServerActor() const serverActor = await getServerActor()
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles ] })
return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter, withFiles ] })
.findAndCountAll(query) .findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { 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> = { const query: IFindOptions<VideoModel> = {
offset: start, offset: start,
limit: count, limit: count,
@ -724,7 +734,7 @@ export class VideoModel extends Model<VideoModel> {
const serverActor = await getServerActor() 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) .findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { return {

View file

@ -6,7 +6,7 @@ import { CustomConfig } from '../../../../shared/models/server/custom-config.mod
import { import {
createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo, createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
setAccessTokensToServers, userLogin setAccessTokensToServers, userLogin, immutableAssign
} from '../../utils' } from '../../utils'
describe('Test config API validators', function () { describe('Test config API validators', function () {
@ -20,6 +20,7 @@ describe('Test config API validators', function () {
description: 'my super description', description: 'my super description',
terms: 'my super terms', terms: 'my super terms',
defaultClientRoute: '/videos/recently-added', defaultClientRoute: '/videos/recently-added',
defaultNSFWPolicy: 'blur',
customizations: { customizations: {
javascript: 'alert("coucou")', javascript: 'alert("coucou")',
css: 'body { background-color: red; }' 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 () { it('Should success with the correct parameters', async function () {
await makePutBodyRequest({ await makePutBodyRequest({
url: server.url, url: server.url,

View file

@ -231,9 +231,9 @@ describe('Test users API validators', function () {
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) 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 = { const fields = {
displayNSFW: -1 nsfwPolicy: 'hello'
} }
await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) 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 () { it('Should succeed with the correct params', async function () {
const fields = { const fields = {
password: 'my super password', password: 'my super password',
displayNSFW: true, nsfwPolicy: 'blur',
autoPlayVideo: false, autoPlayVideo: false,
email: 'super_email@example.com' email: 'super_email@example.com'
} }

View file

@ -59,6 +59,7 @@ describe('Test config', function () {
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
expect(data.instance.terms).to.equal('No terms for now.') expect(data.instance.terms).to.equal('No terms for now.')
expect(data.instance.defaultClientRoute).to.equal('/videos/trending') 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.css).to.be.empty
expect(data.instance.customizations.javascript).to.be.empty expect(data.instance.customizations.javascript).to.be.empty
expect(data.cache.previews.size).to.equal(1) expect(data.cache.previews.size).to.equal(1)
@ -83,6 +84,7 @@ describe('Test config', function () {
description: 'my super description', description: 'my super description',
terms: 'my super terms', terms: 'my super terms',
defaultClientRoute: '/videos/recently-added', defaultClientRoute: '/videos/recently-added',
defaultNSFWPolicy: 'blur' as 'blur',
customizations: { customizations: {
javascript: 'alert("coucou")', javascript: 'alert("coucou")',
css: 'body { background-color: red; }' 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.description).to.equal('my super description')
expect(data.instance.terms).to.equal('my super terms') expect(data.instance.terms).to.equal('my super terms')
expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') 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.javascript).to.equal('alert("coucou")')
expect(data.instance.customizations.css).to.equal('body { background-color: red; }') expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
expect(data.cache.previews.size).to.equal(2) 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.description).to.equal('my super description')
expect(data.instance.terms).to.equal('my super terms') expect(data.instance.terms).to.equal('my super terms')
expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') 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.javascript).to.equal('alert("coucou")')
expect(data.instance.customizations.css).to.equal('body { background-color: red; }') expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
expect(data.cache.previews.size).to.equal(2) 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.description).to.equal('Welcome to this PeerTube instance!')
expect(data.instance.terms).to.equal('No terms for now.') expect(data.instance.terms).to.equal('No terms for now.')
expect(data.instance.defaultClientRoute).to.equal('/videos/trending') 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.css).to.be.empty
expect(data.instance.customizations.javascript).to.be.empty expect(data.instance.customizations.javascript).to.be.empty
expect(data.cache.previews.size).to.equal(1) expect(data.cache.previews.size).to.equal(1)

View file

@ -168,7 +168,7 @@ describe('Test users', function () {
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('user_1@example.com') 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.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.roleLabel).to.equal('User') expect(user.roleLabel).to.equal('User')
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
@ -215,12 +215,12 @@ describe('Test users', function () {
const user = users[ 0 ] const user = users[ 0 ]
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('user_1@example.com') 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 ] const rootUser = users[ 1 ]
expect(rootUser.username).to.equal('root') expect(rootUser.username).to.equal('root')
expect(rootUser.email).to.equal('admin1@example.com') expect(rootUser.email).to.equal('admin1@example.com')
expect(rootUser.displayNSFW).to.be.false expect(user.nsfwPolicy).to.equal('display')
userId = user.id userId = user.id
}) })
@ -239,7 +239,7 @@ describe('Test users', function () {
expect(user.username).to.equal('root') expect(user.username).to.equal('root')
expect(user.email).to.equal('admin1@example.com') expect(user.email).to.equal('admin1@example.com')
expect(user.roleLabel).to.equal('Administrator') 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 () { it('Should list only the first user by username desc', async function () {
@ -254,7 +254,7 @@ describe('Test users', function () {
const user = users[ 0 ] const user = users[ 0 ]
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('user_1@example.com') 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 () { it('Should list only the second user by createdAt desc', async function () {
@ -269,7 +269,7 @@ describe('Test users', function () {
const user = users[ 0 ] const user = users[ 0 ]
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('user_1@example.com') 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 () { 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 ].username).to.equal('root')
expect(users[ 0 ].email).to.equal('admin1@example.com') 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 ].username).to.equal('user_1')
expect(users[ 1 ].email).to.equal('user_1@example.com') 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 () { it('Should update my password', async function () {
@ -305,7 +305,7 @@ describe('Test users', function () {
await updateMyUser({ await updateMyUser({
url: server.url, url: server.url,
accessToken: accessTokenUser, accessToken: accessTokenUser,
displayNSFW: true nsfwPolicy: 'do_not_list'
}) })
const res = await getMyUserInformation(server.url, accessTokenUser) const res = await getMyUserInformation(server.url, accessTokenUser)
@ -313,7 +313,7 @@ describe('Test users', function () {
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('user_1@example.com') 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.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.be.null expect(user.account.description).to.be.null
@ -344,7 +344,7 @@ describe('Test users', function () {
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('updated@example.com') 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.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.be.null expect(user.account.description).to.be.null
@ -377,7 +377,7 @@ describe('Test users', function () {
expect(user.username).to.equal('user_1') expect(user.username).to.equal('user_1')
expect(user.email).to.equal('updated@example.com') 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.videoQuota).to.equal(2 * 1024 * 1024)
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')
expect(user.account.description).to.equal('my super description updated') 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.username).to.equal('user_1')
expect(user.email).to.equal('updated2@example.com') 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.videoQuota).to.equal(42)
expect(user.roleLabel).to.equal('Moderator') expect(user.roleLabel).to.equal('Moderator')
expect(user.id).to.be.a('number') expect(user.id).to.be.a('number')

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

View file

@ -3,6 +3,7 @@ import * as request from 'supertest'
import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../' import { makePostBodyRequest, makeUploadRequest, makePutBodyRequest } from '../'
import { UserRole } from '../../../../shared/index' import { UserRole } from '../../../../shared/index'
import { NSFWPolicyType } from '../../../../shared/models/videos/nsfw-policy.type'
function createUser ( function createUser (
url: string, url: string,
@ -128,7 +129,7 @@ function updateMyUser (options: {
url: string url: string
accessToken: string, accessToken: string,
newPassword?: string, newPassword?: string,
displayNSFW?: boolean, nsfwPolicy?: NSFWPolicyType,
email?: string, email?: string,
autoPlayVideo?: boolean autoPlayVideo?: boolean
description?: string description?: string
@ -137,7 +138,7 @@ function updateMyUser (options: {
const toSend = {} const toSend = {}
if (options.newPassword !== undefined && options.newPassword !== null) toSend['password'] = options.newPassword 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.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
if (options.description !== undefined && options.description !== null) toSend['description'] = options.description if (options.description !== undefined && options.description !== null) toSend['description'] = options.description

View file

@ -128,6 +128,18 @@ function getVideosList (url: string) {
.expect('Content-Type', /json/) .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) { function getLocalVideos (url: string) {
const path = '/api/v1/videos' const path = '/api/v1/videos'
@ -202,6 +214,18 @@ function searchVideo (url: string, search: string) {
.expect('Content-Type', /json/) .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) { function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
const path = '/api/v1/videos' const path = '/api/v1/videos'
@ -490,6 +514,7 @@ export {
getVideoPrivacies, getVideoPrivacies,
getVideoLanguages, getVideoLanguages,
getMyVideos, getMyVideos,
searchVideoWithToken,
getVideo, getVideo,
getVideoWithToken, getVideoWithToken,
getVideosList, getVideosList,
@ -499,6 +524,7 @@ export {
searchVideo, searchVideo,
searchVideoWithPagination, searchVideoWithPagination,
searchVideoWithSort, searchVideoWithSort,
getVideosListWithToken,
uploadVideo, uploadVideo,
updateVideo, updateVideo,
rateVideo, rateVideo,

View file

@ -1,3 +1,5 @@
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
export interface CustomConfig { export interface CustomConfig {
instance: { instance: {
name: string name: string
@ -5,6 +7,7 @@ export interface CustomConfig {
description: string description: string
terms: string terms: string
defaultClientRoute: string defaultClientRoute: string
defaultNSFWPolicy: NSFWPolicyType
customizations: { customizations: {
javascript?: string javascript?: string
css?: string css?: string

View file

@ -1,3 +1,5 @@
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
export interface ServerConfig { export interface ServerConfig {
serverVersion: string serverVersion: string
@ -5,6 +7,7 @@ export interface ServerConfig {
name: string name: string
shortDescription: string shortDescription: string
defaultClientRoute: string defaultClientRoute: string
defaultNSFWPolicy: NSFWPolicyType
customizations: { customizations: {
javascript: string javascript: string
css: string css: string

View file

@ -1,6 +1,8 @@
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
export interface UserUpdateMe { export interface UserUpdateMe {
description?: string description?: string
displayNSFW?: boolean nsfwPolicy?: NSFWPolicyType
autoPlayVideo?: boolean autoPlayVideo?: boolean
email?: string email?: string
password?: string password?: string

View file

@ -1,12 +1,13 @@
import { Account } from '../actors' import { Account } from '../actors'
import { VideoChannel } from '../videos/video-channel.model' import { VideoChannel } from '../videos/video-channel.model'
import { UserRole } from './user-role' import { UserRole } from './user-role'
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
export interface User { export interface User {
id: number id: number
username: string username: string
email: string email: string
displayNSFW: boolean nsfwPolicy: NSFWPolicyType
autoPlayVideo: boolean autoPlayVideo: boolean
role: UserRole role: UserRole
videoQuota: number videoQuota: number

View file

@ -0,0 +1 @@
export type NSFWPolicyType = 'do_not_list' | 'blur' | 'display'