24ee59d29809253bc9a89c5d87760b46e13fc155
[oweals/peertube.git] / server / lib / request / request-video-qadu-scheduler.ts
1 import * as Sequelize from 'sequelize'
2
3 import { database as db } from '../../initializers/database'
4 import { AbstractRequestScheduler, RequestsObjects } from './abstract-request-scheduler'
5 import { logger } from '../../helpers'
6 import {
7   REQUESTS_VIDEO_QADU_LIMIT_PODS,
8   REQUESTS_VIDEO_QADU_LIMIT_PER_POD,
9   REQUEST_VIDEO_QADU_ENDPOINT,
10   REQUEST_VIDEO_QADU_TYPES
11 } from '../../initializers'
12 import { RequestsVideoQaduGrouped, PodInstance } from '../../models'
13 import { RemoteQaduVideoRequest, RequestVideoQaduType } from '../../../shared'
14
15 // We create a custom interface because we need "videos" attribute for our computations
16 interface RequestsObjectsCustom<U> extends RequestsObjects<U> {
17   [ id: string ]: {
18     toPod: PodInstance
19     endpoint: string
20     ids: number[] // ids
21     datas: U[]
22
23     videos: {
24       [ uuid: string ]: {
25         uuid: string
26         likes?: number
27         dislikes?: number
28         views?: number
29       }
30     }
31   }
32 }
33
34 export type RequestVideoQaduSchedulerOptions = {
35   type: RequestVideoQaduType
36   videoId: number
37   transaction?: Sequelize.Transaction
38 }
39
40 class RequestVideoQaduScheduler extends AbstractRequestScheduler<RequestsVideoQaduGrouped> {
41   constructor () {
42     super()
43
44     // We limit the size of the requests
45     this.limitPods = REQUESTS_VIDEO_QADU_LIMIT_PODS
46     this.limitPerPod = REQUESTS_VIDEO_QADU_LIMIT_PER_POD
47
48     this.description = 'video QADU requests'
49   }
50
51   getRequestModel () {
52     return db.RequestVideoQadu
53   }
54
55   getRequestToPodModel () {
56     return db.RequestVideoQadu
57   }
58
59   buildRequestsObjects (requests: RequestsVideoQaduGrouped) {
60     const requestsToMakeGrouped: RequestsObjectsCustom<RemoteQaduVideoRequest> = {}
61
62     for (const toPodId of Object.keys(requests)) {
63       for (const data of requests[toPodId]) {
64         const request = data.request
65         const video = data.video
66         const pod = data.pod
67         const hashKey = toPodId
68
69         if (!requestsToMakeGrouped[hashKey]) {
70           requestsToMakeGrouped[hashKey] = {
71             toPod: pod,
72             endpoint: REQUEST_VIDEO_QADU_ENDPOINT,
73             ids: [], // request ids, to delete them from the DB in the future
74             datas: [], // requests data
75             videos: {}
76           }
77         }
78
79         // Maybe another attribute was filled for this video
80         let videoData = requestsToMakeGrouped[hashKey].videos[video.id]
81         if (!videoData) videoData = { uuid: null }
82
83         switch (request.type) {
84         case REQUEST_VIDEO_QADU_TYPES.LIKES:
85           videoData.likes = video.likes
86           break
87
88         case REQUEST_VIDEO_QADU_TYPES.DISLIKES:
89           videoData.dislikes = video.dislikes
90           break
91
92         case REQUEST_VIDEO_QADU_TYPES.VIEWS:
93           videoData.views = video.views
94           break
95
96         default:
97           logger.error('Unknown request video QADU type %s.', request.type)
98           return undefined
99         }
100
101         // Do not forget the uuid so the remote pod can identify the video
102         videoData.uuid = video.uuid
103         requestsToMakeGrouped[hashKey].ids.push(request.id)
104
105         // Maybe there are multiple quick and dirty update for the same video
106         // We use this hash map to dedupe them
107         requestsToMakeGrouped[hashKey].videos[video.id] = videoData
108       }
109     }
110
111     // Now we deduped similar quick and dirty updates, we can build our requests data
112     for (const hashKey of Object.keys(requestsToMakeGrouped)) {
113       for (const videoUUID of Object.keys(requestsToMakeGrouped[hashKey].videos)) {
114         const videoData = requestsToMakeGrouped[hashKey].videos[videoUUID]
115
116         requestsToMakeGrouped[hashKey].datas.push({
117           data: videoData
118         })
119       }
120
121       // We don't need it anymore, it was just to build our data array
122       delete requestsToMakeGrouped[hashKey].videos
123     }
124
125     return requestsToMakeGrouped
126   }
127
128   async createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions) {
129     const dbRequestOptions: Sequelize.BulkCreateOptions = {}
130     if (transaction) dbRequestOptions.transaction = transaction
131
132     // Send the update to all our friends
133     const podIds = await db.Pod.listAllIds(transaction)
134     const queries = []
135     for (const podId of podIds) {
136       queries.push({ type, videoId, podId })
137     }
138
139     await db.RequestVideoQadu.bulkCreate(queries, dbRequestOptions)
140     return undefined
141   }
142 }
143
144 // ---------------------------------------------------------------------------
145
146 export {
147   RequestVideoQaduScheduler
148 }