Add history on server side
authorChocobozzz <me@florianbigard.com>
Mon, 17 Dec 2018 14:52:38 +0000 (15:52 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 18 Dec 2018 10:35:50 +0000 (11:35 +0100)
Add ability to disable, clear and list user videos history

19 files changed:
server/controllers/api/users/index.ts
server/controllers/api/users/me.ts
server/controllers/api/users/my-history.ts [new file with mode: 0644]
server/helpers/custom-validators/users.ts
server/initializers/constants.ts
server/initializers/migrations/0300-user-videos-history-enabled.ts [new file with mode: 0644]
server/middlewares/validators/index.ts
server/middlewares/validators/user-history.ts [new file with mode: 0644]
server/middlewares/validators/videos/video-watch.ts
server/models/account/user-video-history.ts
server/models/account/user.ts
server/models/utils.ts
server/models/video/video.ts
server/tests/api/check-params/users.ts
server/tests/api/check-params/videos-history.ts
server/tests/api/videos/videos-history.ts
shared/models/users/user-update-me.model.ts
shared/utils/users/users.ts
shared/utils/videos/video-history.ts

index 87fab4a407b966b1ec068e0f06667ddefbd18cd5..bc24792a2da1df78567802c9c3b62ad612f529e7 100644 (file)
@@ -38,6 +38,7 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h
 import { meRouter } from './me'
 import { deleteUserToken } from '../../../lib/oauth-model'
 import { myBlocklistRouter } from './my-blocklist'
+import { myVideosHistoryRouter } from './my-history'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -55,6 +56,7 @@ const askSendEmailLimiter = new RateLimit({
 
 const usersRouter = express.Router()
 usersRouter.use('/', myBlocklistRouter)
+usersRouter.use('/', myVideosHistoryRouter)
 usersRouter.use('/', meRouter)
 
 usersRouter.get('/autocomplete',
index f712b0f0b085bfb347ad814ea4558f509981cd02..8a3208160caf7a7b013c66574d0c62006d6b4a94 100644 (file)
@@ -330,6 +330,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
   if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
   if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
   if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
+  if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
 
   await sequelizeTypescript.transaction(async t => {
     const userAccount = await AccountModel.load(user.Account.id)
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
new file mode 100644 (file)
index 0000000..6cd782c
--- /dev/null
@@ -0,0 +1,57 @@
+import * as express from 'express'
+import {
+  asyncMiddleware,
+  asyncRetryTransactionMiddleware,
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  userHistoryRemoveValidator
+} from '../../../middlewares'
+import { UserModel } from '../../../models/account/user'
+import { getFormattedObjects } from '../../../helpers/utils'
+import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
+import { sequelizeTypescript } from '../../../initializers'
+
+const myVideosHistoryRouter = express.Router()
+
+myVideosHistoryRouter.get('/me/history/videos',
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  asyncMiddleware(listMyVideosHistory)
+)
+
+myVideosHistoryRouter.post('/me/history/videos/remove',
+  authenticate,
+  userHistoryRemoveValidator,
+  asyncRetryTransactionMiddleware(removeUserHistory)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  myVideosHistoryRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function listMyVideosHistory (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+
+  const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function removeUserHistory (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+  const beforeDate = req.body.beforeDate || null
+
+  await sequelizeTypescript.transaction(t => {
+    return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t)
+  })
+
+  // Do not send the delete to other instances, we delete OUR copy of this video abuse
+
+  return res.type('json').status(204).end()
+}
index 1cb5e5b0f514a3d93efe669113ddbe1aadcabeb9..80652b4798eb566f2606da590e681f74c581abc7 100644 (file)
@@ -46,6 +46,10 @@ function isUserWebTorrentEnabledValid (value: any) {
   return isBooleanValid(value)
 }
 
+function isUserVideosHistoryEnabledValid (value: any) {
+  return isBooleanValid(value)
+}
+
 function isUserAutoPlayVideoValid (value: any) {
   return isBooleanValid(value)
 }
@@ -73,6 +77,7 @@ function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } |
 // ---------------------------------------------------------------------------
 
 export {
+  isUserVideosHistoryEnabledValid,
   isUserBlockedValid,
   isUserPasswordValid,
   isUserBlockedReasonValid,
index 6971ab775aa1c0e7e6389e47ac2f659755459ab2..6e463a1d601435fbe2cfcef1b15d0f9cdd23bcc1 100644 (file)
@@ -16,7 +16,7 @@ let config: IConfig = require('config')
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 295
+const LAST_MIGRATION_VERSION = 300
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts
new file mode 100644 (file)
index 0000000..aa5fc21
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    const data = {
+      type: Sequelize.BOOLEAN,
+      allowNull: false,
+      defaultValue: true
+    }
+
+    await utils.queryInterface.addColumn('user', 'videosHistoryEnabled', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 46c7f0f3ab876b467e41d12d66374b8f1b4c4eda..65dd00335ed7d6762c9ba28131fdd7e790e93526 100644 (file)
@@ -12,3 +12,4 @@ export * from './videos'
 export * from './webfinger'
 export * from './search'
 export * from './server'
+export * from './user-history'
diff --git a/server/middlewares/validators/user-history.ts b/server/middlewares/validators/user-history.ts
new file mode 100644 (file)
index 0000000..3c8971e
--- /dev/null
@@ -0,0 +1,30 @@
+import * as express from 'express'
+import 'express-validator'
+import { body, param, query } from 'express-validator/check'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import { ActorFollowModel } from '../../models/activitypub/actor-follow'
+import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
+import { UserModel } from '../../models/account/user'
+import { CONFIG } from '../../initializers'
+import { isDateValid, toArray } from '../../helpers/custom-validators/misc'
+
+const userHistoryRemoveValidator = [
+  body('beforeDate')
+    .optional()
+    .custom(isDateValid).withMessage('Should have a valid before date'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking userHistoryRemoveValidator parameters', { parameters: req.body })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+  userHistoryRemoveValidator
+}
index bca64662fbf9a0814a0af1dabd462915efcaf43a..c38ad8a10d284d18a1e9c3eb288007530ff6fe4f 100644 (file)
@@ -4,6 +4,7 @@ import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
 import { isVideoExist } from '../../../helpers/custom-validators/videos'
 import { areValidationErrors } from '../utils'
 import { logger } from '../../../helpers/logger'
+import { UserModel } from '../../../models/account/user'
 
 const videoWatchingValidator = [
   param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
@@ -17,6 +18,12 @@ const videoWatchingValidator = [
     if (areValidationErrors(req, res)) return
     if (!await isVideoExist(req.params.videoId, res, 'id')) return
 
+    const user = res.locals.oauth.token.User as UserModel
+    if (user.videosHistoryEnabled === false) {
+      logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id)
+      return res.status(409).end()
+    }
+
     return next()
   }
 ]
index 0476cad9deef992bc6347e7d6cbb387d598a6b4f..15cb399c952bc909305e331baf8cece53bccd45d 100644 (file)
@@ -1,6 +1,7 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Min, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoModel } from '../video/video'
 import { UserModel } from './user'
+import { Transaction, Op, DestroyOptions } from 'sequelize'
 
 @Table({
   tableName: 'userVideoHistory',
@@ -52,4 +53,34 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
     onDelete: 'CASCADE'
   })
   User: UserModel
+
+  static listForApi (user: UserModel, start: number, count: number) {
+    return VideoModel.listForApi({
+      start,
+      count,
+      sort: '-UserVideoHistories.updatedAt',
+      nsfw: null, // All
+      includeLocalVideos: true,
+      withFiles: false,
+      user,
+      historyOfUser: user
+    })
+  }
+
+  static removeHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) {
+    const query: DestroyOptions = {
+      where: {
+        userId: user.id
+      },
+      transaction: t
+    }
+
+    if (beforeDate) {
+      query.where.updatedAt = {
+        [Op.lt]: beforeDate
+      }
+    }
+
+    return UserVideoHistoryModel.destroy(query)
+  }
 }
index 1843603f1e935ec16c37241e75fe42c4357ffa94..ea017c338fbdd4f6d73b0bac60677028d0f62490 100644 (file)
@@ -32,7 +32,8 @@ import {
   isUserUsernameValid,
   isUserVideoQuotaDailyValid,
   isUserVideoQuotaValid,
-  isUserWebTorrentEnabledValid
+  isUserWebTorrentEnabledValid,
+  isUserVideosHistoryEnabledValid
 } from '../../helpers/custom-validators/users'
 import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
 import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -114,6 +115,12 @@ export class UserModel extends Model<UserModel> {
   @Column
   webTorrentEnabled: boolean
 
+  @AllowNull(false)
+  @Default(true)
+  @Is('UserVideosHistoryEnabled', value => throwIfNotValid(value, isUserVideosHistoryEnabledValid, 'Videos history enabled'))
+  @Column
+  videosHistoryEnabled: boolean
+
   @AllowNull(false)
   @Default(true)
   @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
index 60b0906e80964a4d5bf3e128dbd08b0b6289095e..6694eda690b0570b0cd450decf3fbef5490539be 100644 (file)
@@ -29,7 +29,7 @@ function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) {
     ]
   }
 
-  return [ [ field, direction ], lastSort ]
+  return [ field.split('.').concat([ direction ]), lastSort ]
 }
 
 function getSortOnModel (model: any, value: string, lastSort: string[] = [ 'id', 'ASC' ]) {
index adef37937436f0eb769bfbd4244d5d96bdc2f889..199ea9ea49c866cfce2b991d72e3ae7aa9f0e447 100644 (file)
@@ -153,7 +153,8 @@ type AvailableForListIDsOptions = {
   accountId?: number
   videoChannelId?: number
   trendingDays?: number
-  user?: UserModel
+  user?: UserModel,
+  historyOfUser?: UserModel
 }
 
 @Scopes({
@@ -416,6 +417,16 @@ type AvailableForListIDsOptions = {
       query.subQuery = false
     }
 
+    if (options.historyOfUser) {
+      query.include.push({
+        model: UserVideoHistoryModel,
+        required: true,
+        where: {
+          userId: options.historyOfUser.id
+        }
+      })
+    }
+
     return query
   },
   [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
@@ -987,7 +998,8 @@ export class VideoModel extends Model<VideoModel> {
     videoChannelId?: number,
     followerActorId?: number
     trendingDays?: number,
-    user?: UserModel
+    user?: UserModel,
+    historyOfUser?: UserModel
   }, countVideos = true) {
     if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
       throw new Error('Try to filter all-local but no user has not the see all videos right')
@@ -1026,6 +1038,7 @@ export class VideoModel extends Model<VideoModel> {
       videoChannelId: options.videoChannelId,
       includeLocalVideos: options.includeLocalVideos,
       user: options.user,
+      historyOfUser: options.historyOfUser,
       trendingDays
     }
 
@@ -1341,7 +1354,7 @@ export class VideoModel extends Model<VideoModel> {
     }
 
     const [ count, rowsId ] = await Promise.all([
-      countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined),
+      countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve<number>(undefined),
       VideoModel.scope(idsScope).findAll(query)
     ])
     const ids = rowsId.map(r => r.id)
index ec35429d36c7d4586748a8cf5ac866feaf54a351..f8044cbd4c910c4686d8de1e2cdbc752c05409cf 100644 (file)
@@ -308,6 +308,14 @@ describe('Test users API validators', function () {
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
     })
 
+    it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
+      const fields = {
+        videosHistoryEnabled: -1
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
     it('Should fail with an non authenticated user', async function () {
       const fields = {
         currentPassword: 'my super password',
index 09c6f7861d9572b91e7da4a499e79709891f2114..8c079a956c455aec6964fbcced561726bae6bafb 100644 (file)
@@ -3,8 +3,11 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  checkBadCountPagination,
+  checkBadStartPagination,
   flushTests,
   killallServers,
+  makeGetRequest,
   makePostBodyRequest,
   makePutBodyRequest,
   runServer,
@@ -16,7 +19,9 @@ import {
 const expect = chai.expect
 
 describe('Test videos history API validator', function () {
-  let path: string
+  let watchingPath: string
+  let myHistoryPath = '/api/v1/users/me/history/videos'
+  let myHistoryRemove = myHistoryPath + '/remove'
   let server: ServerInfo
 
   // ---------------------------------------------------------------
@@ -33,14 +38,14 @@ describe('Test videos history API validator', function () {
     const res = await uploadVideo(server.url, server.accessToken, {})
     const videoUUID = res.body.video.uuid
 
-    path = '/api/v1/videos/' + videoUUID + '/watching'
+    watchingPath = '/api/v1/videos/' + videoUUID + '/watching'
   })
 
   describe('When notifying a user is watching a video', function () {
 
     it('Should fail with an unauthenticated user', async function () {
       const fields = { currentTime: 5 }
-      await makePutBodyRequest({ url: server.url, path, fields, statusCodeExpected: 401 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, statusCodeExpected: 401 })
     })
 
     it('Should fail with an incorrect video id', async function () {
@@ -58,13 +63,68 @@ describe('Test videos history API validator', function () {
 
     it('Should fail with a bad current time', async function () {
       const fields = { currentTime: 'hello' }
-      await makePutBodyRequest({ url: server.url, path, fields, token: server.accessToken, statusCodeExpected: 400 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, token: server.accessToken, statusCodeExpected: 400 })
     })
 
     it('Should succeed with the correct parameters', async function () {
       const fields = { currentTime: 5 }
 
-      await makePutBodyRequest({ url: server.url, path, fields, token: server.accessToken, statusCodeExpected: 204 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, token: server.accessToken, statusCodeExpected: 204 })
+    })
+  })
+
+  describe('When listing user videos history', function () {
+    it('Should fail with a bad start pagination', async function () {
+      await checkBadStartPagination(server.url, myHistoryPath, server.accessToken)
+    })
+
+    it('Should fail with a bad count pagination', async function () {
+      await checkBadCountPagination(server.url, myHistoryPath, server.accessToken)
+    })
+
+    it('Should fail with an unauthenticated user', async function () {
+      await makeGetRequest({ url: server.url, path: myHistoryPath, statusCodeExpected: 401 })
+    })
+
+    it('Should succeed with the correct params', async function () {
+      await makeGetRequest({ url: server.url, token: server.accessToken, path: myHistoryPath, statusCodeExpected: 200 })
+    })
+  })
+
+  describe('When removing user videos history', function () {
+    it('Should fail with an unauthenticated user', async function () {
+      await makePostBodyRequest({ url: server.url, path: myHistoryPath + '/remove', statusCodeExpected: 401 })
+    })
+
+    it('Should fail with a bad beforeDate parameter', async function () {
+      const body = { beforeDate: '15' }
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        fields: body,
+        statusCodeExpected: 400
+      })
+    })
+
+    it('Should succeed with a valid beforeDate param', async function () {
+      const body = { beforeDate: new Date().toISOString() }
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        fields: body,
+        statusCodeExpected: 204
+      })
+    })
+
+    it('Should succeed without body', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        statusCodeExpected: 204
+      })
     })
   })
 
index 40ae94f797f198e1bdc9e79c833c82d252dd84a6..f654a422bd45eff49ae18dd0fd50944df6f1b775 100644 (file)
@@ -3,17 +3,21 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  createUser,
   flushTests,
   getVideosListWithToken,
   getVideoWithToken,
-  killallServers, makePutBodyRequest,
-  runServer, searchVideoWithToken,
+  killallServers,
+  runServer,
+  searchVideoWithToken,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo
+  updateMyUser,
+  uploadVideo,
+  userLogin
 } from '../../../../shared/utils'
 import { Video, VideoDetails } from '../../../../shared/models/videos'
-import { userWatchVideo } from '../../../../shared/utils/videos/video-history'
+import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/utils/videos/video-history'
 
 const expect = chai.expect
 
@@ -22,6 +26,8 @@ describe('Test videos history', function () {
   let video1UUID: string
   let video2UUID: string
   let video3UUID: string
+  let video3WatchedDate: Date
+  let userAccessToken: string
 
   before(async function () {
     this.timeout(30000)
@@ -46,6 +52,13 @@ describe('Test videos history', function () {
       const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
       video3UUID = res.body.video.uuid
     }
+
+    const user = {
+      username: 'user_1',
+      password: 'super password'
+    }
+    await createUser(server.url, server.accessToken, user.username, user.password)
+    userAccessToken = await userLogin(server, user)
   })
 
   it('Should get videos, without watching history', async function () {
@@ -62,8 +75,8 @@ describe('Test videos history', function () {
   })
 
   it('Should watch the first and second video', async function () {
-    await userWatchVideo(server.url, server.accessToken, video1UUID, 3)
     await userWatchVideo(server.url, server.accessToken, video2UUID, 8)
+    await userWatchVideo(server.url, server.accessToken, video1UUID, 3)
   })
 
   it('Should return the correct history when listing, searching and getting videos', async function () {
@@ -117,6 +130,68 @@ describe('Test videos history', function () {
     }
   })
 
+  it('Should have these videos when listing my history', async function () {
+    video3WatchedDate = new Date()
+    await userWatchVideo(server.url, server.accessToken, video3UUID, 2)
+
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(3)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 3')
+    expect(videos[1].name).to.equal('video 1')
+    expect(videos[2].name).to.equal('video 2')
+  })
+
+  it('Should not have videos history on another user', async function () {
+    const res = await listMyVideosHistory(server.url, userAccessToken)
+
+    expect(res.body.total).to.equal(0)
+    expect(res.body.data).to.have.lengthOf(0)
+  })
+
+  it('Should clear my history', async function () {
+    await removeMyVideosHistory(server.url, server.accessToken, video3WatchedDate.toISOString())
+  })
+
+  it('Should have my history cleared', async function () {
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(1)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 3')
+  })
+
+  it('Should disable videos history', async function () {
+    await updateMyUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      videosHistoryEnabled: false
+    })
+
+    await userWatchVideo(server.url, server.accessToken, video2UUID, 8, 409)
+  })
+
+  it('Should re-enable videos history', async function () {
+    await updateMyUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      videosHistoryEnabled: true
+    })
+
+    await userWatchVideo(server.url, server.accessToken, video1UUID, 8)
+
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(2)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 1')
+    expect(videos[1].name).to.equal('video 3')
+  })
+
   after(async function () {
     killallServers([ server ])
 
index 10edeee2e657cfe55710a8f87539095448034bb7..e24afab940848d92350963d1303b16d34d759765 100644 (file)
@@ -3,9 +3,12 @@ import { NSFWPolicyType } from '../videos/nsfw-policy.type'
 export interface UserUpdateMe {
   displayName?: string
   description?: string
-  nsfwPolicy?: NSFWPolicyType,
-  webTorrentEnabled?: boolean,
+  nsfwPolicy?: NSFWPolicyType
+
+  webTorrentEnabled?: boolean
   autoPlayVideo?: boolean
+  videosHistoryEnabled?: boolean
+
   email?: string
   currentPassword?: string
   password?: string
index 554e42c018e01fc8af0d58af3c6e2714a71aeb5e..61a7e375722e0aba6f3cc0748e85415eb9e2627d 100644 (file)
@@ -162,14 +162,15 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
 
 function updateMyUser (options: {
   url: string
-  accessToken: string,
-  currentPassword?: string,
-  newPassword?: string,
-  nsfwPolicy?: NSFWPolicyType,
-  email?: string,
+  accessToken: string
+  currentPassword?: string
+  newPassword?: string
+  nsfwPolicy?: NSFWPolicyType
+  email?: string
   autoPlayVideo?: boolean
-  displayName?: string,
+  displayName?: string
   description?: string
+  videosHistoryEnabled?: boolean
 }) {
   const path = '/api/v1/users/me'
 
@@ -181,6 +182,9 @@ function updateMyUser (options: {
   if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
   if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
   if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName
+  if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) {
+    toSend['videosHistoryEnabled'] = options.videosHistoryEnabled
+  }
 
   return makePutBodyRequest({
     url: options.url,
index 7635478f770b39a7e9f1015a20ab4bf87c507899..dc7095b4d7181c817c6ea7a4f352660a62fa5374 100644 (file)
@@ -1,14 +1,39 @@
-import { makePutBodyRequest } from '../requests/requests'
+import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
 
-function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number) {
+function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number, statusCodeExpected = 204) {
   const path = '/api/v1/videos/' + videoId + '/watching'
   const fields = { currentTime }
 
-  return makePutBodyRequest({ url, path, token, fields, statusCodeExpected: 204 })
+  return makePutBodyRequest({ url, path, token, fields, statusCodeExpected })
+}
+
+function listMyVideosHistory (url: string, token: string) {
+  const path = '/api/v1/users/me/history/videos'
+
+  return makeGetRequest({
+    url,
+    path,
+    token,
+    statusCodeExpected: 200
+  })
+}
+
+function removeMyVideosHistory (url: string, token: string, beforeDate?: string) {
+  const path = '/api/v1/users/me/history/videos/remove'
+
+  return makePostBodyRequest({
+    url,
+    path,
+    token,
+    fields: beforeDate ? { beforeDate } : {},
+    statusCodeExpected: 204
+  })
 }
 
 // ---------------------------------------------------------------------------
 
 export {
-  userWatchVideo
+  userWatchVideo,
+  listMyVideosHistory,
+  removeMyVideosHistory
 }