Enh #106 : Add an autoPlayVideo user attribute (#159)
authorAndréas Livet <andreas.livet@gmail.com>
Tue, 19 Dec 2017 09:45:49 +0000 (10:45 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 19 Dec 2017 09:45:49 +0000 (10:45 +0100)
Warning : I was not able to run the tests on my machine. It uses a different approach to handle databse connexion and didn't find where to configure it...

- create a migration file to add a boolean column in user table
- add autoPlayVideo attribute everywhere it is needed (both on client and server side)
- add tests
- add a way to configure this attribute in account-settings
- use the attribute in video-watch component to actually autoplay or not the video

18 files changed:
client/src/app/account/account-settings/account-details/account-details.component.html
client/src/app/account/account-settings/account-details/account-details.component.ts
client/src/app/account/account-settings/account-settings.component.html
client/src/app/core/auth/auth-user.model.ts
client/src/app/core/auth/auth.service.ts
client/src/app/shared/users/user.model.ts
client/src/app/videos/+video-watch/video-watch.component.ts
server/controllers/api/users.ts
server/helpers/custom-validators/users.ts
server/initializers/constants.ts
server/initializers/migrations/0130-user-autoplay-video.ts [new file with mode: 0644]
server/middlewares/validators/users.ts
server/models/account/user.ts
server/tests/api/check-params/users.ts
server/tests/api/users.ts
server/tests/utils/users.ts
shared/models/users/user-update-me.model.ts
shared/models/users/user.model.ts

index bc18b39b4023e6c64f2edb4cb220701b512e7ae6..593b87e295cb710e2eadf7c56ecaded1e3af44cb 100644 (file)
@@ -9,6 +9,12 @@
   <div *ngIf="formErrors['displayNSFW']" class="alert alert-danger">
     {{ formErrors['displayNSFW'] }}
   </div>
+  <br/>
+  <input
+    type="checkbox" id="autoPlayVideo"
+    formControlName="autoPlayVideo"
+  >
+  <label for="autoPlayVideo">Automatically plays video</label>
 
   <input type="submit" value="Save" [disabled]="!form.valid">
 </form>
index d835c53e502440b5b333bd84be17069c984bb596..b8c19d8d608774f348529413dc07003ec96138a5 100644 (file)
@@ -31,7 +31,8 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
 
   buildForm () {
     this.form = this.formBuilder.group({
-      displayNSFW: [ this.user.displayNSFW ]
+      displayNSFW: [ this.user.displayNSFW ],
+      autoPlayVideo: [ this.user.autoPlayVideo ]
     })
 
     this.form.valueChanges.subscribe(data => this.onValueChanged(data))
@@ -43,8 +44,10 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
 
   updateDetails () {
     const displayNSFW = this.form.value['displayNSFW']
+    const autoPlayVideo = this.form.value['autoPlayVideo']
     const details: UserUpdateMe = {
-      displayNSFW
+      displayNSFW,
+      autoPlayVideo
     }
 
     this.error = null
index c0a74cc4764d58d6252585f0227f0a31ad2ee137..f14eadd498a93ae940daf147f147eeb04cdfe044 100644 (file)
@@ -11,5 +11,5 @@
 <div class="account-title">Account settings</div>
 <my-account-change-password></my-account-change-password>
 
-<div class="account-title">Filtering</div>
+<div class="account-title">Videos</div>
 <my-account-details [user]="user"></my-account-details>
index 7b6c8816fdd3d75675a98f0a4a47987ef27bd3b4..9ad275392e359739cd59593d0ddbb1d4ec028563 100644 (file)
@@ -69,7 +69,8 @@ export class AuthUser extends User {
     ROLE: 'role',
     EMAIL: 'email',
     USERNAME: 'username',
-    DISPLAY_NSFW: 'display_nsfw'
+    DISPLAY_NSFW: 'display_nsfw',
+    AUTO_PLAY_VIDEO: 'auto_play_video'
   }
 
   tokens: Tokens
@@ -83,7 +84,8 @@ export class AuthUser extends User {
           username: localStorage.getItem(this.KEYS.USERNAME),
           email: localStorage.getItem(this.KEYS.EMAIL),
           role: parseInt(localStorage.getItem(this.KEYS.ROLE), 10) as UserRole,
-          displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
+          displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true',
+          autoPlayVideo: localStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true'
         },
         Tokens.load()
       )
@@ -97,6 +99,7 @@ export class AuthUser extends User {
     localStorage.removeItem(this.KEYS.ID)
     localStorage.removeItem(this.KEYS.ROLE)
     localStorage.removeItem(this.KEYS.DISPLAY_NSFW)
+    localStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO)
     localStorage.removeItem(this.KEYS.EMAIL)
     Tokens.flush()
   }
@@ -133,6 +136,7 @@ export class AuthUser extends User {
     localStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
     localStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString())
     localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
+    localStorage.setItem(AuthUser.KEYS.AUTO_PLAY_VIDEO, JSON.stringify(this.autoPlayVideo))
     this.tokens.save()
   }
 }
index e2b8b6ba5d84726955e1b4404d1ab00132b2584b..37264a8ad37292d5e500167b77c83cb81f88c622 100644 (file)
@@ -33,6 +33,7 @@ interface UserLoginWithUserInformation extends UserLogin {
   id: number
   role: UserRole
   displayNSFW: boolean
+  autoPlayVideo: boolean
   email: string
   videoQuota: number
   account: Account
@@ -191,6 +192,7 @@ export class AuthService {
       .subscribe(
         res => {
           this.user.displayNSFW = res.displayNSFW
+          this.user.autoPlayVideo = res.autoPlayVideo
           this.user.role = res.role
           this.user.videoChannels = res.videoChannels
           this.user.account = res.account
@@ -212,6 +214,7 @@ export class AuthService {
                         id: res.id,
                         role: res.role,
                         displayNSFW: res.displayNSFW,
+                        autoPlayVideo: res.autoPlayVideo,
                         email: res.email,
                         videoQuota: res.videoQuota,
                         account: res.account,
@@ -230,6 +233,7 @@ export class AuthService {
       role: obj.role,
       email: obj.email,
       displayNSFW: obj.displayNSFW,
+      autoPlayVideo: obj.autoPlayVideo,
       videoQuota: obj.videoQuota,
       videoChannels: obj.videoChannels,
       account: obj.account
index b4d13f37c3dda021cdbfbe17bba5606168d8c754..7a962ae3e424ea847a035ecb6e59e828e1d7eb03 100644 (file)
@@ -8,6 +8,7 @@ export type UserConstructorHash = {
   role: UserRole,
   videoQuota?: number,
   displayNSFW?: boolean,
+  autoPlayVideo?: boolean,
   createdAt?: Date,
   account?: Account,
   videoChannels?: VideoChannel[]
@@ -18,6 +19,7 @@ export class User implements UserServerModel {
   email: string
   role: UserRole
   displayNSFW: boolean
+  autoPlayVideo: boolean
   videoQuota: number
   account: Account
   videoChannels: VideoChannel[]
@@ -42,6 +44,10 @@ export class User implements UserServerModel {
       this.displayNSFW = hash.displayNSFW
     }
 
+    if (hash.autoPlayVideo !== undefined) {
+      this.autoPlayVideo = hash.autoPlayVideo
+    }
+
     if (hash.createdAt !== undefined) {
       this.createdAt = hash.createdAt
     }
index 5e4823c9ce24d415ee64f4642ef7f0614aa6048f..e35b02f3f57a347e145bfa535e37885cc89b24e7 100644 (file)
@@ -290,12 +290,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
           const videojsOptions = {
             controls: true,
-            autoplay: true,
+            autoplay: this.user.autoPlayVideo,
             plugins: {
               peertube: {
                 videoFiles: this.video.files,
                 playerElement: this.playerElement,
-                autoplay: true,
+                autoplay: this.user.autoPlayVideo,
                 peerTubeLink: false
               }
             }
index d6c0e67f95744facfd33b2101ae39065a35434b6..995542604530d3d69376f6c8fc1430e626329144 100644 (file)
@@ -134,6 +134,7 @@ async function createUser (req: express.Request) {
     password: body.password,
     email: body.email,
     displayNSFW: false,
+    autoPlayVideo: true,
     role: body.role,
     videoQuota: body.videoQuota
   })
@@ -162,6 +163,7 @@ async function registerUser (req: express.Request) {
     password: body.password,
     email: body.email,
     displayNSFW: false,
+    autoPlayVideo: true,
     role: UserRole.USER,
     videoQuota: CONFIG.USER.VIDEO_QUOTA
   })
@@ -219,6 +221,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.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
 
   await user.save()
 
index b5b5642d6ff5fe992d3f6ec99061e42fea7688f8..159c2a7004a7152a71d6920af83e4077e4d9a219 100644 (file)
@@ -21,10 +21,18 @@ function isUserUsernameValid (value: string) {
   return exists(value) && validator.matches(value, new RegExp(`^[a-z0-9._]{${min},${max}}$`))
 }
 
-function isUserDisplayNSFWValid (value: any) {
+function isBoolean (value: any) {
   return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
 }
 
+function isUserDisplayNSFWValid (value: any) {
+  return isBoolean(value)
+}
+
+function isUserAutoPlayVideoValid (value: any) {
+  return isBoolean(value)
+}
+
 function isUserRoleValid (value: any) {
   return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
 }
@@ -36,5 +44,6 @@ export {
   isUserRoleValid,
   isUserVideoQuotaValid,
   isUserUsernameValid,
-  isUserDisplayNSFWValid
+  isUserDisplayNSFWValid,
+  isUserAutoPlayVideoValid
 }
index 341086bd6d6aa72247782d666fa3ebc52520c319..ff322730faaed26d2cb6eaa156728e6bc59c6dee 100644 (file)
@@ -8,7 +8,7 @@ import { isTestInstance, root } from '../helpers/core-utils'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 125
+const LAST_MIGRATION_VERSION = 130
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0130-user-autoplay-video.ts b/server/initializers/migrations/0130-user-autoplay-video.ts
new file mode 100644 (file)
index 0000000..9f6878e
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+import * as Promise from 'bluebird'
+
+function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+  const q = utils.queryInterface
+
+  const data = {
+    type: Sequelize.BOOLEAN,
+    allowNull: false,
+    defaultValue: true
+  }
+
+  return q.addColumn('user', 'autoPlayVideo', data)
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 920176d07af046f8ff69441a15ffc05e6d5f05f6..a6fdbe26805ee1a2777b6c7f872db9204b99dd3b 100644 (file)
@@ -5,6 +5,7 @@ import { isSignupAllowed, logger } from '../../helpers'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
   isUserDisplayNSFWValid,
+  isUserAutoPlayVideoValid,
   isUserPasswordValid,
   isUserRoleValid,
   isUserUsernameValid,
@@ -86,6 +87,7 @@ const usersUpdateMeValidator = [
   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('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
 
   (req: express.Request, res: express.Response, next: express.NextFunction) => {
     // TODO: Add old password verification
index 26f04dcb5457a5f227b4ea4d7ec62a32caf9cfc2..70ed61e0783dfe9fa75d9556385b0e23e60c5963 100644 (file)
@@ -20,7 +20,7 @@ import {
 } from '../../helpers'
 import {
   isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
-  isUserVideoQuotaValid
+  isUserVideoQuotaValid, isUserAutoPlayVideoValid
 } from '../../helpers/custom-validators/users'
 import { OAuthTokenModel } from '../oauth/oauth-token'
 import { getSort, throwIfNotValid } from '../utils'
@@ -82,6 +82,12 @@ export class UserModel extends Model<UserModel> {
   @Column
   displayNSFW: boolean
 
+  @AllowNull(false)
+  @Default(true)
+  @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
+  @Column
+  autoPlayVideo: boolean
+
   @AllowNull(false)
   @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
   @Column
@@ -223,6 +229,7 @@ export class UserModel extends Model<UserModel> {
       username: this.username,
       email: this.email,
       displayNSFW: this.displayNSFW,
+      autoPlayVideo: this.autoPlayVideo,
       role: this.role,
       roleLabel: USER_ROLE_LABELS[ this.role ],
       videoQuota: this.videoQuota,
index 1e3533bf3cc5a3b83fb17507969e63fda3b711af..72488e5c4eb9e4a30d346d552df883c0e99a03ed 100644 (file)
@@ -350,6 +350,14 @@ describe('Test users API validators', function () {
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
     })
 
+    it('Should fail with an invalid autoPlayVideo attribute', async function () {
+      const fields = {
+        autoPlayVideo: -1
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
     it('Should fail with an non authenticated user', async function () {
       const fields = {
         password: 'my super password'
@@ -362,6 +370,7 @@ describe('Test users API validators', function () {
       const fields = {
         password: 'my super password',
         displayNSFW: true,
+        autoPlayVideo: false,
         email: 'super_email@example.com'
       }
 
index b3163b1e118f242d2c4b0d313074ce775df69827..67e4cc8c6ce7bb223d62b3d7d1652b33f3d6c3da 100644 (file)
@@ -415,6 +415,15 @@ describe('Test users', function () {
       .a('number')
   })
 
+  it('Should be able to change the autoPlayVideo attribute', async function () {
+    await updateMyUser(server.url, accessTokenUser, undefined, undefined, undefined, false)
+
+    const res = await getMyUserInformation(server.url, accessTokenUser)
+    const user = res.body
+
+    expect(user.autoPlayVideo).to.be.false
+  })
+
   it('Should be able to change the email display attribute', async function () {
     await updateMyUser(server.url, accessTokenUser, undefined, undefined, 'updated@example.com')
 
index ce04b9d9663babc6f22b549a89c7bf1d0ba6960a..a37d84ab479e8defb837e808648c77e962621b80 100644 (file)
@@ -111,12 +111,14 @@ function removeUser (url: string, userId: number, accessToken: string, expectedS
           .expect(expectedStatus)
 }
 
-function updateMyUser (url: string, accessToken: string, newPassword: string, displayNSFW?: boolean, email?: string) {
+function updateMyUser (url: string, accessToken: string, newPassword: string, displayNSFW?: boolean,
+  email?: string, autoPlayVideo?: boolean) {
   const path = '/api/v1/users/me'
 
   const toSend = {}
   if (newPassword !== undefined && newPassword !== null) toSend['password'] = newPassword
   if (displayNSFW !== undefined && displayNSFW !== null) toSend['displayNSFW'] = displayNSFW
+  if (autoPlayVideo !== undefined && autoPlayVideo !== null) toSend['autoPlayVideo'] = autoPlayVideo
   if (email !== undefined && email !== null) toSend['email'] = email
 
   return request(url)
index 0ee41a79be4aa90e18fe9ca2be0382269e0b5ea4..83417a7bd7440b3e45bc62dc18b7623012e900fb 100644 (file)
@@ -1,5 +1,6 @@
 export interface UserUpdateMe {
   displayNSFW?: boolean
+  autoPlayVideo?: boolean
   email?: string
   password?: string
 }
index 4b17881e5cd6e382b8e8d93d52c7ee1442e3a243..f2b43d371e727f7bc5fe446247b7265fe7de56bf 100644 (file)
@@ -7,6 +7,7 @@ export interface User {
   username: string
   email: string
   displayNSFW: boolean
+  autoPlayVideo: boolean
   role: UserRole
   videoQuota: number
   createdAt: Date